Bug 1676942 - Add IOUtils::ReadJSON r=nika,tcampbell

Differential Revision: https://phabricator.services.mozilla.com/D99154
This commit is contained in:
Barret Rennie 2021-01-18 19:52:32 +00:00
Родитель 1b41576f43
Коммит 7e2b57614d
5 изменённых файлов: 181 добавлений и 2 удалений

Просмотреть файл

@ -42,6 +42,15 @@ namespace IOUtils {
* rejects with a DOMException.
*/
Promise<UTF8String> readUTF8(DOMString path, optional ReadUTF8Options opts = {});
/**
* Read the UTF-8 text file located at |path| and return the contents
* parsed as JSON into a JS value.
*
* @param path An absolute path.
*
* @return Resolves with the contents of the file parsed as JSON.
*/
Promise<any> readJSON(DOMString path, optional ReadUTF8Options opts = {});
/**
* Attempts to safely write |data| to a file at |path|.
*

Просмотреть файл

@ -10,6 +10,7 @@
#include "ErrorList.h"
#include "js/ArrayBuffer.h"
#include "js/JSON.h"
#include "js/Utility.h"
#include "js/experimental/TypedData.h"
#include "jsfriendapi.h"
@ -23,10 +24,10 @@
#include "mozilla/Span.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/TextUtils.h"
#include "mozilla/dom/IOUtilsBinding.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/Unused.h"
#include "mozilla/Utf8.h"
#include "mozilla/dom/IOUtilsBinding.h"
#include "mozilla/dom/Promise.h"
#include "nsCOMPtr.h"
#include "nsError.h"
#include "nsFileStreams.h"
@ -236,6 +237,65 @@ already_AddRefed<Promise> IOUtils::ReadUTF8(GlobalObject& aGlobal,
return promise.forget();
}
/* static */
already_AddRefed<Promise> IOUtils::ReadJSON(GlobalObject& aGlobal,
const nsAString& aPath,
const ReadUTF8Options& aOptions) {
MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
RefPtr<Promise> promise = CreateJSPromise(aGlobal);
if (!promise) {
return nullptr;
}
nsCOMPtr<nsIFile> file = new nsLocalFile();
REJECT_IF_INIT_PATH_FAILED(file, aPath, promise);
RunOnBackgroundThread<JsBuffer>([file, decompress = aOptions.mDecompress]() {
return ReadUTF8Sync(file, decompress);
})
->Then(
GetCurrentSerialEventTarget(), __func__,
[promise, file](JsBuffer&& aBuffer) {
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(promise->GetGlobalObject()))) {
promise->MaybeRejectWithUnknownError(
"Could not initialize JS API");
return;
}
JSContext* cx = jsapi.cx();
JS::Rooted<JSString*> jsonStr(
cx, IOUtils::JsBuffer::IntoString(cx, std::move(aBuffer)));
if (!jsonStr) {
RejectJSPromise(promise, IOError(NS_ERROR_OUT_OF_MEMORY));
return;
}
JS::Rooted<JS::Value> val(cx);
if (!JS_ParseJSON(cx, jsonStr, &val)) {
JS::Rooted<JS::Value> exn(cx);
if (JS_GetPendingException(cx, &exn)) {
JS_ClearPendingException(cx);
promise->MaybeReject(exn);
} else {
RejectJSPromise(
promise,
IOError(NS_ERROR_DOM_UNKNOWN_ERR)
.WithMessage("ParseJSON threw an uncatchable exception "
"while parsing file(%s)",
file->HumanReadablePath().get()));
}
return;
}
promise->MaybeResolve(val);
},
[promise](const IOError& aErr) { RejectJSPromise(promise, aErr); });
return promise.forget();
}
/* static */
already_AddRefed<Promise> IOUtils::Write(GlobalObject& aGlobal,
const nsAString& aPath,

Просмотреть файл

@ -64,6 +64,10 @@ class IOUtils final {
const nsAString& aPath,
const ReadUTF8Options& aOptions);
static already_AddRefed<Promise> ReadJSON(GlobalObject& aGlobal,
const nsAString& aPath,
const ReadUTF8Options& aOptions);
static already_AddRefed<Promise> Write(GlobalObject& aGlobal,
const nsAString& aPath,
const Uint8Array& aData,

Просмотреть файл

@ -8,6 +8,7 @@ support-files =
[test_ioutils_dir_iteration.html]
[test_ioutils_mkdir.html]
[test_ioutils_read_write.html]
[test_ioutils_read_write_json.html]
[test_ioutils_read_write_utf8.html]
[test_ioutils_remove.html]
[test_ioutils_stat_touch.html]

Просмотреть файл

@ -0,0 +1,105 @@
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test the IOUtils file I/O API</title>
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<script src="file_ioutils_test_fixtures.js"></script>
<script>
"use strict";
const { Assert } = ChromeUtils.import("resource://testing-common/Assert.jsm");
const { ObjectUtils } = ChromeUtils.import(
"resource://gre/modules/ObjectUtils.jsm"
);
const OBJECT = {
"foo": [
"bar",
123,
456.789,
true,
false,
null,
],
"bar": {
"baz": {},
},
};
const ARRAY = [1, 2.3, true, false, null, { "foo": "bar" }];
const PRIMITIVES = [123, true, false, "hello, world", null];
add_task(async function read_json() {
const tmpDir = await PathUtils.getTempDir();
const filename = PathUtils.join(tmpDir, "test_ioutils_read_json.tmp");
info("Testing IOUtils.readJSON() with a serialized object...");
await IOUtils.writeUTF8(filename, JSON.stringify(OBJECT));
const readObject = await IOUtils.readJSON(filename);
const parsedObject = JSON.parse(await IOUtils.readUTF8(filename));
ok(ObjectUtils.deepEqual(OBJECT, readObject), "JSON objects should round-trip");
ok(
ObjectUtils.deepEqual(parsedObject, readObject),
"IOUtils.readJSON() equivalent to JSON.parse() for objects"
);
info("Testing IOUtils.readJSON() with a serialized array...");
await IOUtils.writeUTF8(filename, JSON.stringify(ARRAY));
const readArray = await IOUtils.readJSON(filename);
const parsedArray = JSON.parse(await IOUtils.readUTF8(filename));
ok(ObjectUtils.deepEqual(ARRAY, readArray), "JSON arrays should round-trip");
ok(
ObjectUtils.deepEqual(parsedArray, readArray),
"IOUtils.readJSON() equivalent to JSON.parse(IOUtils.readUTF8()) for arrays"
);
info("Testing IOUtils.readJSON() with serialized primitives...");
for (const primitive of PRIMITIVES) {
await IOUtils.writeUTF8(filename, JSON.stringify(primitive));
const readPrimitive = await IOUtils.readJSON(filename);
const parsedPrimitive = JSON.parse(await IOUtils.readUTF8(filename));
ok(primitive === readPrimitive, `JSON primitive ${primitive} should round trip`);
ok(
readPrimitive === parsedPrimitive,
`${readPrimitive} === ${parsedPrimitive} -- IOUtils.readJSON() equivalent to JSON.parse() for primitive`
);
}
info("Testing IOUtils.readJSON() with a file that does not exist...");
const notExistsFilename = PathUtils.join(tmpDir, "test_ioutils_read_json_not_exists.tmp");
ok(!await IOUtils.exists(notExistsFilename), `${notExistsFilename} should not exist`);
await Assert.rejects(
IOUtils.readJSON(notExistsFilename),
/NotFoundError: Could not open the file at/,
"IOUtils::readJSON rejects when file does not exist"
);
info("Testing IOUtils.readJSON() with a file that does not contain JSON");
const invalidFilename = PathUtils.join(tmpDir, "test_ioutils_read_json_invalid.tmp");
await IOUtils.writeUTF8(invalidFilename, ":)");
await Assert.rejects(
IOUtils.readJSON(invalidFilename),
/SyntaxError: JSON\.parse/,
"IOUTils::readJSON rejects when the file contains invalid JSON"
);
await cleanup(filename, invalidFilename);
});
</script>
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test"></pre>
</body>
</html>