Bug 1482752 - Have Fetch bodies use File blobs for local files instead of regular blobs. r=baku

Have Fetch bodies use File blobs for local files instead of regular blobs.

Differential Revision: https://phabricator.services.mozilla.com/D3183

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Thomas Wisniewski 2018-09-11 19:13:15 +00:00
parent d8f3b49815
commit 400093bdb6
12 changed files with 327 additions and 28 deletions

View File

@ -1252,6 +1252,21 @@ template
void
FetchBody<Response>::SetMimeType();
template <class Derived>
const nsAString&
FetchBody<Derived>::BodyLocalPath() const
{
return DerivedClass()->BodyLocalPath();
}
template
const nsAString&
FetchBody<Request>::BodyLocalPath() const;
template
const nsAString&
FetchBody<Response>::BodyLocalPath() const;
template <class Derived>
void
FetchBody<Derived>::SetReadableStreamBody(JSContext* aCx, JSObject* aBody)

View File

@ -187,6 +187,9 @@ public:
JS::MutableHandle<JSObject*> aBodyOut,
ErrorResult& aRv);
const nsAString&
BodyLocalPath() const;
// If the body contains a ReadableStream body object, this method produces a
// tee() of it.
void

View File

@ -7,6 +7,11 @@
#include "Fetch.h"
#include "FetchConsumer.h"
#include "mozilla/dom/BlobBinding.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/FileBinding.h"
#include "mozilla/dom/FileCreatorHelper.h"
#include "mozilla/dom/PromiseNativeHandler.h"
#include "mozilla/dom/WorkerCommon.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/WorkerRef.h"
@ -17,6 +22,12 @@
#include "nsIThreadRetargetableRequest.h"
#include "nsProxyRelease.h"
// Undefine the macro of CreateFile to avoid FileCreatorHelper#CreateFile being
// replaced by FileCreatorHelper#CreateFileW.
#ifdef CreateFile
#undef CreateFile
#endif
namespace mozilla {
namespace dom {
@ -284,34 +295,7 @@ public:
// consuming of the body.
mFetchBodyConsumer->NullifyConsumeBodyPump();
MOZ_ASSERT(aBlob);
// Main-thread.
if (!mWorkerRef) {
mFetchBodyConsumer->ContinueConsumeBlobBody(aBlob->Impl());
return;
}
// Web Worker.
{
RefPtr<ContinueConsumeBlobBodyRunnable<Derived>> r =
new ContinueConsumeBlobBodyRunnable<Derived>(mFetchBodyConsumer,
mWorkerRef->Private(),
aBlob->Impl());
if (r->Dispatch()) {
return;
}
}
// The worker is shutting down. Let's use a control runnable to complete the
// shutting down procedure.
RefPtr<AbortConsumeBlobBodyControlRunnable<Derived>> r =
new AbortConsumeBlobBodyControlRunnable<Derived>(mFetchBodyConsumer,
mWorkerRef->Private());
Unused << NS_WARN_IF(!r->Dispatch());
mFetchBodyConsumer->OnBlobResult(aBlob, mWorkerRef);
}
private:
@ -450,6 +434,7 @@ FetchBodyConsumer<Derived>::FetchBodyConsumer(nsIEventTarget* aMainThreadEventTa
#endif
, mBodyStream(aBodyStream)
, mBlobStorageType(MutableBlobStorage::eOnlyInMemory)
, mBodyLocalPath(aBody ? aBody->BodyLocalPath() : VoidString())
, mGlobal(aGlobalObject)
, mConsumeType(aType)
, mConsumePromise(aPromise)
@ -487,6 +472,112 @@ FetchBodyConsumer<Derived>::AssertIsOnTargetThread() const
MOZ_ASSERT(NS_GetCurrentThread() == mTargetThread);
}
namespace {
template <class Derived>
class FileCreationHandler final : public PromiseNativeHandler
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
static void
Create(Promise* aPromise, FetchBodyConsumer<Derived>* aConsumer)
{
AssertIsOnMainThread();
MOZ_ASSERT(aPromise);
RefPtr<FileCreationHandler> handler = new FileCreationHandler<Derived>(aConsumer);
aPromise->AppendNativeHandler(handler);
}
void
ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
{
AssertIsOnMainThread();
if (NS_WARN_IF(!aValue.isObject())) {
mConsumer->OnBlobResult(nullptr);
return;
}
RefPtr<Blob> blob;
if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Blob, &aValue.toObject(), blob)))) {
mConsumer->OnBlobResult(nullptr);
return;
}
mConsumer->OnBlobResult(blob);
}
void
RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
{
AssertIsOnMainThread();
mConsumer->OnBlobResult(nullptr);
}
private:
explicit FileCreationHandler<Derived>(FetchBodyConsumer<Derived>* aConsumer)
: mConsumer(aConsumer)
{
AssertIsOnMainThread();
MOZ_ASSERT(aConsumer);
}
~FileCreationHandler() = default;
RefPtr<FetchBodyConsumer<Derived>> mConsumer;
};
template <class Derived>
NS_IMPL_ADDREF(FileCreationHandler<Derived>)
template <class Derived>
NS_IMPL_RELEASE(FileCreationHandler<Derived>)
template <class Derived>
NS_INTERFACE_MAP_BEGIN(FileCreationHandler<Derived>)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
} // namespace
template <class Derived>
nsresult
FetchBodyConsumer<Derived>::GetBodyLocalFile(nsIFile** aFile) const
{
AssertIsOnMainThread();
if (!mBodyLocalPath.Length()) {
return NS_OK;
}
nsresult rv;
nsCOMPtr<nsIFile> file = do_CreateInstance("@mozilla.org/file/local;1", &rv);
if (NS_FAILED(rv)) {
return rv;
}
rv = file->InitWithPath(mBodyLocalPath);
NS_ENSURE_SUCCESS(rv, rv);
bool exists;
rv = file->Exists(&exists);
NS_ENSURE_SUCCESS(rv, rv);
if (!exists) {
return NS_ERROR_FILE_NOT_FOUND;
}
bool isDir;
rv = file->IsDirectory(&isDir);
NS_ENSURE_SUCCESS(rv, rv);
if (isDir) {
return NS_ERROR_FILE_IS_DIRECTORY;
}
file.forget(aFile);
return NS_OK;
}
/*
* BeginConsumeBodyMainThread() will automatically reject the consume promise
* and clean up on any failures, so there is no need for callers to do so,
@ -506,6 +597,28 @@ FetchBodyConsumer<Derived>::BeginConsumeBodyMainThread(ThreadSafeWorkerRef* aWor
return;
}
// If we're trying to consume a blob, and the request was for a local
// file, then generate and return a File blob.
if (mConsumeType == CONSUME_BLOB) {
nsCOMPtr<nsIFile> file;
nsresult rv = GetBodyLocalFile(getter_AddRefs(file));
if (!NS_WARN_IF(NS_FAILED(rv)) && file) {
ChromeFilePropertyBag bag;
bag.mType = NS_ConvertUTF8toUTF16(mBodyMimeType);
ErrorResult error;
RefPtr<Promise> promise =
FileCreatorHelper::CreateFile(mGlobal, file, bag, true, error);
if (NS_WARN_IF(error.Failed())) {
return;
}
FileCreationHandler<Derived>::Create(promise, this);
autoReject.DontFail();
return;
}
}
nsCOMPtr<nsIInputStreamPump> pump;
nsresult rv = NS_NewInputStreamPump(getter_AddRefs(pump),
mBodyStream.forget(), 0, 0, false,
@ -555,6 +668,51 @@ FetchBodyConsumer<Derived>::BeginConsumeBodyMainThread(ThreadSafeWorkerRef* aWor
}
}
/*
* OnBlobResult() is called when a blob body is ready to be consumed (when its
* network transfer completes in BeginConsumeBodyRunnable or its local File has
* been wrapped by FileCreationHandler). The blob is sent to the target thread
* and ContinueConsumeBody is called.
*/
template <class Derived>
void
FetchBodyConsumer<Derived>::OnBlobResult(Blob* aBlob, ThreadSafeWorkerRef* aWorkerRef)
{
MOZ_ASSERT(aBlob);
// Main-thread.
if (!aWorkerRef) {
ContinueConsumeBlobBody(aBlob->Impl());
return;
}
// Web Worker.
{
RefPtr<ContinueConsumeBlobBodyRunnable<Derived>> r =
new ContinueConsumeBlobBodyRunnable<Derived>(this, aWorkerRef->Private(),
aBlob->Impl());
if (r->Dispatch()) {
return;
}
}
// The worker is shutting down. Let's use a control runnable to complete the
// shutting down procedure.
RefPtr<AbortConsumeBlobBodyControlRunnable<Derived>> r =
new AbortConsumeBlobBodyControlRunnable<Derived>(this,
aWorkerRef->Private());
Unused << NS_WARN_IF(!r->Dispatch());
}
/*
* ContinueConsumeBody() is to be called on the target thread whenever the
* final result of the fetch is known. The fetch promise is resolved or
* rejected based on whether the fetch succeeded, and the body can be
* converted into the expected type of JS object.
*/
template <class Derived>
void
FetchBodyConsumer<Derived>::ContinueConsumeBody(nsresult aStatus,

View File

@ -49,6 +49,9 @@ public:
void
BeginConsumeBodyMainThread(ThreadSafeWorkerRef* aWorkerRef);
void
OnBlobResult(Blob* aBlob, ThreadSafeWorkerRef* aWorkerRef = nullptr);
void
ContinueConsumeBody(nsresult aStatus, uint32_t aLength, uint8_t* aResult,
bool aShuttingDown = false);
@ -79,6 +82,9 @@ private:
~FetchBodyConsumer();
nsresult
GetBodyLocalFile(nsIFile** aFile) const;
void
AssertIsOnTargetThread() const;
@ -96,6 +102,8 @@ private:
MutableBlobStorage::MutableBlobStorageType mBlobStorageType;
nsCString mBodyMimeType;
nsString mBodyLocalPath;
nsCOMPtr<nsIGlobalObject> mGlobal;
// Touched on the main-thread only.

View File

@ -11,6 +11,7 @@
#include "nsIDocument.h"
#include "nsIInputStream.h"
#include "nsIOutputStream.h"
#include "nsIFileChannel.h"
#include "nsIHttpChannel.h"
#include "nsIHttpChannelInternal.h"
#include "nsIScriptSecurityManager.h"
@ -950,6 +951,19 @@ FetchDriver::OnStartRequest(nsIRequest* aRequest,
}
response->SetBody(pipeInputStream, contentLength);
// If the request is a file channel, then remember the local path to
// that file so we can later create File blobs rather than plain ones.
nsCOMPtr<nsIFileChannel> fc = do_QueryInterface(aRequest);
if (fc) {
nsCOMPtr<nsIFile> file;
rv = fc->GetFile(getter_AddRefs(file));
if (!NS_WARN_IF(NS_FAILED(rv))) {
nsAutoString path;
file->GetPath(path);
response->SetBodyLocalPath(path);
}
}
response->InitChannelInfo(channel);
nsCOMPtr<nsIURI> channelURI;

View File

@ -476,6 +476,18 @@ public:
}
}
void
SetBodyLocalPath(nsAString& aLocalPath)
{
mBodyLocalPath = aLocalPath;
}
const nsAString&
BodyLocalPath() const
{
return mBodyLocalPath;
}
// The global is used as the client for the new object.
already_AddRefed<InternalRequest>
GetRequestConstructorCopy(nsIGlobalObject* aGlobal, ErrorResult& aRv) const;
@ -570,6 +582,7 @@ private:
// mURLList: a list of one or more fetch URLs
nsTArray<nsCString> mURLList;
RefPtr<InternalHeaders> mHeaders;
nsString mBodyLocalPath;
nsCOMPtr<nsIInputStream> mBodyStream;
int64_t mBodyLength;

