/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "IPCBlobInputStream.h" #include "IPCBlobInputStreamChild.h" #include "IPCBlobInputStreamStorage.h" #include "mozilla/ipc/InputStreamParams.h" #include "mozilla/SlicedInputStream.h" #include "mozilla/NonBlockingAsyncInputStream.h" #include "IPCBlobInputStreamThread.h" #include "nsIAsyncInputStream.h" #include "nsIAsyncOutputStream.h" #include "nsIPipe.h" #include "nsStreamUtils.h" #include "nsStringStream.h" namespace mozilla { namespace dom { namespace { class InputStreamCallbackRunnable final : public CancelableRunnable { public: // Note that the execution can be synchronous in case the event target is // null. static void Execute(nsIInputStreamCallback* aCallback, nsIEventTarget* aEventTarget, IPCBlobInputStream* aStream) { MOZ_ASSERT(aCallback); RefPtr runnable = new InputStreamCallbackRunnable(aCallback, aStream); nsCOMPtr target = aEventTarget; if (aEventTarget) { target->Dispatch(runnable, NS_DISPATCH_NORMAL); } else { runnable->Run(); } } NS_IMETHOD Run() override { mCallback->OnInputStreamReady(mStream); mCallback = nullptr; mStream = nullptr; return NS_OK; } private: InputStreamCallbackRunnable(nsIInputStreamCallback* aCallback, IPCBlobInputStream* aStream) : CancelableRunnable("dom::InputStreamCallbackRunnable"), mCallback(aCallback), mStream(aStream) { MOZ_ASSERT(mCallback); MOZ_ASSERT(mStream); } nsCOMPtr mCallback; RefPtr mStream; }; class FileMetadataCallbackRunnable final : public CancelableRunnable { public: static void Execute(nsIFileMetadataCallback* aCallback, nsIEventTarget* aEventTarget, IPCBlobInputStream* aStream) { MOZ_ASSERT(aCallback); MOZ_ASSERT(aEventTarget); RefPtr runnable = new FileMetadataCallbackRunnable(aCallback, aStream); nsCOMPtr target = aEventTarget; target->Dispatch(runnable, NS_DISPATCH_NORMAL); } NS_IMETHOD Run() override { mCallback->OnFileMetadataReady(mStream); mCallback = nullptr; mStream = nullptr; return NS_OK; } private: FileMetadataCallbackRunnable(nsIFileMetadataCallback* aCallback, IPCBlobInputStream* aStream) : CancelableRunnable("dom::FileMetadataCallbackRunnable"), mCallback(aCallback), mStream(aStream) { MOZ_ASSERT(mCallback); MOZ_ASSERT(mStream); } nsCOMPtr mCallback; RefPtr mStream; }; } // namespace NS_IMPL_ADDREF(IPCBlobInputStream); NS_IMPL_RELEASE(IPCBlobInputStream); NS_INTERFACE_MAP_BEGIN(IPCBlobInputStream) NS_INTERFACE_MAP_ENTRY(nsIInputStream) NS_INTERFACE_MAP_ENTRY(nsIAsyncInputStream) NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback) NS_INTERFACE_MAP_ENTRY(nsICloneableInputStream) NS_INTERFACE_MAP_ENTRY(nsICloneableInputStreamWithRange) NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableInputStream) NS_INTERFACE_MAP_ENTRY(nsIFileMetadata) NS_INTERFACE_MAP_ENTRY(nsIAsyncFileMetadata) NS_INTERFACE_MAP_ENTRY(nsIInputStreamLength) NS_INTERFACE_MAP_ENTRY(nsIAsyncInputStreamLength) NS_INTERFACE_MAP_ENTRY(nsIIPCBlobInputStream) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream) NS_INTERFACE_MAP_END IPCBlobInputStream::IPCBlobInputStream(IPCBlobInputStreamChild* aActor) : mActor(aActor), mState(eInit), mStart(0), mLength(0), mConsumed(false), mMutex("IPCBlobInputStream::mMutex") { MOZ_ASSERT(aActor); mLength = aActor->Size(); if (XRE_IsParentProcess()) { nsCOMPtr stream; IPCBlobInputStreamStorage::Get()->GetStream(mActor->ID(), 0, mLength, getter_AddRefs(stream)); if (stream) { mState = eRunning; mRemoteStream = stream; } } } IPCBlobInputStream::~IPCBlobInputStream() { Close(); } // nsIInputStream interface NS_IMETHODIMP IPCBlobInputStream::Available(uint64_t* aLength) { nsCOMPtr asyncRemoteStream; { MutexAutoLock lock(mMutex); // We don't have a remoteStream yet: let's return 0. if (mState == eInit || mState == ePending) { *aLength = 0; return NS_OK; } if (mState == eClosed) { return NS_BASE_STREAM_CLOSED; } MOZ_ASSERT(mState == eRunning); MOZ_ASSERT(mRemoteStream || mAsyncRemoteStream); nsresult rv = EnsureAsyncRemoteStream(lock); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } asyncRemoteStream = mAsyncRemoteStream; } MOZ_ASSERT(asyncRemoteStream); return asyncRemoteStream->Available(aLength); } NS_IMETHODIMP IPCBlobInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) { nsCOMPtr asyncRemoteStream; { MutexAutoLock lock(mMutex); // Read is not available is we don't have a remoteStream. if (mState == eInit || mState == ePending) { return NS_BASE_STREAM_WOULD_BLOCK; } if (mState == eClosed) { return NS_BASE_STREAM_CLOSED; } MOZ_ASSERT(mState == eRunning); MOZ_ASSERT(mRemoteStream || mAsyncRemoteStream); nsresult rv = EnsureAsyncRemoteStream(lock); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } asyncRemoteStream = mAsyncRemoteStream; } MOZ_ASSERT(asyncRemoteStream); nsresult rv = asyncRemoteStream->Read(aBuffer, aCount, aReadCount); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } { MutexAutoLock lock(mMutex); mConsumed = true; } return NS_OK; } NS_IMETHODIMP IPCBlobInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount, uint32_t* aResult) { nsCOMPtr asyncRemoteStream; { MutexAutoLock lock(mMutex); // ReadSegments is not available is we don't have a remoteStream. if (mState == eInit || mState == ePending) { return NS_BASE_STREAM_WOULD_BLOCK; } if (mState == eClosed) { return NS_BASE_STREAM_CLOSED; } MOZ_ASSERT(mState == eRunning); MOZ_ASSERT(mRemoteStream || mAsyncRemoteStream); nsresult rv = EnsureAsyncRemoteStream(lock); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } asyncRemoteStream = mAsyncRemoteStream; } MOZ_ASSERT(asyncRemoteStream); nsresult rv = asyncRemoteStream->ReadSegments(aWriter, aClosure, aCount, aResult); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // If some data has been read, we mark the stream as consumed. if (*aResult != 0) { MutexAutoLock lock(mMutex); mConsumed = true; } return NS_OK; } NS_IMETHODIMP IPCBlobInputStream::IsNonBlocking(bool* aNonBlocking) { *aNonBlocking = true; return NS_OK; } NS_IMETHODIMP IPCBlobInputStream::Close() { nsCOMPtr asyncRemoteStream; nsCOMPtr remoteStream; { MutexAutoLock lock(mMutex); if (mActor) { mActor->ForgetStream(this); mActor = nullptr; } asyncRemoteStream.swap(mAsyncRemoteStream); remoteStream.swap(mRemoteStream); mInputStreamCallback = nullptr; mInputStreamCallbackEventTarget = nullptr; mFileMetadataCallback = nullptr; mFileMetadataCallbackEventTarget = nullptr; mState = eClosed; } if (asyncRemoteStream) { asyncRemoteStream->CloseWithStatus(NS_BASE_STREAM_CLOSED); } if (remoteStream) { remoteStream->Close(); } return NS_OK; } // nsICloneableInputStream interface NS_IMETHODIMP IPCBlobInputStream::GetCloneable(bool* aCloneable) { MutexAutoLock lock(mMutex); *aCloneable = mState != eClosed; return NS_OK; } NS_IMETHODIMP IPCBlobInputStream::Clone(nsIInputStream** aResult) { MutexAutoLock lock(mMutex); if (mState == eClosed) { return NS_BASE_STREAM_CLOSED; } MOZ_ASSERT(mActor); RefPtr stream = mActor->CreateStream(); if (!stream) { return NS_ERROR_FAILURE; } stream->InitWithExistingRange(mStart, mLength, lock); stream.forget(aResult); return NS_OK; } // nsICloneableInputStreamWithRange interface NS_IMETHODIMP IPCBlobInputStream::CloneWithRange(uint64_t aStart, uint64_t aLength, nsIInputStream** aResult) { MutexAutoLock lock(mMutex); if (mState == eClosed) { return NS_BASE_STREAM_CLOSED; } // Too short or out of range. if (aLength == 0 || aStart >= mLength) { return NS_NewCStringInputStream(aResult, EmptyCString()); } MOZ_ASSERT(mActor); RefPtr stream = mActor->CreateStream(); if (!stream) { return NS_ERROR_FAILURE; } CheckedInt streamSize = mLength; streamSize -= aStart; if (!streamSize.isValid()) { return NS_ERROR_FAILURE; } if (aLength > streamSize.value()) { aLength = streamSize.value(); } stream->InitWithExistingRange(aStart + mStart, aLength, lock); stream.forget(aResult); return NS_OK; } // nsIAsyncInputStream interface NS_IMETHODIMP IPCBlobInputStream::CloseWithStatus(nsresult aStatus) { return Close(); } NS_IMETHODIMP IPCBlobInputStream::AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags, uint32_t aRequestedCount, nsIEventTarget* aEventTarget) { nsCOMPtr asyncRemoteStream; { MutexAutoLock lock(mMutex); // See IPCBlobInputStream.h for more information about this state machine. switch (mState) { // First call, we need to retrieve the stream from the parent actor. case eInit: MOZ_ASSERT(mActor); mInputStreamCallback = aCallback; mInputStreamCallbackEventTarget = aEventTarget; mState = ePending; mActor->StreamNeeded(this, aEventTarget); return NS_OK; // We are still waiting for the remote inputStream case ePending: { if (mInputStreamCallback && aCallback) { return NS_ERROR_FAILURE; } mInputStreamCallback = aCallback; mInputStreamCallbackEventTarget = aEventTarget; return NS_OK; } // We have the remote inputStream, let's check if we can execute the // callback. case eRunning: { if (mInputStreamCallback && aCallback) { return NS_ERROR_FAILURE; } nsresult rv = EnsureAsyncRemoteStream(lock); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } mInputStreamCallback = aCallback; mInputStreamCallbackEventTarget = aEventTarget; asyncRemoteStream = mAsyncRemoteStream; break; } // Stream is closed. default: MOZ_ASSERT(mState == eClosed); return NS_BASE_STREAM_CLOSED; } } MOZ_ASSERT(asyncRemoteStream); return asyncRemoteStream->AsyncWait(aCallback ? this : nullptr, 0, 0, aEventTarget); } void IPCBlobInputStream::StreamReady( already_AddRefed aInputStream) { nsCOMPtr inputStream = std::move(aInputStream); // If inputStream is null, it means that the serialization went wrong or the // stream is not available anymore. We keep the state as pending just to block // any additional operation. if (!inputStream) { return; } nsCOMPtr fileMetadataCallback; nsCOMPtr fileMetadataCallbackEventTarget; nsCOMPtr inputStreamCallback; nsCOMPtr inputStreamCallbackEventTarget; nsCOMPtr asyncRemoteStream; { MutexAutoLock lock(mMutex); // We have been closed in the meantime. if (mState == eClosed) { if (inputStream) { MutexAutoUnlock unlock(mMutex); inputStream->Close(); } return; } // Now it's the right time to apply a slice if needed. if (mStart > 0 || mLength < mActor->Size()) { inputStream = new SlicedInputStream(inputStream.forget(), mStart, mLength); } mRemoteStream = inputStream; MOZ_ASSERT(mState == ePending); mState = eRunning; fileMetadataCallback.swap(mFileMetadataCallback); fileMetadataCallbackEventTarget.swap(mFileMetadataCallbackEventTarget); inputStreamCallback = mInputStreamCallback ? this : nullptr; inputStreamCallbackEventTarget = mInputStreamCallbackEventTarget; if (inputStreamCallback) { nsresult rv = EnsureAsyncRemoteStream(lock); if (NS_WARN_IF(NS_FAILED(rv))) { return; } MOZ_ASSERT(mAsyncRemoteStream); asyncRemoteStream = mAsyncRemoteStream; } } if (fileMetadataCallback) { FileMetadataCallbackRunnable::Execute( fileMetadataCallback, fileMetadataCallbackEventTarget, this); } if (inputStreamCallback) { MOZ_ASSERT(asyncRemoteStream); nsresult rv = asyncRemoteStream->AsyncWait(inputStreamCallback, 0, 0, inputStreamCallbackEventTarget); Unused << NS_WARN_IF(NS_FAILED(rv)); } } void IPCBlobInputStream::InitWithExistingRange( uint64_t aStart, uint64_t aLength, const MutexAutoLock& aProofOfLock) { MOZ_ASSERT(mActor->Size() >= aStart + aLength); mStart = aStart; mLength = aLength; // In the child, we slice in StreamReady() when we set mState to eRunning. // But in the parent, we start out eRunning, so it's necessary to slice the // stream as soon as we have the information during the initialization phase // because the stream is immediately consumable. if (mState == eRunning && mRemoteStream && XRE_IsParentProcess() && (mStart > 0 || mLength < mActor->Size())) { mRemoteStream = new SlicedInputStream(mRemoteStream.forget(), mStart, mLength); } } // nsIInputStreamCallback NS_IMETHODIMP IPCBlobInputStream::OnInputStreamReady(nsIAsyncInputStream* aStream) { nsCOMPtr callback; nsCOMPtr callbackEventTarget; { MutexAutoLock lock(mMutex); // We have been closed in the meantime. if (mState == eClosed) { return NS_OK; } MOZ_ASSERT(mState == eRunning); MOZ_ASSERT(mAsyncRemoteStream == aStream); // The callback has been canceled in the meantime. if (!mInputStreamCallback) { return NS_OK; } callback.swap(mInputStreamCallback); callbackEventTarget.swap(mInputStreamCallbackEventTarget); } // This must be the last operation because the execution of the callback can // be synchronous. MOZ_ASSERT(callback); InputStreamCallbackRunnable::Execute(callback, callbackEventTarget, this); return NS_OK; } // nsIIPCSerializableInputStream void IPCBlobInputStream::Serialize(mozilla::ipc::InputStreamParams& aParams, FileDescriptorArray& aFileDescriptors, bool aDelayedStart, uint32_t aMaxSize, uint32_t* aSizeUsed, nsIContentChild* aManager) { MOZ_ASSERT(aSizeUsed); *aSizeUsed = 0; SerializeInternal(aParams); } void IPCBlobInputStream::Serialize(mozilla::ipc::InputStreamParams& aParams, FileDescriptorArray& aFileDescriptors, bool aDelayedStart, uint32_t aMaxSize, uint32_t* aSizeUsed, mozilla::ipc::PBackgroundChild* aManager) { MOZ_ASSERT(aSizeUsed); *aSizeUsed = 0; SerializeInternal(aParams); } void IPCBlobInputStream::Serialize(mozilla::ipc::InputStreamParams& aParams, FileDescriptorArray& aFileDescriptors, bool aDelayedStart, uint32_t aMaxSize, uint32_t* aSizeUsed, nsIContentParent* aManager) { MOZ_ASSERT(aSizeUsed); *aSizeUsed = 0; SerializeInternal(aParams); } void IPCBlobInputStream::Serialize(mozilla::ipc::InputStreamParams& aParams, FileDescriptorArray& aFileDescriptors, bool aDelayedStart, uint32_t aMaxSize, uint32_t* aSizeUsed, mozilla::ipc::PBackgroundParent* aManager) { MOZ_ASSERT(aSizeUsed); *aSizeUsed = 0; SerializeInternal(aParams); } void IPCBlobInputStream::SerializeInternal( mozilla::ipc::InputStreamParams& aParams) { MutexAutoLock lock(mMutex); mozilla::ipc::IPCBlobInputStreamParams params; params.id() = mActor->ID(); params.start() = mStart; params.length() = mLength; aParams = params; } bool IPCBlobInputStream::Deserialize( const mozilla::ipc::InputStreamParams& aParams, const FileDescriptorArray& aFileDescriptors) { MOZ_CRASH("This should never be called."); return false; } // nsIAsyncFileMetadata NS_IMETHODIMP IPCBlobInputStream::AsyncFileMetadataWait(nsIFileMetadataCallback* aCallback, nsIEventTarget* aEventTarget) { MOZ_ASSERT(!!aCallback == !!aEventTarget); // If we have the callback, we must have the event target. if (NS_WARN_IF(!!aCallback != !!aEventTarget)) { return NS_ERROR_FAILURE; } // See IPCBlobInputStream.h for more information about this state machine. { MutexAutoLock lock(mMutex); switch (mState) { // First call, we need to retrieve the stream from the parent actor. case eInit: MOZ_ASSERT(mActor); mFileMetadataCallback = aCallback; mFileMetadataCallbackEventTarget = aEventTarget; mState = ePending; mActor->StreamNeeded(this, aEventTarget); return NS_OK; // We are still waiting for the remote inputStream case ePending: if (mFileMetadataCallback && aCallback) { return NS_ERROR_FAILURE; } mFileMetadataCallback = aCallback; mFileMetadataCallbackEventTarget = aEventTarget; return NS_OK; // We have the remote inputStream, let's check if we can execute the // callback. case eRunning: break; // Stream is closed. default: MOZ_ASSERT(mState == eClosed); return NS_BASE_STREAM_CLOSED; } MOZ_ASSERT(mState == eRunning); } FileMetadataCallbackRunnable::Execute(aCallback, aEventTarget, this); return NS_OK; } // nsIFileMetadata NS_IMETHODIMP IPCBlobInputStream::GetSize(int64_t* aRetval) { nsCOMPtr fileMetadata; { MutexAutoLock lock(mMutex); fileMetadata = do_QueryInterface(mRemoteStream); if (!fileMetadata) { return mState == eClosed ? NS_BASE_STREAM_CLOSED : NS_ERROR_FAILURE; } } return fileMetadata->GetSize(aRetval); } NS_IMETHODIMP IPCBlobInputStream::GetLastModified(int64_t* aRetval) { nsCOMPtr fileMetadata; { MutexAutoLock lock(mMutex); fileMetadata = do_QueryInterface(mRemoteStream); if (!fileMetadata) { return mState == eClosed ? NS_BASE_STREAM_CLOSED : NS_ERROR_FAILURE; } } return fileMetadata->GetLastModified(aRetval); } NS_IMETHODIMP IPCBlobInputStream::GetFileDescriptor(PRFileDesc** aRetval) { nsCOMPtr fileMetadata; { MutexAutoLock lock(mMutex); fileMetadata = do_QueryInterface(mRemoteStream); if (!fileMetadata) { return mState == eClosed ? NS_BASE_STREAM_CLOSED : NS_ERROR_FAILURE; } } return fileMetadata->GetFileDescriptor(aRetval); } nsresult IPCBlobInputStream::EnsureAsyncRemoteStream( const MutexAutoLock& aProofOfLock) { // We already have an async remote stream. if (mAsyncRemoteStream) { return NS_OK; } if (!mRemoteStream) { return NS_ERROR_FAILURE; } // If the stream is blocking, we want to make it unblocking using a pipe. bool nonBlocking = false; nsresult rv = mRemoteStream->IsNonBlocking(&nonBlocking); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr asyncStream = do_QueryInterface(mRemoteStream); // If non-blocking and non-async, let's use NonBlockingAsyncInputStream. if (nonBlocking && !asyncStream) { rv = NonBlockingAsyncInputStream::Create(mRemoteStream.forget(), getter_AddRefs(asyncStream)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_ASSERT(asyncStream); } if (!asyncStream) { // Let's make the stream async using the DOMFile thread. nsCOMPtr pipeIn; nsCOMPtr pipeOut; rv = NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut), true, true); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } RefPtr thread = IPCBlobInputStreamThread::GetOrCreate(); if (NS_WARN_IF(!thread)) { return NS_ERROR_FAILURE; } rv = NS_AsyncCopy(mRemoteStream, pipeOut, thread, NS_ASYNCCOPY_VIA_WRITESEGMENTS); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } asyncStream = pipeIn; } MOZ_ASSERT(asyncStream); mAsyncRemoteStream = asyncStream; mRemoteStream = nullptr; return NS_OK; } // nsIInputStreamLength NS_IMETHODIMP IPCBlobInputStream::Length(int64_t* aLength) { MutexAutoLock lock(mMutex); if (mState == eClosed) { return NS_BASE_STREAM_CLOSED; } if (mConsumed) { return NS_ERROR_NOT_AVAILABLE; } return NS_BASE_STREAM_WOULD_BLOCK; } // nsIAsyncInputStreamLength NS_IMETHODIMP IPCBlobInputStream::AsyncLengthWait(nsIInputStreamLengthCallback* aCallback, nsIEventTarget* aEventTarget) { MutexAutoLock lock(mMutex); if (mState == eClosed) { return NS_BASE_STREAM_CLOSED; } if (mConsumed) { return NS_ERROR_NOT_AVAILABLE; } // If we have the callback, we must have the event target. if (NS_WARN_IF(!!aCallback != !!aEventTarget)) { return NS_ERROR_FAILURE; } MOZ_ASSERT(mActor); mLengthCallback = aCallback; mLengthCallbackEventTarget = aEventTarget; if (aCallback) { mActor->LengthNeeded(this, aEventTarget); } return NS_OK; } namespace { class InputStreamLengthCallbackRunnable final : public CancelableRunnable { public: static void Execute(nsIInputStreamLengthCallback* aCallback, nsIEventTarget* aEventTarget, IPCBlobInputStream* aStream, int64_t aLength) { MOZ_ASSERT(aCallback); MOZ_ASSERT(aEventTarget); RefPtr runnable = new InputStreamLengthCallbackRunnable(aCallback, aStream, aLength); nsCOMPtr target = aEventTarget; target->Dispatch(runnable, NS_DISPATCH_NORMAL); } NS_IMETHOD Run() override { mCallback->OnInputStreamLengthReady(mStream, mLength); mCallback = nullptr; mStream = nullptr; return NS_OK; } private: InputStreamLengthCallbackRunnable(nsIInputStreamLengthCallback* aCallback, IPCBlobInputStream* aStream, int64_t aLength) : CancelableRunnable("dom::InputStreamLengthCallbackRunnable"), mCallback(aCallback), mStream(aStream), mLength(aLength) { MOZ_ASSERT(mCallback); MOZ_ASSERT(mStream); } nsCOMPtr mCallback; RefPtr mStream; int64_t mLength; }; } // namespace void IPCBlobInputStream::LengthReady(int64_t aLength) { nsCOMPtr lengthCallback; nsCOMPtr lengthCallbackEventTarget; { MutexAutoLock lock(mMutex); // We have been closed in the meantime. if (mState == eClosed || mConsumed) { return; } if (mStart > 0) { aLength -= mStart; } if (mLength < mActor->Size()) { // If the remote stream must be sliced, we must return here the correct // value. aLength = XPCOM_MIN(aLength, (int64_t)mLength); } lengthCallback.swap(mLengthCallback); lengthCallbackEventTarget.swap(mLengthCallbackEventTarget); } if (lengthCallback) { InputStreamLengthCallbackRunnable::Execute( lengthCallback, lengthCallbackEventTarget, this, aLength); } } } // namespace dom } // namespace mozilla