mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 05:11:16 +00:00
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:
parent
d8f3b49815
commit
400093bdb6
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -58,6 +58,8 @@ LOCAL_INCLUDES += [
|
||||
'/netwerk/protocol/http',
|
||||
]
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += [ 'tests/browser.ini' ]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
||||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
|
2
dom/fetch/tests/browser.ini
Normal file
2
dom/fetch/tests/browser.ini
Normal file
@ -0,0 +1,2 @@
|
||||
[DEFAULT]
|
||||
[browser_blobFromFile.js]
|
52
dom/fetch/tests/browser_blobFromFile.js
Normal file
52
dom/fetch/tests/browser_blobFromFile.js
Normal 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);
|
||||
});
|
Loading…
Reference in New Issue
Block a user