View File

@ -219,6 +219,21 @@ public:
GetUnfilteredBody(aStream, aBodySize);
}
void
SetBodyLocalPath(nsAString& aLocalPath)
{
mBodyLocalPath = aLocalPath;
}
const nsAString&
BodyLocalPath() const
{
if (mWrappedResponse) {
return mWrappedResponse->BodyLocalPath();
}
return mBodyLocalPath;
}
void
SetBody(nsIInputStream* aBody, int64_t aBodySize)
{
@ -373,6 +388,7 @@ private:
const nsCString mStatusText;
RefPtr<InternalHeaders> mHeaders;
nsCOMPtr<nsIInputStream> mBody;
nsString mBodyLocalPath;
int64_t mBodySize;
// It's used to passed to the CacheResponse to generate padding size. Once, we
// generate the padding size for resposne, we don't need it anymore.

View File

@ -143,6 +143,14 @@ public:
mRequest->SetBody(aStream, aBodyLength);
}
using FetchBody::BodyLocalPath;
const nsAString&
BodyLocalPath() const
{
return mRequest->BodyLocalPath();
}
static already_AddRefed<Request>
Constructor(const GlobalObject& aGlobal, const RequestOrUSVString& aInput,
const RequestInit& aInit, ErrorResult& rv);

View File

