Source code
Revision control
Copy as Markdown
Other Tools
#include "gtest/gtest.h"
#include "mozilla/CORSMode.h"
#include "mozilla/dom/XMLDocument.h"
#include "mozilla/dom/ReferrerPolicyBinding.h"
#include "mozilla/FetchPreloader.h"
#include "mozilla/gtest/MozAssertions.h"
#include "mozilla/Maybe.h"
#include "mozilla/PreloadHashKey.h"
#include "mozilla/SpinEventLoopUntil.h"
#include "nsNetUtil.h"
#include "nsIChannel.h"
#include "nsIStreamListener.h"
#include "nsISupportsPriority.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::SetCanceledReason(const nsACString& aReason) {
return SetCanceledReasonImpl(aReason);
}
NS_IMETHODIMP FakeChannel::GetCanceledReason(nsACString& aReason) {
return GetCanceledReasonImpl(aReason);
}
NS_IMETHODIMP FakeChannel::CancelWithReason(nsresult aStatus,
const nsACString& aReason) {
return CancelWithReasonImpl(aStatus, aReason);
}
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(
nsITransportSecurityInfo** 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, uint64_t aHttpChannelId,
int32_t aSupportsPriorityValue) 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;
mozilla::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(
"uriloader:TestFetchPreloader:Await"_ns, [&]() {
bool yield = !eventInProgress;
eventInProgress = true; // Just for convenience
return yield;
}));
}
// WinBase.h defines this.
#undef Yield
void Yield() { eventInProgress = false; }
} // namespace
// ****************************************************************************
// Test body
// ****************************************************************************
// Caching with all good results (data + NS_OK)
TEST(TestFetchPreloader, CacheNoneBeforeConsume)
{
nsCOMPtr<nsIURI> uri;
auto key = mozilla::PreloadHashKey::CreateAsFetch(uri, mozilla::CORS_NONE);
RefPtr<FakeChannel> channel = new FakeChannel();
RefPtr<FakePreloader> preloader = new FakePreloader(channel);
RefPtr<mozilla::dom::Document> doc;
NS_NewXMLDocument(getter_AddRefs(doc), nullptr, nullptr);
EXPECT_TRUE(NS_SUCCEEDED(preloader->OpenChannel(
key, uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty, doc,
0, nsISupportsPriority::PRIORITY_NORMAL)));
RefPtr<FakeListener> listener = new FakeListener();
EXPECT_NS_SUCCEEDED(preloader->AsyncConsume(listener));
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
EXPECT_NS_SUCCEEDED(channel->Start());
EXPECT_NS_SUCCEEDED(channel->Data("one"_ns));
EXPECT_NS_SUCCEEDED(channel->Data("two"_ns));
EXPECT_NS_SUCCEEDED(channel->Data("three"_ns));
EXPECT_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;
auto key = mozilla::PreloadHashKey::CreateAsFetch(uri, mozilla::CORS_NONE);
RefPtr<FakeChannel> channel = new FakeChannel();
RefPtr<FakePreloader> preloader = new FakePreloader(channel);
RefPtr<mozilla::dom::Document> doc;
NS_NewXMLDocument(getter_AddRefs(doc), nullptr, nullptr);
EXPECT_TRUE(NS_SUCCEEDED(preloader->OpenChannel(
key, uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty, doc,
0, nsISupportsPriority::PRIORITY_NORMAL)));
EXPECT_NS_SUCCEEDED(channel->Start());
RefPtr<FakeListener> listener = new FakeListener();
EXPECT_NS_SUCCEEDED(preloader->AsyncConsume(listener));
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
NS_DispatchToMainThread(NS_NewRunnableFunction("test", [&]() {
EXPECT_TRUE(listener->mOnStart);
EXPECT_NS_SUCCEEDED(channel->Data("one"_ns));
EXPECT_NS_SUCCEEDED(channel->Data("two"_ns));
EXPECT_NS_SUCCEEDED(channel->Data("three"_ns));
EXPECT_TRUE(listener->mOnData.EqualsLiteral("onetwothree"));
EXPECT_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;
auto key = mozilla::PreloadHashKey::CreateAsFetch(uri, mozilla::CORS_NONE);
RefPtr<FakeChannel> channel = new FakeChannel();
RefPtr<FakePreloader> preloader = new FakePreloader(channel);
RefPtr<mozilla::dom::Document> doc;
NS_NewXMLDocument(getter_AddRefs(doc), nullptr, nullptr);
EXPECT_TRUE(NS_SUCCEEDED(preloader->OpenChannel(
key, uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty, doc,
0, nsISupportsPriority::PRIORITY_NORMAL)));
EXPECT_NS_SUCCEEDED(channel->Start());
EXPECT_NS_SUCCEEDED(channel->Data("one"_ns));
EXPECT_NS_SUCCEEDED(channel->Data("two"_ns));
RefPtr<FakeListener> listener = new FakeListener();
EXPECT_NS_SUCCEEDED(preloader->AsyncConsume(listener));
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
NS_DispatchToMainThread(NS_NewRunnableFunction("test", [&]() {
EXPECT_TRUE(listener->mOnStart);
EXPECT_NS_SUCCEEDED(channel->Data("three"_ns));
EXPECT_TRUE(listener->mOnData.EqualsLiteral("onetwothree"));
EXPECT_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;
auto key = mozilla::PreloadHashKey::CreateAsFetch(uri, mozilla::CORS_NONE);
RefPtr<FakeChannel> channel = new FakeChannel();
RefPtr<FakePreloader> preloader = new FakePreloader(channel);
RefPtr<mozilla::dom::Document> doc;
NS_NewXMLDocument(getter_AddRefs(doc), nullptr, nullptr);
EXPECT_TRUE(NS_SUCCEEDED(preloader->OpenChannel(
key, uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty, doc,
0, nsISupportsPriority::PRIORITY_NORMAL)));
EXPECT_NS_SUCCEEDED(channel->Start());
EXPECT_NS_SUCCEEDED(channel->Data("one"_ns));
EXPECT_NS_SUCCEEDED(channel->Data("two"_ns));
EXPECT_NS_SUCCEEDED(channel->Data("three"_ns));
// Request consumation of the preload...
RefPtr<FakeListener> listener = new FakeListener();
EXPECT_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_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;
auto key = mozilla::PreloadHashKey::CreateAsFetch(uri, mozilla::CORS_NONE);
RefPtr<FakeChannel> channel = new FakeChannel();
RefPtr<FakePreloader> preloader = new FakePreloader(channel);
RefPtr<mozilla::dom::Document> doc;
NS_NewXMLDocument(getter_AddRefs(doc), nullptr, nullptr);
EXPECT_TRUE(NS_SUCCEEDED(preloader->OpenChannel(
key, uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty, doc,
0, nsISupportsPriority::PRIORITY_NORMAL)));
EXPECT_NS_SUCCEEDED(channel->Start());
EXPECT_NS_SUCCEEDED(channel->Data("one"_ns));
EXPECT_NS_SUCCEEDED(channel->Data("two"_ns));
EXPECT_NS_SUCCEEDED(channel->Data("three"_ns));
EXPECT_NS_SUCCEEDED(channel->Stop(NS_OK));
RefPtr<FakeListener> listener = new FakeListener();
EXPECT_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;
auto key = mozilla::PreloadHashKey::CreateAsFetch(uri, mozilla::CORS_NONE);
RefPtr<FakeChannel> channel = new FakeChannel();
RefPtr<FakePreloader> preloader = new FakePreloader(channel);
RefPtr<mozilla::dom::Document> doc;
NS_NewXMLDocument(getter_AddRefs(doc), nullptr, nullptr);
EXPECT_TRUE(NS_SUCCEEDED(preloader->OpenChannel(
key, uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty, doc,
0, nsISupportsPriority::PRIORITY_NORMAL)));
EXPECT_NS_SUCCEEDED(channel->Start());
EXPECT_NS_SUCCEEDED(channel->Data("one"_ns));
EXPECT_NS_SUCCEEDED(channel->Data("two"_ns));
EXPECT_NS_SUCCEEDED(channel->Data("three"_ns));
EXPECT_NS_FAILED(channel->Stop(ERROR_ONSTOP));
RefPtr<FakeListener> listener = new FakeListener();
EXPECT_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;
auto key = mozilla::PreloadHashKey::CreateAsFetch(uri, mozilla::CORS_NONE);
RefPtr<FakeChannel> channel = new FakeChannel();
RefPtr<FakePreloader> preloader = new FakePreloader(channel);
RefPtr<mozilla::dom::Document> doc;
NS_NewXMLDocument(getter_AddRefs(doc), nullptr, nullptr);
EXPECT_TRUE(NS_SUCCEEDED(preloader->OpenChannel(
key, uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty, doc,
0, nsISupportsPriority::PRIORITY_NORMAL)));
EXPECT_NS_SUCCEEDED(channel->Start());
EXPECT_NS_SUCCEEDED(channel->Data("one"_ns));
EXPECT_NS_SUCCEEDED(channel->Data("two"_ns));
channel->Cancel(ERROR_CANCEL);
EXPECT_NS_FAILED(channel->Stop(ERROR_CANCEL));
RefPtr<FakeListener> listener = new FakeListener();
EXPECT_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;
auto key = mozilla::PreloadHashKey::CreateAsFetch(uri, mozilla::CORS_NONE);
RefPtr<FakeChannel> channel = new FakeChannel();
RefPtr<FakePreloader> preloader = new FakePreloader(channel);
RefPtr<mozilla::dom::Document> doc;
NS_NewXMLDocument(getter_AddRefs(doc), nullptr, nullptr);
EXPECT_TRUE(NS_SUCCEEDED(preloader->OpenChannel(
key, uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty, doc,
0, nsISupportsPriority::PRIORITY_NORMAL)));
EXPECT_NS_SUCCEEDED(channel->Start());
EXPECT_NS_SUCCEEDED(channel->Data("one"_ns));
EXPECT_NS_SUCCEEDED(channel->Data("two"_ns));
EXPECT_NS_SUCCEEDED(channel->Data("three"_ns));
EXPECT_NS_SUCCEEDED(channel->Stop(NS_OK));
RefPtr<FakeListener> listener = new FakeListener();
listener->mOnStartResult = ERROR_THROW;
EXPECT_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;
auto key = mozilla::PreloadHashKey::CreateAsFetch(uri, mozilla::CORS_NONE);
RefPtr<FakeChannel> channel = new FakeChannel();
RefPtr<FakePreloader> preloader = new FakePreloader(channel);
RefPtr<mozilla::dom::Document> doc;
NS_NewXMLDocument(getter_AddRefs(doc), nullptr, nullptr);
EXPECT_TRUE(NS_SUCCEEDED(preloader->OpenChannel(
key, uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty, doc,
0, nsISupportsPriority::PRIORITY_NORMAL)));
EXPECT_NS_SUCCEEDED(channel->Start());
EXPECT_NS_SUCCEEDED(channel->Data("one"_ns));
EXPECT_NS_SUCCEEDED(channel->Data("two"_ns));
EXPECT_NS_SUCCEEDED(channel->Data("three"_ns));
EXPECT_NS_SUCCEEDED(channel->Stop(NS_OK));
RefPtr<FakeListener> listener = new FakeListener();
listener->mOnDataResult = ERROR_THROW;
EXPECT_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;
auto key = mozilla::PreloadHashKey::CreateAsFetch(uri, mozilla::CORS_NONE);
RefPtr<FakeChannel> channel = new FakeChannel();
RefPtr<FakePreloader> preloader = new FakePreloader(channel);
RefPtr<mozilla::dom::Document> doc;
NS_NewXMLDocument(getter_AddRefs(doc), nullptr, nullptr);
EXPECT_TRUE(NS_SUCCEEDED(preloader->OpenChannel(
key, uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty, doc,
0, nsISupportsPriority::PRIORITY_NORMAL)));
EXPECT_NS_SUCCEEDED(channel->Start());
EXPECT_NS_SUCCEEDED(channel->Data("one"_ns));
EXPECT_NS_SUCCEEDED(channel->Data("two"_ns));
EXPECT_NS_SUCCEEDED(channel->Data("three"_ns));
EXPECT_NS_SUCCEEDED(channel->Stop(NS_OK));
RefPtr<FakeListener> listener = new FakeListener();
listener->mOnStopResult = ERROR_THROW;
EXPECT_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;
auto key = mozilla::PreloadHashKey::CreateAsFetch(uri, mozilla::CORS_NONE);
RefPtr<FakeChannel> channel = new FakeChannel();
RefPtr<FakePreloader> preloader = new FakePreloader(channel);
RefPtr<mozilla::dom::Document> doc;
NS_NewXMLDocument(getter_AddRefs(doc), nullptr, nullptr);
EXPECT_TRUE(NS_SUCCEEDED(preloader->OpenChannel(
key, uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty, doc,
0, nsISupportsPriority::PRIORITY_NORMAL)));
EXPECT_NS_SUCCEEDED(channel->Start());
EXPECT_NS_SUCCEEDED(channel->Data("one"_ns));
EXPECT_NS_SUCCEEDED(channel->Data("two"_ns));
EXPECT_NS_SUCCEEDED(channel->Data("three"_ns));
EXPECT_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_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;
auto key = mozilla::PreloadHashKey::CreateAsFetch(uri, mozilla::CORS_NONE);
RefPtr<FakeChannel> channel = new FakeChannel();
RefPtr<FakePreloader> preloader = new FakePreloader(channel);
RefPtr<mozilla::dom::Document> doc;
NS_NewXMLDocument(getter_AddRefs(doc), nullptr, nullptr);
EXPECT_TRUE(NS_SUCCEEDED(preloader->OpenChannel(
key, uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty, doc,
0, nsISupportsPriority::PRIORITY_NORMAL)));
EXPECT_NS_SUCCEEDED(channel->Start());
EXPECT_NS_SUCCEEDED(channel->Data("one"_ns));
EXPECT_NS_SUCCEEDED(channel->Data("two"_ns));
EXPECT_NS_SUCCEEDED(channel->Data("three"_ns));
EXPECT_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_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;
auto key = mozilla::PreloadHashKey::CreateAsFetch(uri, mozilla::CORS_NONE);
RefPtr<FakeChannel> channel = new FakeChannel();
RefPtr<FakePreloader> preloader = new FakePreloader(channel);
RefPtr<mozilla::dom::Document> doc;
NS_NewXMLDocument(getter_AddRefs(doc), nullptr, nullptr);
EXPECT_TRUE(NS_SUCCEEDED(preloader->OpenChannel(
key, uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty, doc,
0, nsISupportsPriority::PRIORITY_NORMAL)));
EXPECT_NS_SUCCEEDED(channel->Start());
EXPECT_NS_SUCCEEDED(channel->Data("one"_ns));
EXPECT_NS_SUCCEEDED(channel->Data("two"_ns));
RefPtr<FakeListener> listener = new FakeListener();
listener->mCancelIn = FakeListener::OnData;
EXPECT_NS_SUCCEEDED(preloader->AsyncConsume(listener));
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
NS_DispatchToMainThread(NS_NewRunnableFunction("test", [&]() {
EXPECT_NS_FAILED(channel->Data("three"_ns));
EXPECT_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;
auto key = mozilla::PreloadHashKey::CreateAsFetch(uri, mozilla::CORS_NONE);
RefPtr<FakeChannel> channel = new FakeChannel();
RefPtr<FakePreloader> preloader = new FakePreloader(channel);
RefPtr<mozilla::dom::Document> doc;
NS_NewXMLDocument(getter_AddRefs(doc), nullptr, nullptr);
EXPECT_TRUE(NS_SUCCEEDED(preloader->OpenChannel(
key, uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty, doc,
0, nsISupportsPriority::PRIORITY_NORMAL)));
EXPECT_NS_SUCCEEDED(channel->Start());
EXPECT_NS_SUCCEEDED(channel->Data("one"_ns));
EXPECT_NS_SUCCEEDED(channel->Data("two"_ns));
// 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_NS_SUCCEEDED(channel->Data("three"_ns));
EXPECT_NS_SUCCEEDED(channel->Stop(NS_OK));
}));
RefPtr<FakeListener> listener = new FakeListener();
listener->mCancelIn = FakeListener::OnStart;
EXPECT_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;
auto key = mozilla::PreloadHashKey::CreateAsFetch(uri, mozilla::CORS_NONE);
RefPtr<FakeChannel> channel = new FakeChannel();
RefPtr<FakePreloader> preloader = new FakePreloader(channel);
RefPtr<mozilla::dom::Document> doc;
NS_NewXMLDocument(getter_AddRefs(doc), nullptr, nullptr);
EXPECT_TRUE(NS_SUCCEEDED(preloader->OpenChannel(
key, uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty, doc,
0, nsISupportsPriority::PRIORITY_NORMAL)));
EXPECT_NS_SUCCEEDED(channel->Start());
EXPECT_NS_SUCCEEDED(channel->Data("one"_ns));
EXPECT_NS_SUCCEEDED(channel->Data("two"_ns));
// 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_NS_SUCCEEDED(channel->Data("three"_ns));
EXPECT_NS_SUCCEEDED(channel->Stop(NS_OK));
}));
RefPtr<FakeListener> listener = new FakeListener();
listener->mCancelIn = FakeListener::OnData;
EXPECT_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;
auto key = mozilla::PreloadHashKey::CreateAsFetch(uri, mozilla::CORS_NONE);
RefPtr<FakeChannel> channel = new FakeChannel();
RefPtr<FakePreloader> preloader = new FakePreloader(channel);
RefPtr<mozilla::dom::Document> doc;
NS_NewXMLDocument(getter_AddRefs(doc), nullptr, nullptr);
EXPECT_TRUE(NS_SUCCEEDED(preloader->OpenChannel(
key, uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty, doc,
0, nsISupportsPriority::PRIORITY_NORMAL)));
EXPECT_NS_SUCCEEDED(channel->Start());
EXPECT_NS_SUCCEEDED(channel->Data("one"_ns));
EXPECT_NS_SUCCEEDED(channel->Data("two"_ns));
// 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_NS_SUCCEEDED(channel->Data("three"_ns));
EXPECT_NS_SUCCEEDED(channel->Stop(NS_OK));
}));
RefPtr<FakeListener> listener = new FakeListener();
listener->mOnStartResult = ERROR_THROW;
EXPECT_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)));
}