Bug 1706594 - Add nsICancelable out param to nsBaseChannel::BeginAsyncRead virtual method. r=nika,necko-reviewers,valentin

This patch introduces:

- a second nsICancelable out param in nsBaseChannel::BeginAsyncRead, which is meant to be non-null
  when the subclass is unable to set the nsIRequest out param right away (e.g because there is
  some async work needed before the subclass is able to get an nsIRequest instance).
  The nsICancelable does only allow the channel to cancel the underlying request (and stream pump),
  but it doesn't allow the channel to be suspended and resumed, but that was already the case
  for the subclasses that were already not returning an nsIRequest instance (e.g. like the
  ExtensionProtocolHandler was doing when it is asynchrously retriving a stream from the parent
  process).

- require NotNull<nsCOMPtr<...>> in the SimpleChannel's BeginAsyncRead callback signature, to make harder
  to overlook the requirement of returning an nsIRequest or nsICancelable.

- a diagnostic assertion (from SimpleChannel's BeginAsyncRead method) to more visible fail if we still end
  up with neither an nsIRequest or nsICancelable result as SimpleChannel callback result.

- changes to ExtensionProtocolHandler, PageThumbProtocolHandler and nsAnnoProtocolHandler to adapt them
  to the new nsBaseChannel::BeginAsyncRead signature.

Differential Revision: https://phabricator.services.mozilla.com/D125545
This commit is contained in:
Luca Greco 2021-10-11 11:06:47 +00:00
Родитель 557947a3d8
Коммит e0845722c3
7 изменённых файлов: 262 добавлений и 64 удалений

Просмотреть файл

@ -43,15 +43,32 @@ nsresult SimpleChannel::OpenContentStream(bool async,
} }
nsresult SimpleChannel::BeginAsyncRead(nsIStreamListener* listener, nsresult SimpleChannel::BeginAsyncRead(nsIStreamListener* listener,
nsIRequest** request) { nsIRequest** request,
nsICancelable** cancelableRequest) {
NS_ENSURE_TRUE(mCallbacks, NS_ERROR_UNEXPECTED); NS_ENSURE_TRUE(mCallbacks, NS_ERROR_UNEXPECTED);
nsCOMPtr<nsIRequest> req; RequestOrReason res = mCallbacks->StartAsyncRead(listener, this);
MOZ_TRY_VAR(req, mCallbacks->StartAsyncRead(listener, this));
if (res.isErr()) {
return res.propagateErr();
}
mCallbacks = nullptr; mCallbacks = nullptr;
req.forget(request); RequestOrCancelable value = res.unwrap();
if (value.is<NotNullRequest>()) {
nsCOMPtr<nsIRequest> req = value.as<NotNullRequest>().get();
req.forget(request);
} else if (value.is<NotNullCancelable>()) {
nsCOMPtr<nsICancelable> cancelable = value.as<NotNullCancelable>().get();
cancelable.forget(cancelableRequest);
} else {
MOZ_ASSERT_UNREACHABLE(
"StartAsyncRead didn't return a NotNull nsIRequest or nsICancelable.");
return NS_ERROR_UNEXPECTED;
}
return NS_OK; return NS_OK;
} }

Просмотреть файл

