/* 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 "ActorsChild.h" #include "BackgroundChildImpl.h" #include "FileHandleBase.h" #include "FileRequestBase.h" #include "js/Date.h" #include "mozilla/dom/EncodingUtils.h" #include "mozilla/dom/File.h" #include "mozilla/dom/ipc/BlobChild.h" #include "MutableFileBase.h" #include "nsCOMPtr.h" #include "nsContentUtils.h" #include "nsString.h" #include "xpcpublic.h" #include "mozilla/dom/BindingUtils.h" namespace mozilla { namespace dom { /******************************************************************************* * Helpers ******************************************************************************/ namespace { class MOZ_STACK_CLASS AutoSetCurrentFileHandle final { typedef mozilla::ipc::BackgroundChildImpl BackgroundChildImpl; FileHandleBase* const mFileHandle; FileHandleBase* mPreviousFileHandle; FileHandleBase** mThreadLocalSlot; public: explicit AutoSetCurrentFileHandle(FileHandleBase* aFileHandle) : mFileHandle(aFileHandle) , mPreviousFileHandle(nullptr) , mThreadLocalSlot(nullptr) { if (aFileHandle) { BackgroundChildImpl::ThreadLocal* threadLocal = BackgroundChildImpl::GetThreadLocalForCurrentThread(); MOZ_ASSERT(threadLocal); // Hang onto this location for resetting later. mThreadLocalSlot = &threadLocal->mCurrentFileHandle; // Save the current value. mPreviousFileHandle = *mThreadLocalSlot; // Set the new value. *mThreadLocalSlot = aFileHandle; } } ~AutoSetCurrentFileHandle() { MOZ_ASSERT_IF(mThreadLocalSlot, mFileHandle); MOZ_ASSERT_IF(mThreadLocalSlot, *mThreadLocalSlot == mFileHandle); if (mThreadLocalSlot) { // Reset old value. *mThreadLocalSlot = mPreviousFileHandle; } } FileHandleBase* FileHandle() const { return mFileHandle; } }; class MOZ_STACK_CLASS ResultHelper final : public FileRequestBase::ResultCallback { FileRequestBase* mFileRequest; AutoSetCurrentFileHandle mAutoFileHandle; union { File* mFile; const nsCString* mString; const FileRequestMetadata* mMetadata; const JS::Handle* mJSValHandle; } mResult; enum { ResultTypeFile, ResultTypeString, ResultTypeMetadata, ResultTypeJSValHandle, } mResultType; public: ResultHelper(FileRequestBase* aFileRequest, FileHandleBase* aFileHandle, File* aResult) : mFileRequest(aFileRequest) , mAutoFileHandle(aFileHandle) , mResultType(ResultTypeFile) { MOZ_ASSERT(aFileRequest); MOZ_ASSERT(aFileHandle); MOZ_ASSERT(aResult); mResult.mFile = aResult; } ResultHelper(FileRequestBase* aFileRequest, FileHandleBase* aFileHandle, const nsCString* aResult) : mFileRequest(aFileRequest) , mAutoFileHandle(aFileHandle) , mResultType(ResultTypeString) { MOZ_ASSERT(aFileRequest); MOZ_ASSERT(aFileHandle); MOZ_ASSERT(aResult); mResult.mString = aResult; } ResultHelper(FileRequestBase* aFileRequest, FileHandleBase* aFileHandle, const FileRequestMetadata* aResult) : mFileRequest(aFileRequest) , mAutoFileHandle(aFileHandle) , mResultType(ResultTypeMetadata) { MOZ_ASSERT(aFileRequest); MOZ_ASSERT(aFileHandle); MOZ_ASSERT(aResult); mResult.mMetadata = aResult; } ResultHelper(FileRequestBase* aFileRequest, FileHandleBase* aFileHandle, const JS::Handle* aResult) : mFileRequest(aFileRequest) , mAutoFileHandle(aFileHandle) , mResultType(ResultTypeJSValHandle) { MOZ_ASSERT(aFileRequest); MOZ_ASSERT(aFileHandle); MOZ_ASSERT(aResult); mResult.mJSValHandle = aResult; } FileRequestBase* FileRequest() const { return mFileRequest; } FileHandleBase* FileHandle() const { return mAutoFileHandle.FileHandle(); } virtual nsresult GetResult(JSContext* aCx, JS::MutableHandle aResult) override { MOZ_ASSERT(aCx); MOZ_ASSERT(mFileRequest); switch (mResultType) { case ResultTypeFile: return GetResult(aCx, mResult.mFile, aResult); case ResultTypeString: return GetResult(aCx, mResult.mString, aResult); case ResultTypeMetadata: return GetResult(aCx, mResult.mMetadata, aResult); case ResultTypeJSValHandle: aResult.set(*mResult.mJSValHandle); return NS_OK; default: MOZ_CRASH("Unknown result type!"); } MOZ_CRASH("Should never get here!"); } private: nsresult GetResult(JSContext* aCx, File* aFile, JS::MutableHandle aResult) { bool ok = GetOrCreateDOMReflector(aCx, aFile, aResult); if (NS_WARN_IF(!ok)) { return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR; } return NS_OK; } nsresult GetResult(JSContext* aCx, const nsCString* aString, JS::MutableHandle aResult) { const nsCString& data = *aString; nsresult rv; if (!mFileRequest->HasEncoding()) { JS::Rooted arrayBuffer(aCx); rv = nsContentUtils::CreateArrayBuffer(aCx, data, arrayBuffer.address()); if (NS_WARN_IF(NS_FAILED(rv))) { return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR; } aResult.setObject(*arrayBuffer); return NS_OK; } nsAutoCString encoding; // The BOM sniffing is baked into the "decode" part of the Encoding // Standard, which the File API references. if (!nsContentUtils::CheckForBOM( reinterpret_cast(data.get()), data.Length(), encoding)) { // BOM sniffing failed. Try the API argument. if (!EncodingUtils::FindEncodingForLabel(mFileRequest->GetEncoding(), encoding)) { // API argument failed. Since we are dealing with a file system file, // we don't have a meaningful type attribute for the blob available, // so proceeding to the next step, which is defaulting to UTF-8. encoding.AssignLiteral("UTF-8"); } } nsString tmpString; rv = nsContentUtils::ConvertStringFromEncoding(encoding, data, tmpString); if (NS_WARN_IF(NS_FAILED(rv))) { return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR; } if (NS_WARN_IF(!xpc::StringToJsval(aCx, tmpString, aResult))) { return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR; } return NS_OK; } nsresult GetResult(JSContext* aCx, const FileRequestMetadata* aMetadata, JS::MutableHandle aResult) { JS::Rooted obj(aCx, JS_NewPlainObject(aCx)); if (NS_WARN_IF(!obj)) { return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR; } const FileRequestSize& size = aMetadata->size(); if (size.type() != FileRequestSize::Tvoid_t) { MOZ_ASSERT(size.type() == FileRequestSize::Tuint64_t); JS::Rooted number(aCx, JS_NumberValue(size.get_uint64_t())); if (NS_WARN_IF(!JS_DefineProperty(aCx, obj, "size", number, 0))) { return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR; } } const FileRequestLastModified& lastModified = aMetadata->lastModified(); if (lastModified.type() != FileRequestLastModified::Tvoid_t) { MOZ_ASSERT(lastModified.type() == FileRequestLastModified::Tint64_t); JS::Rooted date(aCx, JS::NewDateObject(aCx, JS::TimeClip(lastModified.get_int64_t()))); if (NS_WARN_IF(!date)) { return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR; } if (NS_WARN_IF(!JS_DefineProperty(aCx, obj, "lastModified", date, 0))) { return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR; } } aResult.setObject(*obj); return NS_OK; } }; already_AddRefed ConvertActorToFile(FileHandleBase* aFileHandle, const FileRequestGetFileResponse& aResponse) { auto* actor = static_cast(aResponse.fileChild()); MutableFileBase* mutableFile = aFileHandle->MutableFile(); MOZ_ASSERT(mutableFile); const FileRequestMetadata& metadata = aResponse.metadata(); const FileRequestSize& size = metadata.size(); MOZ_ASSERT(size.type() == FileRequestSize::Tuint64_t); const FileRequestLastModified& lastModified = metadata.lastModified(); MOZ_ASSERT(lastModified.type() == FileRequestLastModified::Tint64_t); actor->SetMysteryBlobInfo(mutableFile->Name(), mutableFile->Type(), size.get_uint64_t(), lastModified.get_int64_t()); RefPtr blobImpl = actor->GetBlobImpl(); MOZ_ASSERT(blobImpl); RefPtr file = mutableFile->CreateFileFor(blobImpl, aFileHandle); return file.forget(); } void HandleSuccess(ResultHelper* aResultHelper) { MOZ_ASSERT(aResultHelper); RefPtr fileRequest = aResultHelper->FileRequest(); MOZ_ASSERT(fileRequest); fileRequest->AssertIsOnOwningThread(); RefPtr fileHandle = aResultHelper->FileHandle(); MOZ_ASSERT(fileHandle); if (fileHandle->IsAborted()) { fileRequest->SetError(NS_ERROR_DOM_FILEHANDLE_ABORT_ERR); return; } MOZ_ASSERT(fileHandle->IsOpen()); fileRequest->SetResultCallback(aResultHelper); MOZ_ASSERT(fileHandle->IsOpen() || fileHandle->IsAborted()); } void HandleError(FileRequestBase* aFileRequest, nsresult aErrorCode, FileHandleBase* aFileHandle) { MOZ_ASSERT(aFileRequest); aFileRequest->AssertIsOnOwningThread(); MOZ_ASSERT(NS_FAILED(aErrorCode)); MOZ_ASSERT(NS_ERROR_GET_MODULE(aErrorCode) == NS_ERROR_MODULE_DOM_FILEHANDLE); MOZ_ASSERT(aFileHandle); RefPtr fileRequest = aFileRequest; RefPtr fileHandle = aFileHandle; AutoSetCurrentFileHandle ascfh(aFileHandle); fileRequest->SetError(aErrorCode); MOZ_ASSERT(fileHandle->IsOpen() || fileHandle->IsAborted()); } } // anonymous namespace /******************************************************************************* * BackgroundMutableFileChildBase ******************************************************************************/ BackgroundMutableFileChildBase::BackgroundMutableFileChildBase( DEBUGONLY(PRThread* aOwningThread)) : ThreadObject(DEBUGONLY(aOwningThread)) , mMutableFile(nullptr) { AssertIsOnOwningThread(); MOZ_COUNT_CTOR(BackgroundMutableFileChildBase); } BackgroundMutableFileChildBase::~BackgroundMutableFileChildBase() { AssertIsOnOwningThread(); MOZ_COUNT_DTOR(BackgroundMutableFileChildBase); } void BackgroundMutableFileChildBase::EnsureDOMObject() { AssertIsOnOwningThread(); if (mTemporaryStrongMutableFile) { return; } mTemporaryStrongMutableFile = CreateMutableFile(); MOZ_ASSERT(mTemporaryStrongMutableFile); mTemporaryStrongMutableFile->AssertIsOnOwningThread(); mMutableFile = mTemporaryStrongMutableFile; } void BackgroundMutableFileChildBase::ReleaseDOMObject() { AssertIsOnOwningThread(); MOZ_ASSERT(mTemporaryStrongMutableFile); mTemporaryStrongMutableFile->AssertIsOnOwningThread(); MOZ_ASSERT(mMutableFile == mTemporaryStrongMutableFile); mTemporaryStrongMutableFile = nullptr; } void BackgroundMutableFileChildBase::SendDeleteMeInternal() { AssertIsOnOwningThread(); MOZ_ASSERT(!mTemporaryStrongMutableFile); if (mMutableFile) { mMutableFile->ClearBackgroundActor(); mMutableFile = nullptr; MOZ_ALWAYS_TRUE(PBackgroundMutableFileChild::SendDeleteMe()); } } void BackgroundMutableFileChildBase::ActorDestroy(ActorDestroyReason aWhy) { AssertIsOnOwningThread(); if (mMutableFile) { mMutableFile->ClearBackgroundActor(); DEBUGONLY(mMutableFile = nullptr;) } } PBackgroundFileHandleChild* BackgroundMutableFileChildBase::AllocPBackgroundFileHandleChild( const FileMode& aMode) { MOZ_CRASH("PBackgroundFileHandleChild actors should be manually " "constructed!"); } bool BackgroundMutableFileChildBase::DeallocPBackgroundFileHandleChild( PBackgroundFileHandleChild* aActor) { AssertIsOnOwningThread(); MOZ_ASSERT(aActor); delete static_cast(aActor); return true; } /******************************************************************************* * BackgroundFileHandleChild ******************************************************************************/ BackgroundFileHandleChild::BackgroundFileHandleChild( DEBUGONLY(PRThread* aOwningThread,) FileHandleBase* aFileHandle) : ThreadObject(DEBUGONLY(aOwningThread)) , mTemporaryStrongFileHandle(aFileHandle) , mFileHandle(aFileHandle) { AssertIsOnOwningThread(); MOZ_ASSERT(aFileHandle); aFileHandle->AssertIsOnOwningThread(); MOZ_COUNT_CTOR(BackgroundFileHandleChild); } BackgroundFileHandleChild::~BackgroundFileHandleChild() { AssertIsOnOwningThread(); MOZ_COUNT_DTOR(BackgroundFileHandleChild); } void BackgroundFileHandleChild::SendDeleteMeInternal() { AssertIsOnOwningThread(); if (mFileHandle) { NoteActorDestroyed(); MOZ_ALWAYS_TRUE(PBackgroundFileHandleChild::SendDeleteMe()); } } void BackgroundFileHandleChild::NoteActorDestroyed() { AssertIsOnOwningThread(); MOZ_ASSERT_IF(mTemporaryStrongFileHandle, mFileHandle); if (mFileHandle) { mFileHandle->ClearBackgroundActor(); // Normally this would be DEBUG-only but NoteActorDestroyed is also called // from SendDeleteMeInternal. In that case we're going to receive an actual // ActorDestroy call later and we don't want to touch a dead object. mTemporaryStrongFileHandle = nullptr; mFileHandle = nullptr; } } void BackgroundFileHandleChild::NoteComplete() { AssertIsOnOwningThread(); MOZ_ASSERT_IF(mFileHandle, mTemporaryStrongFileHandle); mTemporaryStrongFileHandle = nullptr; } void BackgroundFileHandleChild::ActorDestroy(ActorDestroyReason aWhy) { AssertIsOnOwningThread(); NoteActorDestroyed(); } bool BackgroundFileHandleChild::RecvComplete(const bool& aAborted) { AssertIsOnOwningThread(); MOZ_ASSERT(mFileHandle); mFileHandle->HandleCompleteOrAbort(aAborted); NoteComplete(); return true; } PBackgroundFileRequestChild* BackgroundFileHandleChild::AllocPBackgroundFileRequestChild( const FileRequestParams& aParams) { MOZ_CRASH("PBackgroundFileRequestChild actors should be manually " "constructed!"); } bool BackgroundFileHandleChild::DeallocPBackgroundFileRequestChild( PBackgroundFileRequestChild* aActor) { MOZ_ASSERT(aActor); delete static_cast(aActor); return true; } /******************************************************************************* * BackgroundFileRequestChild ******************************************************************************/ BackgroundFileRequestChild::BackgroundFileRequestChild( DEBUGONLY(PRThread* aOwningThread,) FileRequestBase* aFileRequest) : ThreadObject(DEBUGONLY(aOwningThread)) , mFileRequest(aFileRequest) , mFileHandle(aFileRequest->FileHandle()) , mActorDestroyed(false) { AssertIsOnOwningThread(); MOZ_ASSERT(aFileRequest); aFileRequest->AssertIsOnOwningThread(); MOZ_ASSERT(mFileHandle); mFileHandle->AssertIsOnOwningThread(); MOZ_COUNT_CTOR(BackgroundFileRequestChild); } BackgroundFileRequestChild::~BackgroundFileRequestChild() { AssertIsOnOwningThread(); MOZ_ASSERT(!mFileHandle); MOZ_COUNT_DTOR(BackgroundFileRequestChild); } void BackgroundFileRequestChild::HandleResponse(nsresult aResponse) { AssertIsOnOwningThread(); MOZ_ASSERT(NS_FAILED(aResponse)); MOZ_ASSERT(NS_ERROR_GET_MODULE(aResponse) == NS_ERROR_MODULE_DOM_FILEHANDLE); MOZ_ASSERT(mFileHandle); HandleError(mFileRequest, aResponse, mFileHandle); } void BackgroundFileRequestChild::HandleResponse( const FileRequestGetFileResponse& aResponse) { AssertIsOnOwningThread(); RefPtr file = ConvertActorToFile(mFileHandle, aResponse); ResultHelper helper(mFileRequest, mFileHandle, file); HandleSuccess(&helper); } void BackgroundFileRequestChild::HandleResponse(const nsCString& aResponse) { AssertIsOnOwningThread(); ResultHelper helper(mFileRequest, mFileHandle, &aResponse); HandleSuccess(&helper); } void BackgroundFileRequestChild::HandleResponse(const FileRequestMetadata& aResponse) { AssertIsOnOwningThread(); ResultHelper helper(mFileRequest, mFileHandle, &aResponse); HandleSuccess(&helper); } void BackgroundFileRequestChild::HandleResponse(JS::Handle aResponse) { AssertIsOnOwningThread(); ResultHelper helper(mFileRequest, mFileHandle, &aResponse); HandleSuccess(&helper); } void BackgroundFileRequestChild::ActorDestroy(ActorDestroyReason aWhy) { AssertIsOnOwningThread(); MOZ_ASSERT(!mActorDestroyed); mActorDestroyed = true; if (mFileHandle) { mFileHandle->AssertIsOnOwningThread(); mFileHandle->OnRequestFinished(/* aActorDestroyedNormally */ aWhy == Deletion); DEBUGONLY(mFileHandle = nullptr;) } } bool BackgroundFileRequestChild::Recv__delete__(const FileRequestResponse& aResponse) { AssertIsOnOwningThread(); MOZ_ASSERT(mFileRequest); MOZ_ASSERT(mFileHandle); if (mFileHandle->IsAborted()) { // Always handle an "error" with ABORT_ERR if the file handle was aborted, // even if the request succeeded or failed with another error. HandleResponse(NS_ERROR_DOM_FILEHANDLE_ABORT_ERR); } else { switch (aResponse.type()) { case FileRequestResponse::Tnsresult: HandleResponse(aResponse.get_nsresult()); break; case FileRequestResponse::TFileRequestGetFileResponse: HandleResponse(aResponse.get_FileRequestGetFileResponse()); break; case FileRequestResponse::TFileRequestReadResponse: HandleResponse(aResponse.get_FileRequestReadResponse().data()); break; case FileRequestResponse::TFileRequestWriteResponse: HandleResponse(JS::UndefinedHandleValue); break; case FileRequestResponse::TFileRequestTruncateResponse: HandleResponse(JS::UndefinedHandleValue); break; case FileRequestResponse::TFileRequestFlushResponse: HandleResponse(JS::UndefinedHandleValue); break; case FileRequestResponse::TFileRequestGetMetadataResponse: HandleResponse(aResponse.get_FileRequestGetMetadataResponse() .metadata()); break; default: MOZ_CRASH("Unknown response type!"); } } mFileHandle->OnRequestFinished(/* aActorDestroyedNormally */ true); // Null this out so that we don't try to call OnRequestFinished() again in // ActorDestroy. mFileHandle = nullptr; return true; } bool BackgroundFileRequestChild::RecvProgress(const uint64_t& aProgress, const uint64_t& aProgressMax) { AssertIsOnOwningThread(); MOZ_ASSERT(mFileRequest); mFileRequest->OnProgress(aProgress, aProgressMax); return true; } } // namespace dom } // namespace mozilla