@ -112,6 +112,14 @@ public:
using FetchBody::GetBody;
using FetchBody::BodyLocalPath;
const nsAString&
BodyLocalPath() const
{
return mInternalResponse->BodyLocalPath();
}
static already_AddRefed<Response>
Error(const GlobalObject& aGlobal);

View File

@ -58,6 +58,8 @@ LOCAL_INCLUDES += [
'/netwerk/protocol/http',
]
BROWSER_CHROME_MANIFESTS += [ 'tests/browser.ini' ]
FINAL_LIBRARY = 'xul'
include('/ipc/chromium/chromium-config.mozbuild')

View File

@ -0,0 +1,2 @@
[DEFAULT]
[browser_blobFromFile.js]

View File

@ -0,0 +1,52 @@
add_task(async function test() {
await SpecialPowers.pushPrefEnv(
{set: [["browser.tabs.remote.separateFileUriProcess", true]]}
);
let fileData = "";
for (var i = 0; i < 100; ++i) {
fileData += "hello world!";
}
let file = Cc["@mozilla.org/file/directory_service;1"]
.getService(Ci.nsIDirectoryService)
.QueryInterface(Ci.nsIProperties)
.get("ProfD", Ci.nsIFile);
file.append('file.txt');
file.createUnique(Ci.nsIFile.FILE_TYPE, 0o600);
let outStream = Cc["@mozilla.org/network/file-output-stream;1"]
.createInstance(Ci.nsIFileOutputStream);
outStream.init(file, 0x02 | 0x08 | 0x20, // write, create, truncate
0666, 0);
outStream.write(fileData, fileData.length);
outStream.close();
let fileHandler = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService)
.getProtocolHandler("file")
.QueryInterface(Ci.nsIFileProtocolHandler);
let fileURL = fileHandler.getURLSpecFromFile(file);
info("Opening url: " + fileURL);
let tab = BrowserTestUtils.addTab(gBrowser, fileURL);
let browser = gBrowser.getBrowserForTab(tab);
await BrowserTestUtils.browserLoaded(browser);
let blob = await ContentTask.spawn(browser, file.leafName, function(fileName) {
return new content.window.Promise(resolve => {
content.window.fetch(fileName).then(r => r.blob()).then(blob => resolve(blob));
});
});
ok(blob instanceof File, "We have a file");
is(blob.size, file.fileSize, "The size matches");
is(blob.name, file.leafName, "The name is correct");
file.remove(false);
gBrowser.removeTab(tab);
});