#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 "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 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 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 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) 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 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; })); } void Yield() { eventInProgress = false; } } // namespace // **************************************************************************** // Test body // **************************************************************************** // Caching with all good results (data + NS_OK) TEST(TestFetchPreloader, CacheNoneBeforeConsume) { nsCOMPtr uri; NS_NewURI(getter_AddRefs(uri), "https://example.com"_ns); auto key = mozilla::PreloadHashKey::CreateAsFetch(uri, mozilla::CORS_NONE); RefPtr channel = new FakeChannel(); RefPtr preloader = new FakePreloader(channel); RefPtr doc; NS_NewXMLDocument(getter_AddRefs(doc)); EXPECT_TRUE(NS_SUCCEEDED( preloader->OpenChannel(key, uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty, doc, 0))); RefPtr 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 uri; NS_NewURI(getter_AddRefs(uri), "https://example.com"_ns); auto key = mozilla::PreloadHashKey::CreateAsFetch(uri, mozilla::CORS_NONE); RefPtr channel = new FakeChannel(); RefPtr preloader = new FakePreloader(channel); RefPtr doc; NS_NewXMLDocument(getter_AddRefs(doc)); EXPECT_TRUE(NS_SUCCEEDED( preloader->OpenChannel(key, uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty, doc, 0))); EXPECT_NS_SUCCEEDED(channel->Start()); RefPtr 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 uri; NS_NewURI(getter_AddRefs(uri), "https://example.com"_ns); auto key = mozilla::PreloadHashKey::CreateAsFetch(uri, mozilla::CORS_NONE); RefPtr channel = new FakeChannel(); RefPtr preloader = new FakePreloader(channel); RefPtr doc; NS_NewXMLDocument(getter_AddRefs(doc)); EXPECT_TRUE(NS_SUCCEEDED( preloader->OpenChannel(key, uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty, doc, 0))); EXPECT_NS_SUCCEEDED(channel->Start()); EXPECT_NS_SUCCEEDED(channel->Data("one"_ns)); EXPECT_NS_SUCCEEDED(channel->Data("two"_ns)); RefPtr 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 uri; NS_NewURI(getter_AddRefs(uri), "https://example.com"_ns); auto key = mozilla::PreloadHashKey::CreateAsFetch(uri, mozilla::CORS_NONE); RefPtr channel = new FakeChannel(); RefPtr preloader = new FakePreloader(channel); RefPtr doc; NS_NewXMLDocument(getter_AddRefs(doc)); EXPECT_TRUE(NS_SUCCEEDED( preloader->OpenChannel(key, uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty, doc, 0))); 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 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 uri; NS_NewURI(getter_AddRefs(uri), "https://example.com"_ns); auto key = mozilla::PreloadHashKey::CreateAsFetch(uri, mozilla::CORS_NONE); RefPtr channel = new FakeChannel(); RefPtr preloader = new FakePreloader(channel); RefPtr doc; NS_NewXMLDocument(getter_AddRefs(doc)); EXPECT_TRUE(NS_SUCCEEDED( preloader->OpenChannel(key, uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty, doc, 0))); 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 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 uri; NS_NewURI(getter_AddRefs(uri), "https://example.com"_ns); auto key = mozilla::PreloadHashKey::CreateAsFetch(uri, mozilla::CORS_NONE); RefPtr channel = new FakeChannel(); RefPtr preloader = new FakePreloader(channel); RefPtr doc; NS_NewXMLDocument(getter_AddRefs(doc)); EXPECT_TRUE(NS_SUCCEEDED( preloader->OpenChannel(key, uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty, doc, 0))); 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 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 uri; NS_NewURI(getter_AddRefs(uri), "https://example.com"_ns); auto key = mozilla::PreloadHashKey::CreateAsFetch(uri, mozilla::CORS_NONE); RefPtr channel = new FakeChannel(); RefPtr preloader = new FakePreloader(channel); RefPtr doc; NS_NewXMLDocument(getter_AddRefs(doc)); EXPECT_TRUE(NS_SUCCEEDED( preloader->OpenChannel(key, uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty, doc, 0))); 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 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 uri; NS_NewURI(getter_AddRefs(uri), "https://example.com"_ns); auto key = mozilla::PreloadHashKey::CreateAsFetch(uri, mozilla::CORS_NONE); RefPtr channel = new FakeChannel(); RefPtr preloader = new FakePreloader(channel); RefPtr doc; NS_NewXMLDocument(getter_AddRefs(doc)); EXPECT_TRUE(NS_SUCCEEDED( preloader->OpenChannel(key, uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty, doc, 0))); 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 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 uri; NS_NewURI(getter_AddRefs(uri), "https://example.com"_ns); auto key = mozilla::PreloadHashKey::CreateAsFetch(uri, mozilla::CORS_NONE); RefPtr channel = new FakeChannel(); RefPtr preloader = new FakePreloader(channel); RefPtr doc; NS_NewXMLDocument(getter_AddRefs(doc)); EXPECT_TRUE(NS_SUCCEEDED( preloader->OpenChannel(key, uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty, doc, 0))); 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 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 uri; NS_NewURI(getter_AddRefs(uri), "https://example.com"_ns); auto key = mozilla::PreloadHashKey::CreateAsFetch(uri, mozilla::CORS_NONE); RefPtr channel = new FakeChannel(); RefPtr preloader = new FakePreloader(channel); RefPtr doc; NS_NewXMLDocument(getter_AddRefs(doc)); EXPECT_TRUE(NS_SUCCEEDED( preloader->OpenChannel(key, uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty, doc, 0))); 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 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 uri; NS_NewURI(getter_AddRefs(uri), "https://example.com"_ns); auto key = mozilla::PreloadHashKey::CreateAsFetch(uri, mozilla::CORS_NONE); RefPtr channel = new FakeChannel(); RefPtr preloader = new FakePreloader(channel); RefPtr doc; NS_NewXMLDocument(getter_AddRefs(doc)); EXPECT_TRUE(NS_SUCCEEDED( preloader->OpenChannel(key, uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty, doc, 0))); 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 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 uri; NS_NewURI(getter_AddRefs(uri), "https://example.com"_ns); auto key = mozilla::PreloadHashKey::CreateAsFetch(uri, mozilla::CORS_NONE); RefPtr channel = new FakeChannel(); RefPtr preloader = new FakePreloader(channel); RefPtr doc; NS_NewXMLDocument(getter_AddRefs(doc)); EXPECT_TRUE(NS_SUCCEEDED( preloader->OpenChannel(key, uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty, doc, 0))); 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 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 uri; NS_NewURI(getter_AddRefs(uri), "https://example.com"_ns); auto key = mozilla::PreloadHashKey::CreateAsFetch(uri, mozilla::CORS_NONE); RefPtr channel = new FakeChannel(); RefPtr preloader = new FakePreloader(channel); RefPtr doc; NS_NewXMLDocument(getter_AddRefs(doc)); EXPECT_TRUE(NS_SUCCEEDED( preloader->OpenChannel(key, uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty, doc, 0))); EXPECT_NS_SUCCEEDED(channel->Start()); EXPECT_NS_SUCCEEDED(channel->Data("one"_ns)); EXPECT_NS_SUCCEEDED(channel->Data("two"_ns)); RefPtr 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 uri; NS_NewURI(getter_AddRefs(uri), "https://example.com"_ns); auto key = mozilla::PreloadHashKey::CreateAsFetch(uri, mozilla::CORS_NONE); RefPtr channel = new FakeChannel(); RefPtr preloader = new FakePreloader(channel); RefPtr doc; NS_NewXMLDocument(getter_AddRefs(doc)); EXPECT_TRUE(NS_SUCCEEDED( preloader->OpenChannel(key, uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty, doc, 0))); 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 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 uri; NS_NewURI(getter_AddRefs(uri), "https://example.com"_ns); auto key = mozilla::PreloadHashKey::CreateAsFetch(uri, mozilla::CORS_NONE); RefPtr channel = new FakeChannel(); RefPtr preloader = new FakePreloader(channel); RefPtr doc; NS_NewXMLDocument(getter_AddRefs(doc)); EXPECT_TRUE(NS_SUCCEEDED( preloader->OpenChannel(key, uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty, doc, 0))); 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 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 uri; NS_NewURI(getter_AddRefs(uri), "https://example.com"_ns); auto key = mozilla::PreloadHashKey::CreateAsFetch(uri, mozilla::CORS_NONE); RefPtr channel = new FakeChannel(); RefPtr preloader = new FakePreloader(channel); RefPtr doc; NS_NewXMLDocument(getter_AddRefs(doc)); EXPECT_TRUE(NS_SUCCEEDED( preloader->OpenChannel(key, uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty, doc, 0))); 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 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))); }