mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-29 15:52:07 +00:00
Bug 934367 - Implement createFile for Directory. r=dhylands
This commit is contained in:
parent
1e41f0c5d5
commit
dea41c84f3
@ -29,3 +29,4 @@ support-files = devicestorage_common.js
|
||||
[test_fs_createDirectory.html]
|
||||
[test_fs_get.html]
|
||||
[test_fs_remove.html]
|
||||
[test_fs_createFile.html]
|
||||
|
@ -31,6 +31,26 @@ function randomFilename(l) {
|
||||
return result;
|
||||
}
|
||||
|
||||
function getRandomBuffer() {
|
||||
var size = 1024;
|
||||
var buffer = new ArrayBuffer(size);
|
||||
var view = new Uint8Array(buffer);
|
||||
for (var i = 0; i < size; i++) {
|
||||
view[i] = parseInt(Math.random() * 255);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
function createRandomBlob(mime) {
|
||||
let size = 1024;
|
||||
let buffer = new ArrayBuffer(size);
|
||||
let view = new Uint8Array(buffer);
|
||||
for (let i = 0; i < size; i++) {
|
||||
view[i] = parseInt(Math.random() * 255);
|
||||
}
|
||||
return blob = new Blob([buffer], {type: mime});
|
||||
}
|
||||
|
||||
let MockPermissionPrompt = SpecialPowers.MockPermissionPrompt;
|
||||
MockPermissionPrompt.init();
|
||||
|
||||
@ -88,6 +108,34 @@ function TestGet(iframe, data) {
|
||||
}, cbError);
|
||||
}
|
||||
|
||||
function TestCreateFile(iframe, data) {
|
||||
function cbError(e) {
|
||||
is(e.name, "SecurityError", "[TestCreateFile] Should fire a SecurityError for type " + data.type);
|
||||
is(data.shouldPass, false, "[TestCreateFile] Error callback was called for type " + data.type + '. Error: ' + e.name);
|
||||
testComplete(iframe, data);
|
||||
}
|
||||
|
||||
let storage = iframe.contentDocument.defaultView.navigator.getDeviceStorage(data.type);
|
||||
isnot(storage, null, "[TestCreateFile] Should be able to get storage object for " + data.type);
|
||||
|
||||
if (!storage) {
|
||||
testComplete(iframe, data);
|
||||
return;
|
||||
}
|
||||
|
||||
storage.getRoot().then(function(root) {
|
||||
ok(true, "[TestCreateFile] Success callback of getRoot was called for type " + data.type);
|
||||
let filename = randomFilename(100) + data.fileExtension;
|
||||
root.createFile(filename, {
|
||||
data: createRandomBlob(data.mimeType),
|
||||
ifExists: "replace"
|
||||
}).then(function() {
|
||||
is(data.shouldPass, true, "[TestCreateFile] Success callback was called for type " + data.type);
|
||||
testComplete(iframe, data);
|
||||
}, cbError);
|
||||
}, cbError);
|
||||
}
|
||||
|
||||
function TestRemove(iframe, data) {
|
||||
function cbError(e) {
|
||||
is(e.name, "SecurityError", "[TestRemove] Should fire a SecurityError for type " + data.type);
|
||||
@ -372,6 +420,182 @@ let gData = [
|
||||
test: TestCreateDirectory
|
||||
},
|
||||
|
||||
// Directory#createFile
|
||||
|
||||
// Web applications with no permissions
|
||||
{
|
||||
type: 'pictures',
|
||||
mimeType: 'image/png',
|
||||
shouldPass: false,
|
||||
fileExtension: '.png',
|
||||
test: TestCreateFile
|
||||
},
|
||||
{
|
||||
type: 'videos',
|
||||
mimeType: 'video/ogv',
|
||||
shouldPass: false,
|
||||
fileExtension: '.ogv',
|
||||
test: TestCreateFile
|
||||
},
|
||||
{
|
||||
type: 'videos',
|
||||
mimeType: 'video/ogg',
|
||||
shouldPass: false,
|
||||
fileExtension: '.ogg',
|
||||
test: TestCreateFile
|
||||
},
|
||||
{
|
||||
type: 'music',
|
||||
mimeType: 'audio/ogg',
|
||||
shouldPass: false,
|
||||
fileExtension: '.ogg',
|
||||
test: TestCreateFile
|
||||
},
|
||||
{
|
||||
type: 'music',
|
||||
mimeType: 'audio/ogg',
|
||||
shouldPass: false,
|
||||
fileExtension: '.txt',
|
||||
test: TestCreateFile
|
||||
},
|
||||
{
|
||||
type: 'sdcard',
|
||||
mimeType: 'text/plain',
|
||||
shouldPass: false,
|
||||
fileExtension: '.txt',
|
||||
test: TestCreateFile
|
||||
},
|
||||
|
||||
// Web applications with permission granted
|
||||
{
|
||||
type: 'pictures',
|
||||
mimeType: 'image/png',
|
||||
shouldPass: true,
|
||||
fileExtension: '.png',
|
||||
|
||||
permissions: ["device-storage:pictures"],
|
||||
|
||||
test: TestCreateFile
|
||||
},
|
||||
{
|
||||
type: 'videos',
|
||||
mimeType: 'video/ogv',
|
||||
shouldPass: true,
|
||||
fileExtension: '.ogv',
|
||||
|
||||
permissions: ["device-storage:videos"],
|
||||
|
||||
test: TestCreateFile
|
||||
},
|
||||
{
|
||||
type: 'videos',
|
||||
mimeType: 'video/ogg',
|
||||
shouldPass: true,
|
||||
fileExtension: '.ogg',
|
||||
|
||||
permissions: ["device-storage:videos"],
|
||||
|
||||
test: TestCreateFile
|
||||
},
|
||||
{
|
||||
type: 'music',
|
||||
mimeType: 'audio/ogg',
|
||||
shouldPass: true,
|
||||
fileExtension: '.ogg',
|
||||
|
||||
permissions: ["device-storage:music"],
|
||||
|
||||
test: TestCreateFile
|
||||
},
|
||||
{
|
||||
type: 'music',
|
||||
mimeType: 'audio/ogg',
|
||||
shouldPass: false,
|
||||
fileExtension: '.txt',
|
||||
|
||||
permissions: ["device-storage:music"],
|
||||
|
||||
test: TestCreateFile
|
||||
},
|
||||
{
|
||||
type: 'sdcard',
|
||||
mimeType: 'text/plain',
|
||||
shouldPass: true,
|
||||
fileExtension: '.txt',
|
||||
|
||||
permissions: ["device-storage:sdcard"],
|
||||
|
||||
test: TestCreateFile
|
||||
},
|
||||
|
||||
// Certified application with permision granted
|
||||
{
|
||||
type: 'pictures',
|
||||
mimeType: 'image/png',
|
||||
shouldPass: true,
|
||||
fileExtension: '.png',
|
||||
|
||||
app: "https://example.com/manifest_cert.webapp",
|
||||
permissions: ["device-storage:pictures"],
|
||||
|
||||
test: TestCreateFile
|
||||
},
|
||||
{
|
||||
type: 'videos',
|
||||
mimeType: 'video/ogv',
|
||||
shouldPass: true,
|
||||
fileExtension: '.ogv',
|
||||
|
||||
app: "https://example.com/manifest_cert.webapp",
|
||||
permissions: ["device-storage:videos"],
|
||||
|
||||
test: TestCreateFile
|
||||
},
|
||||
{
|
||||
type: 'videos',
|
||||
mimeType: 'video/ogg',
|
||||
shouldPass: true,
|
||||
fileExtension: '.ogg',
|
||||
|
||||
app: "https://example.com/manifest_cert.webapp",
|
||||
permissions: ["device-storage:videos"],
|
||||
|
||||
test: TestCreateFile
|
||||
},
|
||||
{
|
||||
type: 'music',
|
||||
mimeType: 'audio/ogg',
|
||||
shouldPass: true,
|
||||
fileExtension: '.ogg',
|
||||
|
||||
app: "https://example.com/manifest_cert.webapp",
|
||||
permissions: ["device-storage:music"],
|
||||
|
||||
test: TestCreateFile
|
||||
},
|
||||
{
|
||||
type: 'music',
|
||||
mimeType: 'audio/ogg',
|
||||
shouldPass: false,
|
||||
fileExtension: '.txt',
|
||||
|
||||
app: "https://example.com/manifest_cert.webapp",
|
||||
permissions: ["device-storage:music"],
|
||||
|
||||
test: TestCreateFile
|
||||
},
|
||||
{
|
||||
type: 'sdcard',
|
||||
mimeType: 'text/plain',
|
||||
shouldPass: true,
|
||||
fileExtension: '.txt',
|
||||
|
||||
app: "https://example.com/manifest_cert.webapp",
|
||||
permissions: ["device-storage:sdcard"],
|
||||
|
||||
test: TestCreateFile
|
||||
},
|
||||
|
||||
// Directory#remove
|
||||
|
||||
// Web applications with no permissions
|
||||
|
132
dom/devicestorage/test/test_fs_createFile.html
Normal file
132
dom/devicestorage/test/test_fs_createFile.html
Normal file
@ -0,0 +1,132 @@
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!DOCTYPE HTML>
|
||||
<html> <!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=910412
|
||||
-->
|
||||
<head>
|
||||
<title>Test createDirectory of the FileSystem API for device storage</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="devicestorage_common.js"></script>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=910412">Mozilla Bug 910412</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="application/javascript;version=1.7">
|
||||
|
||||
devicestorage_setup();
|
||||
|
||||
let gTestCount = 0;
|
||||
let gFileReader = new FileReader();
|
||||
let gRoot;
|
||||
|
||||
function str2array(str) {
|
||||
let strlen = str.length;
|
||||
let buf = new ArrayBuffer(strlen);
|
||||
let bufView = new Uint8Array(buf);
|
||||
for (let i=0; i < strlen; i++) {
|
||||
bufView[i] = str.charCodeAt(i);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
function array2str(data) {
|
||||
return String.fromCharCode.apply(String, new Uint8Array(data));
|
||||
}
|
||||
|
||||
let gTestCases = [
|
||||
// Create with string data.
|
||||
{
|
||||
text: "My name is Yuan.",
|
||||
get data() { return this.text; },
|
||||
shouldPass: true,
|
||||
mode: "replace"
|
||||
},
|
||||
|
||||
// Create with array buffer data.
|
||||
{
|
||||
text: "I'm from Kunming.",
|
||||
get data() { return str2array(this.text); },
|
||||
shouldPass: true,
|
||||
mode: "replace"
|
||||
},
|
||||
|
||||
// Create with array buffer view data.
|
||||
{
|
||||
text: "Kunming is in Yunnan province of China.",
|
||||
get data() { return Uint8Array(str2array(this.text)); },
|
||||
shouldPass: true,
|
||||
mode: "replace"
|
||||
},
|
||||
|
||||
// Create with blob data.
|
||||
{
|
||||
text: "Kunming is in Yunnan province of China.",
|
||||
get data() { return new Blob([this.text], {type: 'image/png'}); },
|
||||
shouldPass: true,
|
||||
mode: "replace"
|
||||
},
|
||||
|
||||
// Don't overwrite existing file.
|
||||
{
|
||||
data: null,
|
||||
shouldPass: false,
|
||||
mode: "fail"
|
||||
}
|
||||
];
|
||||
|
||||
function next() {
|
||||
if (gTestCount >= gTestCases.length) {
|
||||
devicestorage_cleanup();
|
||||
return;
|
||||
}
|
||||
let c = gTestCases[gTestCount++];
|
||||
gRoot.createFile("text.png", {
|
||||
data: c.data,
|
||||
ifExists: c.mode
|
||||
}).then(function(file) {
|
||||
is(c.shouldPass, true, "[" + gTestCount + "] Success callback was called for createFile.");
|
||||
if (!c.shouldPass) {
|
||||
SimpleTest.executeSoon(next);
|
||||
return;
|
||||
}
|
||||
// Check the file content.
|
||||
gFileReader.readAsArrayBuffer(file);
|
||||
gFileReader.onload = function(e) {
|
||||
ab = e.target.result;
|
||||
is(array2str(e.target.result), c.text, "[" + gTestCount + "] Wrong values.");
|
||||
SimpleTest.executeSoon(next);
|
||||
};
|
||||
}, function(e) {
|
||||
is(c.shouldPass, false, "[" + gTestCount + "] Error callback was called for createFile.");
|
||||
SimpleTest.executeSoon(next);
|
||||
});
|
||||
}
|
||||
|
||||
ok(navigator.getDeviceStorage, "Should have getDeviceStorage.");
|
||||
|
||||
let storage = navigator.getDeviceStorage("pictures");
|
||||
ok(storage, "Should have gotten a storage.");
|
||||
|
||||
// Get the root directory
|
||||
storage.getRoot().then(function(dir) {
|
||||
ok(dir, "Should have gotten the root directory.");
|
||||
gRoot = dir;
|
||||
next();
|
||||
}, function(e) {
|
||||
ok(false, e.name + " error should not arrive here!");
|
||||
devicestorage_cleanup();
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
|
321
dom/filesystem/CreateFileTask.cpp
Normal file
321
dom/filesystem/CreateFileTask.cpp
Normal file
@ -0,0 +1,321 @@
|
||||
/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
|
||||
/* vim: set ts=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 "CreateFileTask.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "DOMError.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/dom/FileSystemBase.h"
|
||||
#include "mozilla/dom/FileSystemUtils.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "nsDOMFile.h"
|
||||
#include "nsIFile.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsStringGlue.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
uint32_t CreateFileTask::sOutputBufferSize = 0;
|
||||
|
||||
CreateFileTask::CreateFileTask(FileSystemBase* aFileSystem,
|
||||
const nsAString& aPath,
|
||||
nsIDOMBlob* aBlobData,
|
||||
InfallibleTArray<uint8_t>& aArrayData,
|
||||
bool replace)
|
||||
: FileSystemTaskBase(aFileSystem)
|
||||
, mTargetRealPath(aPath)
|
||||
, mBlobData(aBlobData)
|
||||
, mReplace(replace)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
|
||||
MOZ_ASSERT(aFileSystem);
|
||||
GetOutputBufferSize();
|
||||
if (mBlobData) {
|
||||
nsresult rv = mBlobData->GetInternalStream(getter_AddRefs(mBlobStream));
|
||||
NS_WARN_IF(NS_FAILED(rv));
|
||||
}
|
||||
mArrayData.SwapElements(aArrayData);
|
||||
nsCOMPtr<nsIGlobalObject> globalObject =
|
||||
do_QueryInterface(aFileSystem->GetWindow());
|
||||
if (!globalObject) {
|
||||
return;
|
||||
}
|
||||
mPromise = new Promise(globalObject);
|
||||
}
|
||||
|
||||
CreateFileTask::CreateFileTask(FileSystemBase* aFileSystem,
|
||||
const FileSystemCreateFileParams& aParam,
|
||||
FileSystemRequestParent* aParent)
|
||||
: FileSystemTaskBase(aFileSystem, aParam, aParent)
|
||||
, mReplace(false)
|
||||
{
|
||||
MOZ_ASSERT(FileSystemUtils::IsParentProcess(),
|
||||
"Only call from parent process!");
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
|
||||
MOZ_ASSERT(aFileSystem);
|
||||
GetOutputBufferSize();
|
||||
|
||||
mTargetRealPath = aParam.realPath();
|
||||
|
||||
mReplace = aParam.replace();
|
||||
|
||||
auto& data = aParam.data();
|
||||
|
||||
if (data.type() == FileSystemFileDataValue::TArrayOfuint8_t) {
|
||||
mArrayData = data;
|
||||
return;
|
||||
}
|
||||
|
||||
BlobParent* bp = static_cast<BlobParent*>(static_cast<PBlobParent*>(data));
|
||||
mBlobData = bp->GetBlob();
|
||||
MOZ_ASSERT(mBlobData, "mBlobData should not be null.");
|
||||
nsresult rv = mBlobData->GetInternalStream(getter_AddRefs(mBlobStream));
|
||||
NS_WARN_IF(NS_FAILED(rv));
|
||||
}
|
||||
|
||||
CreateFileTask::~CreateFileTask()
|
||||
{
|
||||
MOZ_ASSERT(!mPromise || NS_IsMainThread(),
|
||||
"mPromise should be released on main thread!");
|
||||
if (mBlobStream) {
|
||||
mBlobStream->Close();
|
||||
}
|
||||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
CreateFileTask::GetPromise()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
|
||||
return nsRefPtr<Promise>(mPromise).forget();
|
||||
}
|
||||
|
||||
FileSystemParams
|
||||
CreateFileTask::GetRequestParams(const nsString& aFileSystem) const
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
|
||||
FileSystemCreateFileParams param;
|
||||
param.filesystem() = aFileSystem;
|
||||
param.realPath() = mTargetRealPath;
|
||||
param.replace() = mReplace;
|
||||
if (mBlobData) {
|
||||
BlobChild* actor
|
||||
= ContentChild::GetSingleton()->GetOrCreateActorForBlob(mBlobData);
|
||||
if (actor) {
|
||||
param.data() = actor;
|
||||
}
|
||||
} else {
|
||||
param.data() = mArrayData;
|
||||
}
|
||||
return param;
|
||||
}
|
||||
|
||||
FileSystemResponseValue
|
||||
CreateFileTask::GetSuccessRequestResult() const
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
|
||||
BlobParent* actor = GetBlobParent(mTargetFile);
|
||||
if (!actor) {
|
||||
return FileSystemErrorResponse(NS_ERROR_DOM_FILESYSTEM_UNKNOWN_ERR);
|
||||
}
|
||||
FileSystemFileResponse response;
|
||||
response.blobParent() = actor;
|
||||
return response;
|
||||
}
|
||||
|
||||
void
|
||||
CreateFileTask::SetSuccessRequestResult(const FileSystemResponseValue& aValue)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
|
||||
FileSystemFileResponse r = aValue;
|
||||
BlobChild* actor = static_cast<BlobChild*>(r.blobChild());
|
||||
nsCOMPtr<nsIDOMBlob> blob = actor->GetBlob();
|
||||
mTargetFile = do_QueryInterface(blob);
|
||||
}
|
||||
|
||||
nsresult
|
||||
CreateFileTask::Work()
|
||||
{
|
||||
class AutoClose
|
||||
{
|
||||
public:
|
||||
AutoClose(nsIOutputStream* aStream)
|
||||
: mStream(aStream)
|
||||
{
|
||||
MOZ_ASSERT(aStream);
|
||||
}
|
||||
|
||||
~AutoClose()
|
||||
{
|
||||
mStream->Close();
|
||||
}
|
||||
private:
|
||||
nsCOMPtr<nsIOutputStream> mStream;
|
||||
};
|
||||
|
||||
MOZ_ASSERT(FileSystemUtils::IsParentProcess(),
|
||||
"Only call from parent process!");
|
||||
MOZ_ASSERT(!NS_IsMainThread(), "Only call on worker thread!");
|
||||
|
||||
if (mFileSystem->IsShutdown()) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIFile> file = mFileSystem->GetLocalFile(mTargetRealPath);
|
||||
if (!file) {
|
||||
return NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR;
|
||||
}
|
||||
|
||||
if (!mFileSystem->IsSafeFile(file)) {
|
||||
return NS_ERROR_DOM_SECURITY_ERR;
|
||||
}
|
||||
|
||||
bool exists = false;
|
||||
nsresult rv = file->Exists(&exists);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (exists) {
|
||||
bool isFile = false;
|
||||
rv = file->IsFile(&isFile);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (!isFile) {
|
||||
return NS_ERROR_DOM_FILESYSTEM_TYPE_MISMATCH_ERR;
|
||||
}
|
||||
|
||||
if (!mReplace) {
|
||||
return NS_ERROR_DOM_FILESYSTEM_PATH_EXISTS_ERR;
|
||||
}
|
||||
|
||||
// Remove the old file before creating.
|
||||
rv = file->Remove(false);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 0600);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIOutputStream> outputStream;
|
||||
rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), file);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
AutoClose acOutputStream(outputStream);
|
||||
|
||||
nsCOMPtr<nsIOutputStream> bufferedOutputStream;
|
||||
rv = NS_NewBufferedOutputStream(getter_AddRefs(bufferedOutputStream),
|
||||
outputStream,
|
||||
sOutputBufferSize);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
AutoClose acBufferedOutputStream(bufferedOutputStream);
|
||||
|
||||
if (mBlobStream) {
|
||||
// Write the file content from blob data.
|
||||
|
||||
uint64_t bufSize = 0;
|
||||
rv = mBlobStream->Available(&bufSize);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
while (bufSize && !mFileSystem->IsShutdown()) {
|
||||
uint32_t written = 0;
|
||||
uint32_t writeSize = bufSize < UINT32_MAX ? bufSize : UINT32_MAX;
|
||||
rv = bufferedOutputStream->WriteFrom(mBlobStream, writeSize, &written);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
bufSize -= written;
|
||||
}
|
||||
|
||||
mBlobStream->Close();
|
||||
mBlobStream = nullptr;
|
||||
|
||||
if (mFileSystem->IsShutdown()) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
mTargetFile = new nsDOMFileFile(file);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Write file content from array data.
|
||||
|
||||
uint32_t written;
|
||||
rv = bufferedOutputStream->Write(
|
||||
reinterpret_cast<char*>(mArrayData.Elements()),
|
||||
mArrayData.Length(),
|
||||
&written);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (mArrayData.Length() != written) {
|
||||
return NS_ERROR_DOM_FILESYSTEM_UNKNOWN_ERR;
|
||||
}
|
||||
|
||||
mTargetFile = new nsDOMFileFile(file);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
CreateFileTask::HandlerCallback()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
|
||||
if (mFileSystem->IsShutdown()) {
|
||||
mPromise = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasError()) {
|
||||
nsRefPtr<DOMError> domError = new DOMError(mFileSystem->GetWindow(),
|
||||
mErrorValue);
|
||||
mPromise->MaybeReject(domError);
|
||||
mPromise = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
mPromise->MaybeResolve(mTargetFile);
|
||||
mPromise = nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
CreateFileTask::GetPermissionAccessType(nsCString& aAccess) const
|
||||
{
|
||||
if (mReplace) {
|
||||
aAccess.AssignLiteral("write");
|
||||
return;
|
||||
}
|
||||
|
||||
aAccess.AssignLiteral("create");
|
||||
}
|
||||
|
||||
void
|
||||
CreateFileTask::GetOutputBufferSize() const
|
||||
{
|
||||
if (sOutputBufferSize || !FileSystemUtils::IsParentProcess()) {
|
||||
return;
|
||||
}
|
||||
sOutputBufferSize =
|
||||
mozilla::Preferences::GetUint("dom.filesystem.outputBufferSize", 4096 * 4);
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
76
dom/filesystem/CreateFileTask.h
Normal file
76
dom/filesystem/CreateFileTask.h
Normal file
@ -0,0 +1,76 @@
|
||||
/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
|
||||
/* vim: set ts=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/. */
|
||||
|
||||
#ifndef mozilla_dom_CreateFileTask_h
|
||||
#define mozilla_dom_CreateFileTask_h
|
||||
|
||||
#include "mozilla/dom/FileSystemTaskBase.h"
|
||||
#include "nsAutoPtr.h"
|
||||
|
||||
class nsIDOMBlob;
|
||||
class nsIInputStream;
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class Promise;
|
||||
|
||||
class CreateFileTask MOZ_FINAL
|
||||
: public FileSystemTaskBase
|
||||
{
|
||||
public:
|
||||
CreateFileTask(FileSystemBase* aFileSystem,
|
||||
const nsAString& aPath,
|
||||
nsIDOMBlob* aBlobData,
|
||||
InfallibleTArray<uint8_t>& aArrayData,
|
||||
bool replace);
|
||||
CreateFileTask(FileSystemBase* aFileSystem,
|
||||
const FileSystemCreateFileParams& aParam,
|
||||
FileSystemRequestParent* aParent);
|
||||
|
||||
virtual
|
||||
~CreateFileTask();
|
||||
|
||||
already_AddRefed<Promise>
|
||||
GetPromise();
|
||||
|
||||
virtual void
|
||||
GetPermissionAccessType(nsCString& aAccess) const MOZ_OVERRIDE;
|
||||
|
||||
protected:
|
||||
virtual FileSystemParams
|
||||
GetRequestParams(const nsString& aFileSystem) const MOZ_OVERRIDE;
|
||||
|
||||
virtual FileSystemResponseValue
|
||||
GetSuccessRequestResult() const MOZ_OVERRIDE;
|
||||
|
||||
virtual void
|
||||
SetSuccessRequestResult(const FileSystemResponseValue& aValue) MOZ_OVERRIDE;
|
||||
|
||||
virtual nsresult
|
||||
Work() MOZ_OVERRIDE;
|
||||
|
||||
virtual void
|
||||
HandlerCallback() MOZ_OVERRIDE;
|
||||
|
||||
private:
|
||||
void
|
||||
GetOutputBufferSize() const;
|
||||
|
||||
static uint32_t sOutputBufferSize;
|
||||
nsRefPtr<Promise> mPromise;
|
||||
nsString mTargetRealPath;
|
||||
nsCOMPtr<nsIDOMBlob> mBlobData;
|
||||
nsCOMPtr<nsIInputStream> mBlobStream;
|
||||
InfallibleTArray<uint8_t> mArrayData;
|
||||
bool mReplace;
|
||||
nsCOMPtr<nsIDOMFile> mTargetFile;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_dom_CreateFileTask_h
|
@ -7,6 +7,7 @@
|
||||
#include "mozilla/dom/Directory.h"
|
||||
|
||||
#include "CreateDirectoryTask.h"
|
||||
#include "CreateFileTask.h"
|
||||
#include "FileSystemPermissionRequest.h"
|
||||
#include "GetFileOrDirectoryTask.h"
|
||||
#include "RemoveTask.h"
|
||||
@ -24,6 +25,11 @@
|
||||
#ifdef CreateDirectory
|
||||
#undef CreateDirectory
|
||||
#endif
|
||||
// Undefine the macro of CreateFile to avoid Directory#CreateFile being replaced
|
||||
// by Directory#CreateFileW.
|
||||
#ifdef CreateFile
|
||||
#undef CreateFile
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
@ -88,6 +94,44 @@ Directory::GetName(nsString& aRetval) const
|
||||
mPath.RFindChar(FileSystemUtils::kSeparatorChar) + 1);
|
||||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
Directory::CreateFile(const nsAString& aPath, const CreateFileOptions& aOptions)
|
||||
{
|
||||
nsresult error = NS_OK;
|
||||
nsString realPath;
|
||||
nsRefPtr<nsIDOMBlob> blobData;
|
||||
InfallibleTArray<uint8_t> arrayData;
|
||||
bool replace = (aOptions.mIfExists == CreateIfExistsMode::Replace);
|
||||
|
||||
// Get the file content.
|
||||
if (aOptions.mData.WasPassed()) {
|
||||
auto& data = aOptions.mData.Value();
|
||||
if (data.IsString()) {
|
||||
NS_ConvertUTF16toUTF8 str(data.GetAsString());
|
||||
arrayData.AppendElements(reinterpret_cast<const uint8_t *>(str.get()),
|
||||
str.Length());
|
||||
} else if (data.IsArrayBuffer()) {
|
||||
ArrayBuffer& buffer = data.GetAsArrayBuffer();
|
||||
arrayData.AppendElements(buffer.Data(), buffer.Length());
|
||||
} else if (data.IsArrayBufferView()){
|
||||
ArrayBufferView& view = data.GetAsArrayBufferView();
|
||||
arrayData.AppendElements(view.Data(), view.Length());
|
||||
} else {
|
||||
blobData = data.GetAsBlob();
|
||||
}
|
||||
}
|
||||
|
||||
if (!DOMPathToRealPath(aPath, realPath)) {
|
||||
error = NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR;
|
||||
}
|
||||
|
||||
nsRefPtr<CreateFileTask> task = new CreateFileTask(mFileSystem, realPath,
|
||||
blobData, arrayData, replace);
|
||||
task->SetError(error);
|
||||
FileSystemPermissionRequest::RequestForTask(task);
|
||||
return task->GetPromise();
|
||||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
Directory::CreateDirectory(const nsAString& aPath)
|
||||
{
|
||||
|
@ -21,10 +21,16 @@
|
||||
#ifdef CreateDirectory
|
||||
#undef CreateDirectory
|
||||
#endif
|
||||
// Undefine the macro of CreateFile to avoid Directory#CreateFile being replaced
|
||||
// by Directory#CreateFileW.
|
||||
#ifdef CreateFile
|
||||
#undef CreateFile
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class CreateFileOptions;
|
||||
class FileSystemBase;
|
||||
class Promise;
|
||||
class StringOrFileOrDirectory;
|
||||
@ -55,6 +61,9 @@ public:
|
||||
void
|
||||
GetName(nsString& aRetval) const;
|
||||
|
||||
already_AddRefed<Promise>
|
||||
CreateFile(const nsAString& aPath, const CreateFileOptions& aOptions);
|
||||
|
||||
already_AddRefed<Promise>
|
||||
CreateDirectory(const nsAString& aPath);
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include "mozilla/dom/FileSystemRequestParent.h"
|
||||
|
||||
#include "CreateDirectoryTask.h"
|
||||
#include "CreateFileTask.h"
|
||||
#include "GetFileOrDirectoryTask.h"
|
||||
#include "RemoveTask.h"
|
||||
|
||||
@ -40,6 +41,7 @@ FileSystemRequestParent::Dispatch(ContentParent* aParent,
|
||||
switch (aParams.type()) {
|
||||
|
||||
FILESYSTEM_REQUEST_PARENT_DISPATCH_ENTRY(CreateDirectory)
|
||||
FILESYSTEM_REQUEST_PARENT_DISPATCH_ENTRY(CreateFile)
|
||||
FILESYSTEM_REQUEST_PARENT_DISPATCH_ENTRY(GetFileOrDirectory)
|
||||
FILESYSTEM_REQUEST_PARENT_DISPATCH_ENTRY(Remove)
|
||||
|
||||
|
@ -15,6 +15,7 @@ EXPORTS.mozilla.dom += [
|
||||
|
||||
SOURCES += [
|
||||
'CreateDirectoryTask.cpp',
|
||||
'CreateFileTask.cpp',
|
||||
'DeviceStorageFileSystem.cpp',
|
||||
'Directory.cpp',
|
||||
'FileSystemBase.cpp',
|
||||
|
@ -201,6 +201,20 @@ struct FileSystemCreateDirectoryParams
|
||||
nsString realPath;
|
||||
};
|
||||
|
||||
union FileSystemFileDataValue
|
||||
{
|
||||
uint8_t[];
|
||||
PBlob;
|
||||
};
|
||||
|
||||
struct FileSystemCreateFileParams
|
||||
{
|
||||
nsString filesystem;
|
||||
nsString realPath;
|
||||
FileSystemFileDataValue data;
|
||||
bool replace;
|
||||
};
|
||||
|
||||
struct FileSystemGetFileOrDirectoryParams
|
||||
{
|
||||
nsString filesystem;
|
||||
@ -224,6 +238,7 @@ struct FileSystemRemoveParams
|
||||
union FileSystemParams
|
||||
{
|
||||
FileSystemCreateDirectoryParams;
|
||||
FileSystemCreateFileParams;
|
||||
FileSystemGetFileOrDirectoryParams;
|
||||
FileSystemRemoveParams;
|
||||
};
|
||||
|
@ -21,6 +21,26 @@ interface Directory {
|
||||
*/
|
||||
readonly attribute DOMString name;
|
||||
|
||||
/*
|
||||
* Creates a new file or replaces an existing file with given data. The file
|
||||
* should be a descendent of current directory.
|
||||
*
|
||||
* @param path The relative path of the new file to current directory.
|
||||
* @param options It has two optional properties, 'ifExists' and 'data'.
|
||||
* If 'ifExists' is 'fail' and the path already exists, createFile must fail;
|
||||
* If 'ifExists' is 'replace', the path already exists, and is a file, create
|
||||
* a new file to replace the existing one;
|
||||
* If 'ifExists' is 'replace', the path already exists, but is a directory,
|
||||
* createFile must fail.
|
||||
* Otherwise, if no other error occurs, createFile will create a new file.
|
||||
* The 'data' property contains the new file's content.
|
||||
* @return If succeeds, the promise is resolved with the new created
|
||||
* File object. Otherwise, rejected with a DOM error.
|
||||
*/
|
||||
[NewObject]
|
||||
// Promise<File>
|
||||
Promise createFile(DOMString path, optional CreateFileOptions options);
|
||||
|
||||
/*
|
||||
* Creates a descendent directory. This method will create any intermediate
|
||||
* directories specified by the path segments.
|
||||
@ -77,3 +97,9 @@ interface Directory {
|
||||
Promise removeDeep((DOMString or File or Directory) path);
|
||||
};
|
||||
|
||||
enum CreateIfExistsMode { "replace", "fail" };
|
||||
|
||||
dictionary CreateFileOptions {
|
||||
CreateIfExistsMode ifExists = "fail";
|
||||
(DOMString or Blob or ArrayBuffer or ArrayBufferView) data;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user