@ -25,7 +25,10 @@ class nsIURI;
namespace mozilla { namespace mozilla {
using InputStreamOrReason = Result<nsCOMPtr<nsIInputStream>, nsresult>; using InputStreamOrReason = Result<nsCOMPtr<nsIInputStream>, nsresult>;
using RequestOrReason = Result<nsCOMPtr<nsIRequest>, nsresult>; using NotNullRequest = NotNull<nsCOMPtr<nsIRequest>>;
using NotNullCancelable = NotNull<nsCOMPtr<nsICancelable>>;
using RequestOrCancelable = Variant<NotNullRequest, NotNullCancelable>;
using RequestOrReason = Result<RequestOrCancelable, nsresult>;
namespace net { namespace net {
@ -78,7 +81,8 @@ class SimpleChannel : public nsBaseChannel {
nsIChannel** channel) override; nsIChannel** channel) override;
virtual nsresult BeginAsyncRead(nsIStreamListener* listener, virtual nsresult BeginAsyncRead(nsIStreamListener* listener,
nsIRequest** request) override; nsIRequest** request,
nsICancelable** cancelableRequest) override;
private: private:
UniquePtr<SimpleChannelCallbacks> mCallbacks; UniquePtr<SimpleChannelCallbacks> mCallbacks;

Просмотреть файл

@ -195,8 +195,11 @@ nsresult nsBaseChannel::PushStreamConverter(const char* fromType,
nsresult nsBaseChannel::BeginPumpingData() { nsresult nsBaseChannel::BeginPumpingData() {
nsresult rv; nsresult rv;
rv = BeginAsyncRead(this, getter_AddRefs(mRequest)); rv = BeginAsyncRead(this, getter_AddRefs(mRequest),
getter_AddRefs(mCancelableAsyncRequest));
if (NS_SUCCEEDED(rv)) { if (NS_SUCCEEDED(rv)) {
MOZ_ASSERT(mRequest || mCancelableAsyncRequest,
"should have got a request or cancelable");
mPumpingData = true; mPumpingData = true;
return NS_OK; return NS_OK;
} }
@ -373,6 +376,10 @@ nsBaseChannel::Cancel(nsresult status) {
mCanceled = true; mCanceled = true;
mStatus = status; mStatus = status;
if (mCancelableAsyncRequest) {
mCancelableAsyncRequest->Cancel(status);
}
if (mRequest) { if (mRequest) {
mRequest->Cancel(status); mRequest->Cancel(status);
} }
@ -788,6 +795,7 @@ static void CallUnknownTypeSniffer(void* aClosure, const uint8_t* aData,
NS_IMETHODIMP NS_IMETHODIMP
nsBaseChannel::OnStartRequest(nsIRequest* request) { nsBaseChannel::OnStartRequest(nsIRequest* request) {
MOZ_ASSERT_IF(mRequest, request == mRequest); MOZ_ASSERT_IF(mRequest, request == mRequest);
MOZ_ASSERT_IF(mCancelableAsyncRequest, !mRequest);
nsAutoCString scheme; nsAutoCString scheme;
mURI->GetScheme(scheme); mURI->GetScheme(scheme);
@ -824,6 +832,7 @@ nsBaseChannel::OnStopRequest(nsIRequest* request, nsresult status) {
// Cause Pending to return false. // Cause Pending to return false.
mPump = nullptr; mPump = nullptr;
mRequest = nullptr; mRequest = nullptr;
mCancelableAsyncRequest = nullptr;
mPumpingData = false; mPumpingData = false;
if (mListener) { // null in case of redirect if (mListener) { // null in case of redirect

Просмотреть файл

@ -30,6 +30,7 @@
#include "nsThreadUtils.h" #include "nsThreadUtils.h"
class nsIInputStream; class nsIInputStream;
class nsICancelable;
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// nsBaseChannel is designed to be subclassed. The subclass is responsible for // nsBaseChannel is designed to be subclassed. The subclass is responsible for
@ -100,10 +101,23 @@ class nsBaseChannel
// //
// On success, the callee must begin pumping data to the stream listener, // On success, the callee must begin pumping data to the stream listener,
// and at some point call OnStartRequest followed by OnStopRequest. // and at some point call OnStartRequest followed by OnStopRequest.
// Additionally, it may provide a request object which may be used to //
// suspend, resume, and cancel the underlying request. // Additionally, when a successful nsresult is returned, then the subclass
// should be setting through its two out params either:
// - a request object, which may be used to suspend, resume, and cancel
// the underlying request.
// - or a cancelable object (e.g. when a request can't be returned right away
// due to some async work needed to retrieve it). which may be used to
// cancel the underlying request (e.g. because the channel has been
// canceled)
//
// Not returning a request or cancelable leads to potentially leaking the
// an underling stream pump (which would keep to be pumping data even after
// the channel has been canceled and nothing is going to handle the data
// available, e.g. see Bug 1706594).
virtual nsresult BeginAsyncRead(nsIStreamListener* listener, virtual nsresult BeginAsyncRead(nsIStreamListener* listener,
nsIRequest** request) { nsIRequest** request,
nsICancelable** cancelableRequest) {
return NS_ERROR_NOT_IMPLEMENTED; return NS_ERROR_NOT_IMPLEMENTED;
} }
@ -271,6 +285,7 @@ class nsBaseChannel
RefPtr<nsInputStreamPump> mPump; RefPtr<nsInputStreamPump> mPump;
RefPtr<nsIRequest> mRequest; RefPtr<nsIRequest> mRequest;
nsCOMPtr<nsICancelable> mCancelableAsyncRequest;
bool mPumpingData{false}; bool mPumpingData{false};
nsCOMPtr<nsIProgressEventSink> mProgressSink; nsCOMPtr<nsIProgressEventSink> mProgressSink;
nsCOMPtr<nsIURI> mOriginalURI; nsCOMPtr<nsIURI> mOriginalURI;

Просмотреть файл

@ -25,6 +25,7 @@
#include "nsContentUtils.h" #include "nsContentUtils.h"
#include "nsServiceManagerUtils.h" #include "nsServiceManagerUtils.h"
#include "nsDirectoryServiceDefs.h" #include "nsDirectoryServiceDefs.h"
#include "nsICancelable.h"
#include "nsIFile.h" #include "nsIFile.h"
#include "nsIFileChannel.h" #include "nsIFileChannel.h"
#include "nsIFileStreams.h" #include "nsIFileStreams.h"
@ -98,7 +99,10 @@ StaticRefPtr<ExtensionProtocolHandler> ExtensionProtocolHandler::sSingleton;
* stream or file descriptor from the parent for a remote moz-extension load * stream or file descriptor from the parent for a remote moz-extension load
* from the child. * from the child.
*/ */
class ExtensionStreamGetter : public RefCounted<ExtensionStreamGetter> { class ExtensionStreamGetter final : public nsICancelable {
NS_DECL_ISUPPORTS
NS_DECL_NSICANCELABLE
public: public:
// To use when getting a remote input stream for a resource // To use when getting a remote input stream for a resource
// in an unpacked extension. // in an unpacked extension.
@ -128,8 +132,6 @@ class ExtensionStreamGetter : public RefCounted<ExtensionStreamGetter> {
SetupEventTarget(); SetupEventTarget();
} }
~ExtensionStreamGetter() = default;
void SetupEventTarget() { void SetupEventTarget() {
mMainThreadEventTarget = nsContentUtils::GetEventTargetByLoadInfo( mMainThreadEventTarget = nsContentUtils::GetEventTargetByLoadInfo(
mLoadInfo, TaskCategory::Other); mLoadInfo, TaskCategory::Other);
@ -139,8 +141,7 @@ class ExtensionStreamGetter : public RefCounted<ExtensionStreamGetter> {
} }
// Get an input stream or file descriptor from the parent asynchronously. // Get an input stream or file descriptor from the parent asynchronously.
Result<Ok, nsresult> GetAsync(nsIStreamListener* aListener, RequestOrReason GetAsync(nsIStreamListener* aListener, nsIChannel* aChannel);
nsIChannel* aChannel);
// Handle an input stream being returned from the parent // Handle an input stream being returned from the parent
void OnStream(already_AddRefed<nsIInputStream> aStream); void OnStream(already_AddRefed<nsIInputStream> aStream);
@ -151,19 +152,24 @@ class ExtensionStreamGetter : public RefCounted<ExtensionStreamGetter> {
static void CancelRequest(nsIStreamListener* aListener, nsIChannel* aChannel, static void CancelRequest(nsIStreamListener* aListener, nsIChannel* aChannel,
nsresult aResult); nsresult aResult);
MOZ_DECLARE_REFCOUNTED_TYPENAME(ExtensionStreamGetter)
private: private:
~ExtensionStreamGetter() = default;
nsCOMPtr<nsIURI> mURI; nsCOMPtr<nsIURI> mURI;
nsCOMPtr<nsILoadInfo> mLoadInfo; nsCOMPtr<nsILoadInfo> mLoadInfo;
nsCOMPtr<nsIJARChannel> mJarChannel; nsCOMPtr<nsIJARChannel> mJarChannel;
nsCOMPtr<nsIInputStreamPump> mPump;
nsCOMPtr<nsIFile> mJarFile; nsCOMPtr<nsIFile> mJarFile;
nsCOMPtr<nsIStreamListener> mListener; nsCOMPtr<nsIStreamListener> mListener;
nsCOMPtr<nsIChannel> mChannel; nsCOMPtr<nsIChannel> mChannel;
nsCOMPtr<nsISerialEventTarget> mMainThreadEventTarget; nsCOMPtr<nsISerialEventTarget> mMainThreadEventTarget;
bool mIsJarChannel; bool mIsJarChannel;
bool mCanceled{false};
nsresult mStatus{NS_OK};
}; };
NS_IMPL_ISUPPORTS(ExtensionStreamGetter, nsICancelable)
class ExtensionJARFileOpener final : public nsISupports { class ExtensionJARFileOpener final : public nsISupports {
public: public:
ExtensionJARFileOpener(nsIFile* aFile, ExtensionJARFileOpener(nsIFile* aFile,
@ -228,14 +234,16 @@ NS_IMPL_ISUPPORTS(ExtensionJARFileOpener, nsISupports)
#define DEFAULT_THREAD_TIMEOUT_MS 30000 #define DEFAULT_THREAD_TIMEOUT_MS 30000
// Request an FD or input stream from the parent. // Request an FD or input stream from the parent.
Result<Ok, nsresult> ExtensionStreamGetter::GetAsync( RequestOrReason ExtensionStreamGetter::GetAsync(nsIStreamListener* aListener,
nsIStreamListener* aListener, nsIChannel* aChannel) { nsIChannel* aChannel) {
MOZ_ASSERT(IsNeckoChild()); MOZ_ASSERT(IsNeckoChild());
MOZ_ASSERT(mMainThreadEventTarget); MOZ_ASSERT(mMainThreadEventTarget);
mListener = aListener; mListener = aListener;
mChannel = aChannel; mChannel = aChannel;
nsCOMPtr<nsICancelable> cancelableRequest(this);
RefPtr<ExtensionStreamGetter> self = this; RefPtr<ExtensionStreamGetter> self = this;
if (mIsJarChannel) { if (mIsJarChannel) {
// Request an FD for this moz-extension URI // Request an FD for this moz-extension URI
@ -245,7 +253,7 @@ Result<Ok, nsresult> ExtensionStreamGetter::GetAsync(
[self](const mozilla::ipc::ResponseRejectReason) { [self](const mozilla::ipc::ResponseRejectReason) {
self->OnFD(FileDescriptor()); self->OnFD(FileDescriptor());
}); });
return Ok(); return RequestOrCancelable(WrapNotNull(cancelableRequest));
} }
// Request an input stream for this moz-extension URI // Request an input stream for this moz-extension URI
@ -257,7 +265,29 @@ Result<Ok, nsresult> ExtensionStreamGetter::GetAsync(
[self](const mozilla::ipc::ResponseRejectReason) { [self](const mozilla::ipc::ResponseRejectReason) {
self->OnStream(nullptr); self->OnStream(nullptr);
}); });
return Ok(); return RequestOrCancelable(WrapNotNull(cancelableRequest));
}
// Called to cancel the ongoing async request.
NS_IMETHODIMP
ExtensionStreamGetter::Cancel(nsresult aStatus) {
if (mCanceled) {
return NS_OK;
}
mCanceled = true;
mStatus = aStatus;
if (mPump) {
mPump->Cancel(aStatus);
mPump = nullptr;
}
if (mIsJarChannel && mJarChannel) {
mJarChannel->Cancel(aStatus);
}
return NS_OK;
} }
// static // static
@ -275,20 +305,26 @@ void ExtensionStreamGetter::CancelRequest(nsIStreamListener* aListener,
// Handle an input stream sent from the parent. // Handle an input stream sent from the parent.
void ExtensionStreamGetter::OnStream(already_AddRefed<nsIInputStream> aStream) { void ExtensionStreamGetter::OnStream(already_AddRefed<nsIInputStream> aStream) {
MOZ_ASSERT(IsNeckoChild()); MOZ_ASSERT(IsNeckoChild());
MOZ_ASSERT(mChannel);
MOZ_ASSERT(mListener); MOZ_ASSERT(mListener);
MOZ_ASSERT(mMainThreadEventTarget); MOZ_ASSERT(mMainThreadEventTarget);
nsCOMPtr<nsIInputStream> stream = std::move(aStream); nsCOMPtr<nsIInputStream> stream = std::move(aStream);
nsCOMPtr<nsIChannel> channel = std::move(mChannel);
// We must keep an owning reference to the listener // We must keep an owning reference to the listener
// until we pass it on to AsyncRead. // until we pass it on to AsyncRead.
nsCOMPtr<nsIStreamListener> listener = std::move(mListener); nsCOMPtr<nsIStreamListener> listener = std::move(mListener);
MOZ_ASSERT(mChannel); if (mCanceled) {
// The channel that has created this stream getter has been canceled.
CancelRequest(listener, channel, mStatus);
return;
}
if (!stream) { if (!stream) {
// The parent didn't send us back a stream. // The parent didn't send us back a stream.
CancelRequest(listener, mChannel, NS_ERROR_FILE_ACCESS_DENIED); CancelRequest(listener, channel, NS_ERROR_FILE_ACCESS_DENIED);
return; return;
} }
@ -296,36 +332,48 @@ void ExtensionStreamGetter::OnStream(already_AddRefed<nsIInputStream> aStream) {
nsresult rv = NS_NewInputStreamPump(getter_AddRefs(pump), stream.forget(), 0, nsresult rv = NS_NewInputStreamPump(getter_AddRefs(pump), stream.forget(), 0,
0, false, mMainThreadEventTarget); 0, false, mMainThreadEventTarget);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
CancelRequest(listener, mChannel, rv); CancelRequest(listener, channel, rv);
return; return;
} }
rv = pump->AsyncRead(listener); rv = pump->AsyncRead(listener);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
CancelRequest(listener, mChannel, rv); CancelRequest(listener, channel, rv);
return;
} }
mPump = pump;
} }
// Handle an FD sent from the parent. // Handle an FD sent from the parent.
void ExtensionStreamGetter::OnFD(const FileDescriptor& aFD) { void ExtensionStreamGetter::OnFD(const FileDescriptor& aFD) {
MOZ_ASSERT(IsNeckoChild()); MOZ_ASSERT(IsNeckoChild());
MOZ_ASSERT(mListener);
MOZ_ASSERT(mChannel); MOZ_ASSERT(mChannel);
MOZ_ASSERT(mListener);
if (!aFD.IsValid()) { nsCOMPtr<nsIChannel> channel = std::move(mChannel);
OnStream(nullptr);
return;
}
// We must keep an owning reference to the listener // We must keep an owning reference to the listener
// until we pass it on to AsyncOpen. // until we pass it on to AsyncOpen.
nsCOMPtr<nsIStreamListener> listener = std::move(mListener); nsCOMPtr<nsIStreamListener> listener = std::move(mListener);
if (mCanceled) {
// The channel that has created this stream getter has been canceled.
CancelRequest(listener, channel, mStatus);
return;
}
if (!aFD.IsValid()) {
// The parent didn't send us back a valid file descriptor.
CancelRequest(listener, channel, NS_ERROR_FILE_ACCESS_DENIED);
return;
}
RefPtr<FileDescriptorFile> fdFile = new FileDescriptorFile(aFD, mJarFile); RefPtr<FileDescriptorFile> fdFile = new FileDescriptorFile(aFD, mJarFile);
mJarChannel->SetJarFile(fdFile); mJarChannel->SetJarFile(fdFile);
nsresult rv = mJarChannel->AsyncOpen(listener); nsresult rv = mJarChannel->AsyncOpen(listener);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
CancelRequest(listener, mChannel, rv); CancelRequest(listener, channel, rv);
} }
} }
@ -529,7 +577,8 @@ nsresult ExtensionProtocolHandler::SubstituteChannel(nsIURI* aURI,
} else { } else {
MOZ_TRY(convert(listener, channel, origChannel)); MOZ_TRY(convert(listener, channel, origChannel));
} }
return RequestOrReason(origChannel); nsCOMPtr<nsIRequest> request(origChannel);
return RequestOrCancelable(WrapNotNull(request));
}); });
} else if (readyPromise) { } else if (readyPromise) {
size_t matchIdx; size_t matchIdx;
@ -551,7 +600,8 @@ nsresult ExtensionProtocolHandler::SubstituteChannel(nsIURI* aURI,
return aChannel->AsyncOpen(aListener); return aChannel->AsyncOpen(aListener);
}); });
return RequestOrReason(origChannel); nsCOMPtr<nsIRequest> request(origChannel);
return RequestOrCancelable(WrapNotNull(request));
}); });
} else { } else {
return NS_OK; return NS_OK;
@ -864,8 +914,7 @@ void ExtensionProtocolHandler::NewSimpleChannel(
aURI, aLoadinfo, aStreamGetter, aURI, aLoadinfo, aStreamGetter,
[](nsIStreamListener* listener, nsIChannel* simpleChannel, [](nsIStreamListener* listener, nsIChannel* simpleChannel,
ExtensionStreamGetter* getter) -> RequestOrReason { ExtensionStreamGetter* getter) -> RequestOrReason {
MOZ_TRY(getter->GetAsync(listener, simpleChannel)); return getter->GetAsync(listener, simpleChannel);
return RequestOrReason(nullptr);
}); });
SetContentType(aURI, channel); SetContentType(aURI, channel);
@ -888,7 +937,8 @@ void ExtensionProtocolHandler::NewSimpleChannel(nsIURI* aURI,
simpleChannel->Cancel(NS_BINDING_ABORTED); simpleChannel->Cancel(NS_BINDING_ABORTED);
return Err(rv); return Err(rv);
} }
return RequestOrReason(origChannel); nsCOMPtr<nsIRequest> request(origChannel);
return RequestOrCancelable(WrapNotNull(request));
}); });
SetContentType(aURI, channel); SetContentType(aURI, channel);

