}
nsresult SimpleChannel::BeginAsyncRead(nsIStreamListener* listener,
- nsIRequest** request) {
+ nsIRequest** request,
+ nsICancelable** cancelableRequest) {
NS_ENSURE_TRUE(mCallbacks, NS_ERROR_UNEXPECTED);
- nsCOMPtr<nsIRequest> req;
- MOZ_TRY_VAR(req, mCallbacks->StartAsyncRead(listener, this));
+ RequestOrReason res = mCallbacks->StartAsyncRead(listener, this);
+
+ if (res.isErr()) {
+ return res.propagateErr();
+ }
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;
}
namespace mozilla {
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 {
nsIChannel** channel) override;
virtual nsresult BeginAsyncRead(nsIStreamListener* listener,
- nsIRequest** request) override;
+ nsIRequest** request,
+ nsICancelable** cancelableRequest) override;
private:
UniquePtr<SimpleChannelCallbacks> mCallbacks;
nsresult nsBaseChannel::BeginPumpingData() {
nsresult rv;
- rv = BeginAsyncRead(this, getter_AddRefs(mRequest));
+ rv = BeginAsyncRead(this, getter_AddRefs(mRequest),
+ getter_AddRefs(mCancelableAsyncRequest));
if (NS_SUCCEEDED(rv)) {
+ MOZ_ASSERT(mRequest || mCancelableAsyncRequest,
+ "should have got a request or cancelable");
mPumpingData = true;
return NS_OK;
}
mCanceled = true;
mStatus = status;
+ if (mCancelableAsyncRequest) {
+ mCancelableAsyncRequest->Cancel(status);
+ }
+
if (mRequest) {
mRequest->Cancel(status);
}
NS_IMETHODIMP
nsBaseChannel::OnStartRequest(nsIRequest* request) {
MOZ_ASSERT_IF(mRequest, request == mRequest);
+ MOZ_ASSERT_IF(mCancelableAsyncRequest, !mRequest);
nsAutoCString scheme;
mURI->GetScheme(scheme);
// Cause Pending to return false.
mPump = nullptr;
mRequest = nullptr;
+ mCancelableAsyncRequest = nullptr;
mPumpingData = false;
if (mListener) { // null in case of redirect
#include "nsThreadUtils.h"
class nsIInputStream;
+class nsICancelable;
//-----------------------------------------------------------------------------
// nsBaseChannel is designed to be subclassed. The subclass is responsible for
//
// On success, the callee must begin pumping data to the stream listener,
// 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,
- nsIRequest** request) {
+ nsIRequest** request,
+ nsICancelable** cancelableRequest) {
return NS_ERROR_NOT_IMPLEMENTED;
}
RefPtr<nsInputStreamPump> mPump;
RefPtr<nsIRequest> mRequest;
+ nsCOMPtr<nsICancelable> mCancelableAsyncRequest;
bool mPumpingData{false};
nsCOMPtr<nsIProgressEventSink> mProgressSink;
nsCOMPtr<nsIURI> mOriginalURI;
#include "nsContentUtils.h"
#include "nsServiceManagerUtils.h"
#include "nsDirectoryServiceDefs.h"
+#include "nsICancelable.h"
#include "nsIFile.h"
#include "nsIFileChannel.h"
#include "nsIFileStreams.h"
* stream or file descriptor from the parent for a remote moz-extension load
* from the child.
*/
-class ExtensionStreamGetter : public RefCounted<ExtensionStreamGetter> {
+class ExtensionStreamGetter final : public nsICancelable {
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICANCELABLE
+
public:
// To use when getting a remote input stream for a resource
// in an unpacked extension.
SetupEventTarget();
}
- ~ExtensionStreamGetter() = default;
-
void SetupEventTarget() {
mMainThreadEventTarget = nsContentUtils::GetEventTargetByLoadInfo(
mLoadInfo, TaskCategory::Other);
}
// Get an input stream or file descriptor from the parent asynchronously.
- Result<Ok, nsresult> GetAsync(nsIStreamListener* aListener,
- nsIChannel* aChannel);
+ RequestOrReason GetAsync(nsIStreamListener* aListener, nsIChannel* aChannel);
// Handle an input stream being returned from the parent
void OnStream(already_AddRefed<nsIInputStream> aStream);
static void CancelRequest(nsIStreamListener* aListener, nsIChannel* aChannel,
nsresult aResult);
- MOZ_DECLARE_REFCOUNTED_TYPENAME(ExtensionStreamGetter)
-
private:
+ ~ExtensionStreamGetter() = default;
+
nsCOMPtr<nsIURI> mURI;
nsCOMPtr<nsILoadInfo> mLoadInfo;
nsCOMPtr<nsIJARChannel> mJarChannel;
+ nsCOMPtr<nsIInputStreamPump> mPump;
nsCOMPtr<nsIFile> mJarFile;
nsCOMPtr<nsIStreamListener> mListener;
nsCOMPtr<nsIChannel> mChannel;
nsCOMPtr<nsISerialEventTarget> mMainThreadEventTarget;
bool mIsJarChannel;
+ bool mCanceled{false};
+ nsresult mStatus{NS_OK};
};
+NS_IMPL_ISUPPORTS(ExtensionStreamGetter, nsICancelable)
+
class ExtensionJARFileOpener final : public nsISupports {
public:
ExtensionJARFileOpener(nsIFile* aFile,
#define DEFAULT_THREAD_TIMEOUT_MS 30000
// Request an FD or input stream from the parent.
-Result<Ok, nsresult> ExtensionStreamGetter::GetAsync(
- nsIStreamListener* aListener, nsIChannel* aChannel) {
+RequestOrReason ExtensionStreamGetter::GetAsync(nsIStreamListener* aListener,
+ nsIChannel* aChannel) {
MOZ_ASSERT(IsNeckoChild());
MOZ_ASSERT(mMainThreadEventTarget);
mListener = aListener;
mChannel = aChannel;
+ nsCOMPtr<nsICancelable> cancelableRequest(this);
+
RefPtr<ExtensionStreamGetter> self = this;
if (mIsJarChannel) {
// Request an FD for this moz-extension URI
[self](const mozilla::ipc::ResponseRejectReason) {
self->OnFD(FileDescriptor());
});
- return Ok();
+ return RequestOrCancelable(WrapNotNull(cancelableRequest));
}
// Request an input stream for this moz-extension URI
[self](const mozilla::ipc::ResponseRejectReason) {
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
// Handle an input stream sent from the parent.
void ExtensionStreamGetter::OnStream(already_AddRefed<nsIInputStream> aStream) {
MOZ_ASSERT(IsNeckoChild());
+ MOZ_ASSERT(mChannel);
MOZ_ASSERT(mListener);
MOZ_ASSERT(mMainThreadEventTarget);
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 to AsyncRead.
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) {
// 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;
}
nsresult rv = NS_NewInputStreamPump(getter_AddRefs(pump), stream.forget(), 0,
0, false, mMainThreadEventTarget);
if (NS_FAILED(rv)) {
- CancelRequest(listener, mChannel, rv);
+ CancelRequest(listener, channel, rv);
return;
}
rv = pump->AsyncRead(listener);
if (NS_FAILED(rv)) {
- CancelRequest(listener, mChannel, rv);
+ CancelRequest(listener, channel, rv);
+ return;
}
+
+ mPump = pump;
}
// Handle an FD sent from the parent.
void ExtensionStreamGetter::OnFD(const FileDescriptor& aFD) {
MOZ_ASSERT(IsNeckoChild());
- MOZ_ASSERT(mListener);
MOZ_ASSERT(mChannel);
+ MOZ_ASSERT(mListener);
- if (!aFD.IsValid()) {
- OnStream(nullptr);
- return;
- }
+ nsCOMPtr<nsIChannel> channel = std::move(mChannel);
// We must keep an owning reference to the listener
// until we pass it on to AsyncOpen.
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);
mJarChannel->SetJarFile(fdFile);
nsresult rv = mJarChannel->AsyncOpen(listener);
if (NS_FAILED(rv)) {
- CancelRequest(listener, mChannel, rv);
+ CancelRequest(listener, channel, rv);
}
}
} else {
MOZ_TRY(convert(listener, channel, origChannel));
}
- return RequestOrReason(origChannel);
+ nsCOMPtr<nsIRequest> request(origChannel);
+ return RequestOrCancelable(WrapNotNull(request));
});
} else if (readyPromise) {
size_t matchIdx;
return aChannel->AsyncOpen(aListener);
});
- return RequestOrReason(origChannel);
+ nsCOMPtr<nsIRequest> request(origChannel);
+ return RequestOrCancelable(WrapNotNull(request));
});
} else {
return NS_OK;
aURI, aLoadinfo, aStreamGetter,
[](nsIStreamListener* listener, nsIChannel* simpleChannel,
ExtensionStreamGetter* getter) -> RequestOrReason {
- MOZ_TRY(getter->GetAsync(listener, simpleChannel));
- return RequestOrReason(nullptr);
+ return getter->GetAsync(listener, simpleChannel);
});
SetContentType(aURI, channel);
simpleChannel->Cancel(NS_BINDING_ABORTED);
return Err(rv);
}
- return RequestOrReason(origChannel);
+ nsCOMPtr<nsIRequest> request(origChannel);
+ return RequestOrCancelable(WrapNotNull(request));
});
SetContentType(aURI, channel);
* Helper class used with SimpleChannel to asynchronously obtain an input
* 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:
PageThumbStreamGetter(nsIURI* aURI, nsILoadInfo* aLoadInfo)
: mURI(aURI), mLoadInfo(aLoadInfo) {
SetupEventTarget();
}
- ~PageThumbStreamGetter() = default;
-
void SetupEventTarget() {
mMainThreadEventTarget = nsContentUtils::GetEventTargetByLoadInfo(
mLoadInfo, TaskCategory::Other);
}
// Get an input stream from the parent asynchronously.
- Result<Ok, nsresult> GetAsync(nsIStreamListener* aListener,
- nsIChannel* aChannel);
+ RequestOrReason GetAsync(nsIStreamListener* aListener, nsIChannel* aChannel);
// Handle an input stream being returned from the parent
void OnStream(already_AddRefed<nsIInputStream> aStream);
static void CancelRequest(nsIStreamListener* aListener, nsIChannel* aChannel,
nsresult aResult);
- MOZ_DECLARE_REFCOUNTED_TYPENAME(PageThumbStreamGetter)
-
private:
+ ~PageThumbStreamGetter() = default;
+
nsCOMPtr<nsIURI> mURI;
nsCOMPtr<nsILoadInfo> mLoadInfo;
nsCOMPtr<nsIStreamListener> mListener;
nsCOMPtr<nsIChannel> mChannel;
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.
-Result<Ok, nsresult> PageThumbStreamGetter::GetAsync(
- nsIStreamListener* aListener, nsIChannel* aChannel) {
+RequestOrReason PageThumbStreamGetter::GetAsync(nsIStreamListener* aListener,
+ nsIChannel* aChannel) {
MOZ_ASSERT(IsNeckoChild());
MOZ_ASSERT(mMainThreadEventTarget);
mListener = aListener;
mChannel = aChannel;
+ nsCOMPtr<nsICancelable> cancelableRequest(this);
+
RefPtr<PageThumbStreamGetter> self = this;
// Request an input stream for this moz-page-thumb URI.
[self](const mozilla::ipc::ResponseRejectReason) {
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
// Handle an input stream sent from the parent.
void PageThumbStreamGetter::OnStream(already_AddRefed<nsIInputStream> aStream) {
MOZ_ASSERT(IsNeckoChild());
+ MOZ_ASSERT(mChannel);
MOZ_ASSERT(mListener);
MOZ_ASSERT(mMainThreadEventTarget);
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
// to AsyncRead.
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) {
// 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;
}
nsresult rv = NS_NewInputStreamPump(getter_AddRefs(pump), stream.forget(), 0,
0, false, mMainThreadEventTarget);
if (NS_FAILED(rv)) {
- CancelRequest(listener, mChannel, rv);
+ CancelRequest(listener, channel, rv);
return;
}
rv = pump->AsyncRead(listener);
if (NS_FAILED(rv)) {
- CancelRequest(listener, mChannel, rv);
+ CancelRequest(listener, channel, rv);
+ return;
}
+
+ mPump = pump;
}
NS_IMPL_QUERY_INTERFACE(PageThumbProtocolHandler,
aURI, aLoadinfo, aStreamGetter,
[](nsIStreamListener* listener, nsIChannel* simpleChannel,
PageThumbStreamGetter* getter) -> RequestOrReason {
- MOZ_TRY(getter->GetAsync(listener, simpleChannel));
- return RequestOrReason(nullptr);
+ return getter->GetAsync(listener, simpleChannel);
});
SetContentType(aURI, channel);
#include "nsAnnoProtocolHandler.h"
#include "nsFaviconService.h"
+#include "nsICancelable.h"
#include "nsIChannel.h"
#include "nsIInputStream.h"
#include "nsISupportsUtils.h"
* just fallback to the default favicon. If anything happens at that point, the
* 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:
faviconAsyncLoader(nsIChannel* aChannel, nsIStreamListener* aListener,
uint16_t aPreferredSize)
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 {
MOZ_DIAGNOSTIC_ASSERT(mListener);
+ MOZ_ASSERT(mChannel);
NS_ENSURE_TRUE(mListener, NS_ERROR_UNEXPECTED);
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_UNEXPECTED);
- nsresult rv;
// Ensure we'll break possible cycles with the listener.
- auto cleanup = MakeScopeExit([&]() { mListener = nullptr; });
+ 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;
nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
nsCOMPtr<nsIEventTarget> target =
target);
MOZ_ASSERT(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;
}
}
}
// we should pass the loadInfo of the original channel along
// to the new channel. Note that mChannel can not be null,
// constructor checks that.
- nsCOMPtr<nsIChannel> newChannel;
- rv = GetDefaultIcon(mChannel, getter_AddRefs(newChannel));
+ rv = GetDefaultIcon(mChannel, getter_AddRefs(mDefaultIconChannel));
if (NS_FAILED(rv)) {
- mListener->OnStartRequest(mChannel);
- mListener->OnStopRequest(mChannel, rv);
+ CancelRequest(mListener, mChannel, 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:
private:
nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsIChannel> mDefaultIconChannel;
nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsIInputStreamPump> mPump;
nsCString mData;
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
////////////////////////////////////////////////////////////////////////////////
nsCOMPtr<nsIChannel> channel = NS_NewSimpleChannel(
aURI, aLoadInfo, aAnnotationURI,
[](nsIStreamListener* listener, nsIChannel* channel,
- nsIURI* annotationURI) {
+ nsIURI* annotationURI) -> RequestOrReason {
auto fallback = [&]() -> RequestOrReason {
nsCOMPtr<nsIChannel> chan;
nsresult rv = GetDefaultIcon(channel, getter_AddRefs(chan));
rv = chan->AsyncOpen(listener);
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.
rv = faviconService->GetFaviconDataAsync(faviconSpec, callback);
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);