Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 "FileBlobImpl.h"
#include "BaseBlobImpl.h"
#include "mozilla/SlicedInputStream.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/WorkerRunnable.h"
#include "nsCExternalHandlerService.h"
#include "nsIFile.h"
#include "nsIFileStreams.h"
#include "nsIMIMEService.h"
#include "nsNetUtil.h"
#include "nsStreamUtils.h"
namespace mozilla::dom {
FileBlobImpl::FileBlobImpl(nsIFile* aFile)
: mMutex("FileBlobImpl::mMutex"),
mFile(aFile),
mSerialNumber(BaseBlobImpl::NextSerialNumber()),
mStart(0),
mFileId(-1),
mIsFile(true),
mWholeFile(true) {
MOZ_ASSERT(mFile, "must have file");
MOZ_ASSERT(XRE_IsParentProcess());
// Lazily get the content type and size
mContentType.SetIsVoid(true);
mMozFullPath.SetIsVoid(true);
mFile->GetLeafName(mName);
}
FileBlobImpl::FileBlobImpl(const nsAString& aName,
const nsAString& aContentType, uint64_t aLength,
nsIFile* aFile)
: mMutex("FileBlobImpl::mMutex"),
mFile(aFile),
mContentType(aContentType),
mName(aName),
mSerialNumber(BaseBlobImpl::NextSerialNumber()),
mStart(0),
mFileId(-1),
mLength(Some(aLength)),
mIsFile(true),
mWholeFile(true) {
MOZ_ASSERT(mFile, "must have file");
MOZ_ASSERT(XRE_IsParentProcess());
mMozFullPath.SetIsVoid(true);
}
FileBlobImpl::FileBlobImpl(const nsAString& aName,
const nsAString& aContentType, uint64_t aLength,
nsIFile* aFile, int64_t aLastModificationDate)
: mMutex("FileBlobImpl::mMutex"),
mFile(aFile),
mContentType(aContentType),
mName(aName),
mSerialNumber(BaseBlobImpl::NextSerialNumber()),
mStart(0),
mFileId(-1),
mLength(Some(aLength)),
mLastModified(Some(aLastModificationDate)),
mIsFile(true),
mWholeFile(true) {
MOZ_ASSERT(mFile, "must have file");
MOZ_ASSERT(XRE_IsParentProcess());
mMozFullPath.SetIsVoid(true);
}
FileBlobImpl::FileBlobImpl(nsIFile* aFile, const nsAString& aName,
const nsAString& aContentType)
: mMutex("FileBlobImpl::mMutex"),
mFile(aFile),
mContentType(aContentType),
mName(aName),
mSerialNumber(BaseBlobImpl::NextSerialNumber()),
mStart(0),
mFileId(-1),
mIsFile(true),
mWholeFile(true) {
MOZ_ASSERT(mFile, "must have file");
MOZ_ASSERT(XRE_IsParentProcess());
if (aContentType.IsEmpty()) {
// Lazily get the content type and size
mContentType.SetIsVoid(true);
}
mMozFullPath.SetIsVoid(true);
}
FileBlobImpl::FileBlobImpl(const FileBlobImpl* aOther, uint64_t aStart,
uint64_t aLength, const nsAString& aContentType)
: mMutex("FileBlobImpl::mMutex"),
mFile(aOther->mFile),
mContentType(aContentType),
mSerialNumber(BaseBlobImpl::NextSerialNumber()),
mStart(aOther->mStart + aStart),
mFileId(-1),
mLength(Some(aLength)),
mIsFile(false),
mWholeFile(false) {
MOZ_ASSERT(mFile, "must have file");
MOZ_ASSERT(XRE_IsParentProcess());
mMozFullPath = aOther->mMozFullPath;
}
already_AddRefed<BlobImpl> FileBlobImpl::CreateSlice(
uint64_t aStart, uint64_t aLength, const nsAString& aContentType,
ErrorResult& aRv) const {
RefPtr<FileBlobImpl> impl =
new FileBlobImpl(this, aStart, aLength, aContentType);
return impl.forget();
}
void FileBlobImpl::GetMozFullPathInternal(nsAString& aFilename,
ErrorResult& aRv) {
MOZ_ASSERT(mIsFile, "Should only be called on files");
MutexAutoLock lock(mMutex);
if (!mMozFullPath.IsVoid()) {
aFilename = mMozFullPath;
return;
}
aRv = mFile->GetPath(aFilename);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
mMozFullPath = aFilename;
}
uint64_t FileBlobImpl::GetSize(ErrorResult& aRv) {
MutexAutoLock lock(mMutex);
if (mLength.isNothing()) {
MOZ_ASSERT(mWholeFile,
"Should only use lazy size when using the whole file");
int64_t fileSize;
aRv = mFile->GetFileSize(&fileSize);
if (NS_WARN_IF(aRv.Failed())) {
return 0;
}
if (fileSize < 0) {
aRv.Throw(NS_ERROR_FAILURE);
return 0;
}
mLength.emplace(fileSize);
}
return mLength.value();
}
class FileBlobImpl::GetTypeRunnable final : public WorkerMainThreadRunnable {
public:
GetTypeRunnable(WorkerPrivate* aWorkerPrivate, FileBlobImpl* aBlobImpl)
: WorkerMainThreadRunnable(aWorkerPrivate, "FileBlobImpl :: GetType"_ns),
mBlobImpl(aBlobImpl) {
MOZ_ASSERT(aBlobImpl);
aWorkerPrivate->AssertIsOnWorkerThread();
}
bool MainThreadRun() override {
MOZ_ASSERT(NS_IsMainThread());
nsAutoString type;
mBlobImpl->GetType(type);
return true;
}
private:
~GetTypeRunnable() override = default;
RefPtr<FileBlobImpl> mBlobImpl;
};
void FileBlobImpl::GetType(nsAString& aType) {
MutexAutoLock lock(mMutex);
aType.Truncate();
if (mContentType.IsVoid()) {
MOZ_ASSERT(mWholeFile,
"Should only use lazy ContentType when using the whole file");
if (!NS_IsMainThread()) {
WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
if (!workerPrivate) {
// I have no idea in which thread this method is called. We cannot
// return any valid value.
return;
}
// NOTE: We need to unlock the mutex while we're dispatching to the main
// thread, as otherwise we could deadlock in a few ways:
//
// 1. We spin a nested event loop while `Dispatch` is being called to wait
// for the runnable to complete. Some event dispatched to that nested
// loop could theoretically access `FileBlobImpl` which would lead to a
// deadlock on this thread.
// 2. The main thread could attempt to access a method on the
// `FileBlobImpl` while the runnable is being dispatched to the main
// thread, which will lead to the main thread being deadlocked (as the
// background thread is still holding the mutex).
//
// Instead, we unlock here, and we'll re-acquire the mutex on the main
// thread to update `mContentType`, and acquire it again on this thread to
// return the relevant value.
MutexAutoUnlock unlock(mMutex);
RefPtr<GetTypeRunnable> runnable =
new GetTypeRunnable(workerPrivate, this);
ErrorResult rv;
runnable->Dispatch(workerPrivate, Canceling, rv);
if (NS_WARN_IF(rv.Failed())) {
rv.SuppressException();
return;
}
} else {
nsresult rv;
nsCOMPtr<nsIMIMEService> mimeService =
do_GetService(NS_MIMESERVICE_CONTRACTID, &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
nsAutoCString mimeType;
rv = mimeService->GetTypeFromFile(mFile, mimeType);
if (NS_FAILED(rv)) {
mimeType.Truncate();
}
AppendUTF8toUTF16(mimeType, mContentType);
mContentType.SetIsVoid(false);
}
}
aType = mContentType;
}
void FileBlobImpl::GetBlobImplType(nsAString& aBlobImplType) const {
aBlobImplType = u"FileBlobImpl"_ns;
}
int64_t FileBlobImpl::GetLastModified(ErrorResult& aRv) {
MOZ_ASSERT(mIsFile, "Should only be called on files");
MutexAutoLock lock(mMutex);
if (mLastModified.isNothing()) {
PRTime msecs;
aRv = mFile->GetLastModifiedTime(&msecs);
if (NS_WARN_IF(aRv.Failed())) {
return 0;
}
mLastModified.emplace(int64_t(msecs));
}
return mLastModified.value();
}
const uint32_t sFileStreamFlags =
nsIFileInputStream::CLOSE_ON_EOF | nsIFileInputStream::REOPEN_ON_REWIND |
nsIFileInputStream::DEFER_OPEN | nsIFileInputStream::SHARE_DELETE;
void FileBlobImpl::CreateInputStream(nsIInputStream** aStream,
ErrorResult& aRv) const {
nsCOMPtr<nsIInputStream> stream;
aRv = NS_NewLocalFileInputStream(getter_AddRefs(stream), mFile, -1, -1,
sFileStreamFlags);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
if (mWholeFile) {
stream.forget(aStream);
return;
}
MOZ_ASSERT(mLength.isSome());
RefPtr<SlicedInputStream> slicedInputStream =
new SlicedInputStream(stream.forget(), mStart, mLength.value());
slicedInputStream.forget(aStream);
}
bool FileBlobImpl::IsDirectory() const {
bool isDirectory = false;
if (mFile) {
mFile->IsDirectory(&isDirectory);
}
return isDirectory;
}
} // namespace mozilla::dom