Просмотреть файл

@ -49,7 +49,10 @@ StaticRefPtr<PageThumbProtocolHandler> PageThumbProtocolHandler::sSingleton;
* Helper class used with SimpleChannel to asynchronously obtain an input * Helper class used with SimpleChannel to asynchronously obtain an input
* stream from the parent for a remote moz-page-thumb load from the child. * stream from the parent for a remote moz-page-thumb load from the child.
*/ */
class PageThumbStreamGetter : public RefCounted<PageThumbStreamGetter> { class PageThumbStreamGetter final : public nsICancelable {
NS_DECL_ISUPPORTS
NS_DECL_NSICANCELABLE
public: public:
PageThumbStreamGetter(nsIURI* aURI, nsILoadInfo* aLoadInfo) PageThumbStreamGetter(nsIURI* aURI, nsILoadInfo* aLoadInfo)
: mURI(aURI), mLoadInfo(aLoadInfo) { : mURI(aURI), mLoadInfo(aLoadInfo) {
@ -59,8 +62,6 @@ class PageThumbStreamGetter : public RefCounted<PageThumbStreamGetter> {
SetupEventTarget(); SetupEventTarget();
} }
~PageThumbStreamGetter() = default;
void SetupEventTarget() { void SetupEventTarget() {
mMainThreadEventTarget = nsContentUtils::GetEventTargetByLoadInfo( mMainThreadEventTarget = nsContentUtils::GetEventTargetByLoadInfo(
mLoadInfo, TaskCategory::Other); mLoadInfo, TaskCategory::Other);
@ -70,8 +71,7 @@ class PageThumbStreamGetter : public RefCounted<PageThumbStreamGetter> {
} }
// Get an input stream from the parent asynchronously. // Get an input stream from the parent asynchronously.
Result<Ok, nsresult> GetAsync(nsIStreamListener* aListener, RequestOrReason GetAsync(nsIStreamListener* aListener, nsIChannel* aChannel);
nsIChannel* aChannel);
// Handle an input stream being returned from the parent // Handle an input stream being returned from the parent
void OnStream(already_AddRefed<nsIInputStream> aStream); void OnStream(already_AddRefed<nsIInputStream> aStream);
@ -79,25 +79,32 @@ class PageThumbStreamGetter : public RefCounted<PageThumbStreamGetter> {
static void CancelRequest(nsIStreamListener* aListener, nsIChannel* aChannel, static void CancelRequest(nsIStreamListener* aListener, nsIChannel* aChannel,
nsresult aResult); nsresult aResult);
MOZ_DECLARE_REFCOUNTED_TYPENAME(PageThumbStreamGetter)
private: private:
~PageThumbStreamGetter() = default;
nsCOMPtr<nsIURI> mURI; nsCOMPtr<nsIURI> mURI;
nsCOMPtr<nsILoadInfo> mLoadInfo; nsCOMPtr<nsILoadInfo> mLoadInfo;
nsCOMPtr<nsIStreamListener> mListener; nsCOMPtr<nsIStreamListener> mListener;
nsCOMPtr<nsIChannel> mChannel; nsCOMPtr<nsIChannel> mChannel;
nsCOMPtr<nsISerialEventTarget> mMainThreadEventTarget; nsCOMPtr<nsISerialEventTarget> mMainThreadEventTarget;
nsCOMPtr<nsIInputStreamPump> mPump;
bool mCanceled{false};
nsresult mStatus{NS_OK};
}; };
NS_IMPL_ISUPPORTS(PageThumbStreamGetter, nsICancelable)
// Request an input stream from the parent. // Request an input stream from the parent.
Result<Ok, nsresult> PageThumbStreamGetter::GetAsync( RequestOrReason PageThumbStreamGetter::GetAsync(nsIStreamListener* aListener,
nsIStreamListener* aListener, nsIChannel* aChannel) { nsIChannel* aChannel) {
MOZ_ASSERT(IsNeckoChild()); MOZ_ASSERT(IsNeckoChild());
MOZ_ASSERT(mMainThreadEventTarget); MOZ_ASSERT(mMainThreadEventTarget);
mListener = aListener; mListener = aListener;
mChannel = aChannel; mChannel = aChannel;
nsCOMPtr<nsICancelable> cancelableRequest(this);
RefPtr<PageThumbStreamGetter> self = this; RefPtr<PageThumbStreamGetter> self = this;
// Request an input stream for this moz-page-thumb URI. // Request an input stream for this moz-page-thumb URI.
@ -109,7 +116,25 @@ Result<Ok, nsresult> PageThumbStreamGetter::GetAsync(
[self](const mozilla::ipc::ResponseRejectReason) { [self](const mozilla::ipc::ResponseRejectReason) {
self->OnStream(nullptr); self->OnStream(nullptr);
}); });
return Ok(); return RequestOrCancelable(WrapNotNull(cancelableRequest));
}
// Called to cancel the ongoing async request.
NS_IMETHODIMP
PageThumbStreamGetter::Cancel(nsresult aStatus) {
if (mCanceled) {
return NS_OK;
}
mCanceled = true;
mStatus = aStatus;
if (mPump) {
mPump->Cancel(aStatus);
mPump = nullptr;
}
return NS_OK;
} }
// static // static
@ -127,20 +152,26 @@ void PageThumbStreamGetter::CancelRequest(nsIStreamListener* aListener,
// Handle an input stream sent from the parent. // Handle an input stream sent from the parent.
void PageThumbStreamGetter::OnStream(already_AddRefed<nsIInputStream> aStream) { void PageThumbStreamGetter::OnStream(already_AddRefed<nsIInputStream> aStream) {
MOZ_ASSERT(IsNeckoChild()); MOZ_ASSERT(IsNeckoChild());
MOZ_ASSERT(mChannel);
MOZ_ASSERT(mListener); MOZ_ASSERT(mListener);
MOZ_ASSERT(mMainThreadEventTarget); MOZ_ASSERT(mMainThreadEventTarget);
nsCOMPtr<nsIInputStream> stream = std::move(aStream); nsCOMPtr<nsIInputStream> stream = std::move(aStream);
nsCOMPtr<nsIChannel> channel = std::move(mChannel);
// We must keep an owning reference to the listener until we pass it on // We must keep an owning reference to the listener until we pass it on
// to AsyncRead. // to AsyncRead.
nsCOMPtr<nsIStreamListener> listener = mListener.forget(); nsCOMPtr<nsIStreamListener> listener = mListener.forget();
MOZ_ASSERT(mChannel); if (mCanceled) {
// The channel that has created this stream getter has been canceled.
CancelRequest(listener, channel, mStatus);
return;
}
if (!stream) { if (!stream) {
// The parent didn't send us back a stream. // The parent didn't send us back a stream.
CancelRequest(listener, mChannel, NS_ERROR_FILE_ACCESS_DENIED); CancelRequest(listener, channel, NS_ERROR_FILE_ACCESS_DENIED);
return; return;
} }
@ -148,14 +179,17 @@ void PageThumbStreamGetter::OnStream(already_AddRefed<nsIInputStream> aStream) {
nsresult rv = NS_NewInputStreamPump(getter_AddRefs(pump), stream.forget(), 0, nsresult rv = NS_NewInputStreamPump(getter_AddRefs(pump), stream.forget(), 0,
0, false, mMainThreadEventTarget); 0, false, mMainThreadEventTarget);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
CancelRequest(listener, mChannel, rv); CancelRequest(listener, channel, rv);
return; return;
} }
rv = pump->AsyncRead(listener); rv = pump->AsyncRead(listener);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
CancelRequest(listener, mChannel, rv); CancelRequest(listener, channel, rv);
return;
} }
mPump = pump;
} }
NS_IMPL_QUERY_INTERFACE(PageThumbProtocolHandler, NS_IMPL_QUERY_INTERFACE(PageThumbProtocolHandler,
@ -454,8 +488,7 @@ void PageThumbProtocolHandler::NewSimpleChannel(
aURI, aLoadinfo, aStreamGetter, aURI, aLoadinfo, aStreamGetter,
[](nsIStreamListener* listener, nsIChannel* simpleChannel, [](nsIStreamListener* listener, nsIChannel* simpleChannel,
PageThumbStreamGetter* getter) -> RequestOrReason { PageThumbStreamGetter* getter) -> RequestOrReason {
MOZ_TRY(getter->GetAsync(listener, simpleChannel)); return getter->GetAsync(listener, simpleChannel);
return RequestOrReason(nullptr);
}); });
SetContentType(aURI, channel); SetContentType(aURI, channel);

Просмотреть файл

@ -15,6 +15,7 @@
#include "nsAnnoProtocolHandler.h" #include "nsAnnoProtocolHandler.h"
#include "nsFaviconService.h" #include "nsFaviconService.h"
#include "nsICancelable.h"
#include "nsIChannel.h" #include "nsIChannel.h"
#include "nsIInputStream.h" #include "nsIInputStream.h"
#include "nsISupportsUtils.h" #include "nsISupportsUtils.h"
@ -72,7 +73,10 @@ namespace {
* just fallback to the default favicon. If anything happens at that point, the * just fallback to the default favicon. If anything happens at that point, the
* world must be against us, so we can do nothing. * world must be against us, so we can do nothing.
*/ */
class faviconAsyncLoader : public AsyncStatementCallback { class faviconAsyncLoader : public AsyncStatementCallback, public nsICancelable {
NS_DECL_NSICANCELABLE
NS_DECL_ISUPPORTS_INHERITED
public: public:
faviconAsyncLoader(nsIChannel* aChannel, nsIStreamListener* aListener, faviconAsyncLoader(nsIChannel* aChannel, nsIStreamListener* aListener,
uint16_t aPreferredSize) uint16_t aPreferredSize)
@ -120,13 +124,35 @@ class faviconAsyncLoader : public AsyncStatementCallback {
return NS_OK; return NS_OK;
} }
static void CancelRequest(nsIStreamListener* aListener, nsIChannel* aChannel,
nsresult aResult) {
MOZ_ASSERT(aListener);
MOZ_ASSERT(aChannel);
aListener->OnStartRequest(aChannel);
aListener->OnStopRequest(aChannel, aResult);
aChannel->Cancel(NS_BINDING_ABORTED);
}
NS_IMETHOD HandleCompletion(uint16_t aReason) override { NS_IMETHOD HandleCompletion(uint16_t aReason) override {
MOZ_DIAGNOSTIC_ASSERT(mListener); MOZ_DIAGNOSTIC_ASSERT(mListener);
MOZ_ASSERT(mChannel);
NS_ENSURE_TRUE(mListener, NS_ERROR_UNEXPECTED); NS_ENSURE_TRUE(mListener, NS_ERROR_UNEXPECTED);
NS_ENSURE_TRUE(mChannel, NS_ERROR_UNEXPECTED);
// Ensure we'll break possible cycles with the listener.
auto cleanup = MakeScopeExit([&]() {
mListener = nullptr;
mChannel = nullptr;
});
if (mCanceled) {
// The channel that has created this faviconAsyncLoader has been canceled.
CancelRequest(mListener, mChannel, mStatus);
return NS_OK;
}
nsresult rv; nsresult rv;
// Ensure we'll break possible cycles with the listener.
auto cleanup = MakeScopeExit([&]() { mListener = nullptr; });
nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo(); nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
nsCOMPtr<nsIEventTarget> target = nsCOMPtr<nsIEventTarget> target =
@ -141,7 +167,14 @@ class faviconAsyncLoader : public AsyncStatementCallback {
target); target);
MOZ_ASSERT(NS_SUCCEEDED(rv)); MOZ_ASSERT(NS_SUCCEEDED(rv));
if (NS_SUCCEEDED(rv)) { if (NS_SUCCEEDED(rv)) {
return pump->AsyncRead(mListener); rv = pump->AsyncRead(mListener);
if (NS_FAILED(rv)) {
CancelRequest(mListener, mChannel, rv);
return rv;
}
mPump = pump;
return NS_OK;
} }
} }
} }
@ -150,14 +183,20 @@ class faviconAsyncLoader : public AsyncStatementCallback {
// we should pass the loadInfo of the original channel along // we should pass the loadInfo of the original channel along
// to the new channel. Note that mChannel can not be null, // to the new channel. Note that mChannel can not be null,
// constructor checks that. // constructor checks that.
nsCOMPtr<nsIChannel> newChannel; rv = GetDefaultIcon(mChannel, getter_AddRefs(mDefaultIconChannel));
rv = GetDefaultIcon(mChannel, getter_AddRefs(newChannel));
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
mListener->OnStartRequest(mChannel); CancelRequest(mListener, mChannel, rv);
mListener->OnStopRequest(mChannel, rv);
return rv; return rv;
} }
return newChannel->AsyncOpen(mListener);
rv = mDefaultIconChannel->AsyncOpen(mListener);
if (NS_FAILED(rv)) {
mDefaultIconChannel = nullptr;
CancelRequest(mListener, mChannel, rv);
return rv;
}
return NS_OK;
} }
protected: protected:
@ -165,11 +204,40 @@ class faviconAsyncLoader : public AsyncStatementCallback {
private: private:
nsCOMPtr<nsIChannel> mChannel; nsCOMPtr<nsIChannel> mChannel;
nsCOMPtr<nsIChannel> mDefaultIconChannel;
nsCOMPtr<nsIStreamListener> mListener; nsCOMPtr<nsIStreamListener> mListener;
nsCOMPtr<nsIInputStreamPump> mPump;
nsCString mData; nsCString mData;
uint16_t mPreferredSize; uint16_t mPreferredSize;
bool mCanceled{false};
nsresult mStatus{NS_OK};
}; };
NS_IMPL_ISUPPORTS_INHERITED(faviconAsyncLoader, AsyncStatementCallback,
nsICancelable)
NS_IMETHODIMP
faviconAsyncLoader::Cancel(nsresult aStatus) {
if (mCanceled) {
return NS_OK;
}
mCanceled = true;
mStatus = aStatus;
if (mPump) {
mPump->Cancel(aStatus);
mPump = nullptr;
}
if (mDefaultIconChannel) {
mDefaultIconChannel->Cancel(aStatus);
mDefaultIconChannel = nullptr;
}
return NS_OK;
}
} // namespace } // namespace
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -266,7 +334,7 @@ nsresult nsAnnoProtocolHandler::NewFaviconChannel(nsIURI* aURI,
nsCOMPtr<nsIChannel> channel = NS_NewSimpleChannel( nsCOMPtr<nsIChannel> channel = NS_NewSimpleChannel(
aURI, aLoadInfo, aAnnotationURI, aURI, aLoadInfo, aAnnotationURI,
[](nsIStreamListener* listener, nsIChannel* channel, [](nsIStreamListener* listener, nsIChannel* channel,
nsIURI* annotationURI) { nsIURI* annotationURI) -> RequestOrReason {
auto fallback = [&]() -> RequestOrReason { auto fallback = [&]() -> RequestOrReason {
nsCOMPtr<nsIChannel> chan; nsCOMPtr<nsIChannel> chan;
nsresult rv = GetDefaultIcon(channel, getter_AddRefs(chan)); nsresult rv = GetDefaultIcon(channel, getter_AddRefs(chan));
@ -275,7 +343,8 @@ nsresult nsAnnoProtocolHandler::NewFaviconChannel(nsIURI* aURI,
rv = chan->AsyncOpen(listener); rv = chan->AsyncOpen(listener);
NS_ENSURE_SUCCESS(rv, Err(rv)); NS_ENSURE_SUCCESS(rv, Err(rv));
return RequestOrReason(std::move(chan)); nsCOMPtr<nsIRequest> request(chan);
return RequestOrCancelable(WrapNotNull(request));
}; };
// Now we go ahead and get our data asynchronously for the favicon. // Now we go ahead and get our data asynchronously for the favicon.
@ -298,7 +367,8 @@ nsresult nsAnnoProtocolHandler::NewFaviconChannel(nsIURI* aURI,
rv = faviconService->GetFaviconDataAsync(faviconSpec, callback); rv = faviconService->GetFaviconDataAsync(faviconSpec, callback);
if (NS_FAILED(rv)) return fallback(); if (NS_FAILED(rv)) return fallback();
return RequestOrReason(nullptr); nsCOMPtr<nsICancelable> cancelable = do_QueryInterface(callback);
return RequestOrCancelable(WrapNotNull(cancelable));
}); });
NS_ENSURE_TRUE(channel, NS_ERROR_OUT_OF_MEMORY); NS_ENSURE_TRUE(channel, NS_ERROR_OUT_OF_MEMORY);