From f1f7e8a209b7b020264bb8ca9c0a24cdd750bce2 Mon Sep 17 00:00:00 2001 From: Jari Jalkanen Date: Fri, 15 Jul 2022 09:40:40 +0000 Subject: [PATCH] Bug 1759152 - Expose file system to workers. r=dom-storage-reviewers,jesup,janv Depends on D149983 Differential Revision: https://phabricator.services.mozilla.com/D140862 --- dom/fs/parent/BackgroundFileSystemParent.cpp | 49 ++--- dom/fs/test/common/test_basics.js | 191 +++++++++++++++++- dom/fs/test/mochitest/head.js | 2 +- dom/fs/test/mochitest/test_basics.html | 16 +- .../mochitest/worker/test_basics_worker.js | 9 +- dom/fs/test/xpcshell/test_basics.js | 10 +- dom/quota/StorageManager.cpp | 10 +- dom/quota/moz.build | 1 + dom/quota/test/modules/content/Assert.js | 5 +- .../test/modules/content/WorkerDriver.js | 9 + .../test/modules/content/worker/Assert.js | 8 + dom/quota/test/modules/content/worker/head.js | 35 +++- 12 files changed, 287 insertions(+), 58 deletions(-) diff --git a/dom/fs/parent/BackgroundFileSystemParent.cpp b/dom/fs/parent/BackgroundFileSystemParent.cpp index 87c26a3d00ab..8d49e42fb91b 100644 --- a/dom/fs/parent/BackgroundFileSystemParent.cpp +++ b/dom/fs/parent/BackgroundFileSystemParent.cpp @@ -9,6 +9,7 @@ #include "nsNetCID.h" #include "mozilla/dom/FileSystemTypes.h" +#include "mozilla/dom/quota/QuotaCommon.h" #include "mozilla/ipc/Endpoint.h" #include "mozilla/ipc/FileDescriptorUtils.h" #include "mozilla/Maybe.h" @@ -65,14 +66,19 @@ mozilla::ipc::IPCResult BackgroundFileSystemParent::RecvGetRoot( nsAutoCString origin = quota::QuotaManager::GetOriginFromValidatedPrincipalInfo(mPrincipalInfo); - // This opens the quota manager, which has to be done on PBackground - auto res = - fs::data::FileSystemDataManager::CreateFileSystemDataManager(origin); - QM_TRY(OkIf(res.isErr()), IPC_OK(), [aResolver](const auto&) { - aResolver(fs::FileSystemGetRootResponse(NS_ERROR_UNEXPECTED)); - }); + auto sendBackError = [aResolver](const auto& aRv) { + aResolver(fs::FileSystemGetRootResponse(aRv)); + }; + + fs::EntryId rootId = fs::data::GetRootHandle(origin); + + // This opens the quota manager, which has to be done on PBackground + QM_TRY_UNWRAP( + fs::data::FileSystemDataManager * dataRaw, + fs::data::FileSystemDataManager::CreateFileSystemDataManager(origin), + IPC_OK(), sendBackError); + UniquePtr data(dataRaw); - UniquePtr data(res.unwrap()); nsCOMPtr pbackground = NS_GetCurrentThread(); nsCOMPtr target = @@ -86,22 +92,19 @@ mozilla::ipc::IPCResult BackgroundFileSystemParent::RecvGetRoot( // We'll have to thread-hop back to this thread to respond. We could // just have the create be one-way, then send the actual request on the // new channel, but that's an extra IPC instead. - InvokeAsync( - taskqueue, __func__, - [origin, parentEp = std::move(aParentEp), aResolver, - data = std::move(data), taskqueue, pbackground]() mutable { - RefPtr parent = - new OriginPrivateFileSystemParent(taskqueue); - if (!parentEp.Bind(parent)) { - auto response = fs::FileSystemGetRootResponse(NS_ERROR_FAILURE); - return RootPromise::CreateAndReject(response, __func__); - } - - // Send response back to pbackground to send to child - auto response = - fs::FileSystemGetRootResponse(fs::data::GetRootHandle(origin)); - return RootPromise::CreateAndResolve(response, __func__); - }) + InvokeAsync(taskqueue, __func__, + [origin, parentEp = std::move(aParentEp), aResolver, rootId, + data = std::move(data), taskqueue, pbackground]() mutable { + RefPtr parent = + new OriginPrivateFileSystemParent(taskqueue); + if (!parentEp.Bind(parent)) { + auto response = + fs::FileSystemGetRootResponse(NS_ERROR_FAILURE); + return RootPromise::CreateAndReject(response, __func__); + } + // Send response back to pbackground to send to child + return RootPromise::CreateAndResolve(rootId, __func__); + }) ->Then(GetCurrentSerialEventTarget(), __func__, [aResolver](const RootPromise::ResolveOrRejectValue& aValue) { if (aValue.IsReject()) { diff --git a/dom/fs/test/common/test_basics.js b/dom/fs/test/common/test_basics.js index edfcb72a733c..7171e35f5ba6 100644 --- a/dom/fs/test/common/test_basics.js +++ b/dom/fs/test/common/test_basics.js @@ -3,19 +3,192 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ -async function test1() { - const { nsresult } = await require_module("dom/fs/test/common/nsresult.js"); +exported_symbols.testGetDirectoryDoesNotThrow = async function() { + await navigator.storage.getDirectory(); + + Assert.ok(true, "Should not have thrown"); +}; + +exported_symbols.testGetDirectoryKindIsDirectory = async function() { + const root = await navigator.storage.getDirectory(); + + Assert.equal(root.kind, "directory"); +}; + +exported_symbols.testDirectoryHandleStringConversion = async function() { + const root = await navigator.storage.getDirectory(); + + Assert.equal( + "" + root, + "[object FileSystemDirectoryHandle]", + "Is directoryHandle convertible to string?" + ); +}; + +exported_symbols.testNewDirectoryHandleFromPrototype = async function() { + const root = await navigator.storage.getDirectory(); try { - await navigator.storage.getDirectory(); - + Object.create(root.prototype); Assert.ok(false, "Should have thrown"); - } catch (e) { + } catch (ex) { Assert.ok(true, "Should have thrown"); - Assert.ok( - e.result === nsresult.NS_ERROR_NOT_IMPLEMENTED, - "Threw right result code" + Assert.ok(ex instanceof TypeError, "Threw the right error type"); + } +}; + +exported_symbols.testDirectoryHandleSupportsKeysIterator = async function() { + const root = await navigator.storage.getDirectory(); + + const it = await root.keys(); + Assert.ok(!!it, "Does root support keys iterator?"); +}; + +exported_symbols.testKeysIteratorNextIsCallable = async function() { + const root = await navigator.storage.getDirectory(); + + const it = await root.keys(); + Assert.ok(!!it, "Does root support keys iterator?"); + + try { + await it.next(); + Assert.ok(false, "Should have thrown"); + } catch (ex) { + Assert.ok(true, "Should have thrown"); + Assert.equal( + ex.result, + Cr.NS_ERROR_NOT_IMPLEMENTED, + "Threw the right result code" ); } +}; + +exported_symbols.testDirectoryHandleSupportsValuesIterator = async function() { + const root = await navigator.storage.getDirectory(); + + const it = await root.values(); + Assert.ok(!!it, "Does root support values iterator?"); +}; + +exported_symbols.testValuesIteratorNextIsCallable = async function() { + const root = await navigator.storage.getDirectory(); + + const it = await root.values(); + Assert.ok(!!it, "Does root support values iterator?"); + + try { + await it.next(); + Assert.ok(false, "Should have thrown"); + } catch (ex) { + Assert.ok(true, "Should have thrown"); + Assert.equal( + ex.result, + Cr.NS_ERROR_NOT_IMPLEMENTED, + "Threw the right result code" + ); + } +}; + +exported_symbols.testDirectoryHandleSupportsEntriesIterator = async function() { + const root = await navigator.storage.getDirectory(); + + const it = await root.entries(); + Assert.ok(!!it, "Does root support entries iterator?"); +}; + +exported_symbols.testEntriesIteratorNextIsCallable = async function() { + const root = await navigator.storage.getDirectory(); + + const it = await root.entries(); + Assert.ok(!!it, "Does root support entries iterator?"); + + try { + await it.next(); + Assert.ok(false, "Should have thrown"); + } catch (ex) { + Assert.ok(true, "Should have thrown"); + Assert.equal( + ex.result, + Cr.NS_ERROR_NOT_IMPLEMENTED, + "Threw the right result code" + ); + } +}; + +exported_symbols.testGetFileHandleIsCallable = async function() { + const root = await navigator.storage.getDirectory(); + const allowCreate = { create: true }; + + try { + await root.getFileHandle("name", allowCreate); + + Assert.ok(false, "Should have thrown"); + } catch (ex) { + Assert.ok(true, "Should have thrown"); + Assert.equal( + ex.result, + Cr.NS_ERROR_NOT_IMPLEMENTED, + "Threw the right result code" + ); + } +}; + +exported_symbols.testGetDirectoryHandleIsCallable = async function() { + const root = await navigator.storage.getDirectory(); + const allowCreate = { create: true }; + + try { + await root.getDirectoryHandle("name", allowCreate); + + Assert.ok(false, "Should have thrown"); + } catch (ex) { + Assert.ok(true, "Should have thrown"); + Assert.equal( + ex.result, + Cr.NS_ERROR_NOT_IMPLEMENTED, + "Threw the right result code" + ); + } +}; + +exported_symbols.testRemoveEntryIsCallable = async function() { + const root = await navigator.storage.getDirectory(); + const removeOptions = { recursive: true }; + + try { + await root.removeEntry("root", removeOptions); + + Assert.ok(false, "Should have thrown"); + } catch (ex) { + Assert.ok(true, "Should have thrown"); + Assert.equal( + ex.result, + Cr.NS_ERROR_NOT_IMPLEMENTED, + "Threw the right result code" + ); + } +}; + +exported_symbols.testResolveIsCallable = async function() { + const root = await navigator.storage.getDirectory(); + + try { + await root.resolve(root); + + Assert.ok(false, "Should have thrown"); + } catch (ex) { + Assert.ok(true, "Should have thrown"); + Assert.equal( + ex.result, + Cr.NS_ERROR_NOT_IMPLEMENTED, + "Threw the right result code" + ); + } +}; + +for (const [key, value] of Object.entries(exported_symbols)) { + Object.defineProperty(value, "name", { + value: key, + writable: false, + }); } -exported_symbols.test1 = test1; diff --git a/dom/fs/test/mochitest/head.js b/dom/fs/test/mochitest/head.js index 99a96d48f9d0..5a34ad5830ce 100644 --- a/dom/fs/test/mochitest/head.js +++ b/dom/fs/test/mochitest/head.js @@ -36,7 +36,7 @@ async function run_test_in_worker(script) { // XXX It would be nice if we could call add_setup here (xpcshell-test and // browser-test support it. -add_task(async function() { +add_task(async function setup() { const { setStoragePrefs, clearStoragesForOrigin } = await import( "/tests/dom/quota/test/modules/StorageUtils.js" ); diff --git a/dom/fs/test/mochitest/test_basics.html b/dom/fs/test/mochitest/test_basics.html index 89d3f79a4542..cdada431aa8e 100644 --- a/dom/fs/test/mochitest/test_basics.html +++ b/dom/fs/test/mochitest/test_basics.html @@ -11,11 +11,19 @@ diff --git a/dom/fs/test/mochitest/worker/test_basics_worker.js b/dom/fs/test/mochitest/worker/test_basics_worker.js index 3f1072668837..e4a4958071a1 100644 --- a/dom/fs/test/mochitest/worker/test_basics_worker.js +++ b/dom/fs/test/mochitest/worker/test_basics_worker.js @@ -3,8 +3,9 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ -add_task(async function test_1() { - const { test1 } = await require_module("dom/fs/test/common/test_basics.js"); - - await test1(); +add_task(async function init() { + const testCases = await require_module("dom/fs/test/common/test_basics.js"); + Object.values(testCases).forEach(async testItem => { + add_task(testItem); + }); }); diff --git a/dom/fs/test/xpcshell/test_basics.js b/dom/fs/test/xpcshell/test_basics.js index 3f1072668837..9c70eaceb3f6 100644 --- a/dom/fs/test/xpcshell/test_basics.js +++ b/dom/fs/test/xpcshell/test_basics.js @@ -3,8 +3,12 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ -add_task(async function test_1() { - const { test1 } = await require_module("dom/fs/test/common/test_basics.js"); +add_task(async function init() { + const testSet = "dom/fs/test/common/test_basics.js"; - await test1(); + const testCases = await require_module(testSet); + + Object.values(testCases).forEach(testItem => { + add_task(testItem); + }); }); diff --git a/dom/quota/StorageManager.cpp b/dom/quota/StorageManager.cpp index 22814320adc5..1622802487bd 100644 --- a/dom/quota/StorageManager.cpp +++ b/dom/quota/StorageManager.cpp @@ -10,6 +10,7 @@ #include #include #include "ErrorList.h" +#include "fs/FileSystemRequestHandler.h" #include "MainThreadUtils.h" #include "js/CallArgs.h" #include "js/TypeDecls.h" @@ -754,13 +755,16 @@ already_AddRefed StorageManager::Estimate(ErrorResult& aRv) { } already_AddRefed StorageManager::GetDirectory(ErrorResult& aRv) { - RefPtr promise = Promise::Create(GetParentObject(), aRv); - if (aRv.Failed()) { + MOZ_ASSERT(mOwner); + + RefPtr promise = Promise::Create(mOwner, aRv); + if (NS_WARN_IF(aRv.Failed())) { return nullptr; } - promise->MaybeReject(NS_ERROR_NOT_IMPLEMENTED); + MOZ_ASSERT(promise); + fs::FileSystemRequestHandler{}.GetRoot(promise); return promise.forget(); } diff --git a/dom/quota/moz.build b/dom/quota/moz.build index 4ab51f1a19cc..b3de7e8bc33c 100644 --- a/dom/quota/moz.build +++ b/dom/quota/moz.build @@ -103,4 +103,5 @@ FINAL_LIBRARY = "xul" LOCAL_INCLUDES += [ "/caps", + "/dom/fs/include", ] diff --git a/dom/quota/test/modules/content/Assert.js b/dom/quota/test/modules/content/Assert.js index edcf2c9945b4..e2c8df19c83b 100644 --- a/dom/quota/test/modules/content/Assert.js +++ b/dom/quota/test/modules/content/Assert.js @@ -5,7 +5,6 @@ // Just a wrapper around SimpleTest related functions for now. export const Assert = { - ok(value, message) { - ok(value, message); - }, + ok, + equal: is, }; diff --git a/dom/quota/test/modules/content/WorkerDriver.js b/dom/quota/test/modules/content/WorkerDriver.js index bf0e96f89598..42d1bb66bbc4 100644 --- a/dom/quota/test/modules/content/WorkerDriver.js +++ b/dom/quota/test/modules/content/WorkerDriver.js @@ -20,6 +20,10 @@ export async function runTestInWorker(script) { ok(data.value, data.message); break; + case "is": + is(data.a, data.b, data.message); + break; + case "info": info(data.message); break; @@ -27,6 +31,11 @@ export async function runTestInWorker(script) { case "finish": resolve(); break; + + case "failure": + ok(false, "Worker had a failure: " + data.message); + resolve(); + break; } }; diff --git a/dom/quota/test/modules/content/worker/Assert.js b/dom/quota/test/modules/content/worker/Assert.js index e97abd3f5a29..7c7e2683eaac 100644 --- a/dom/quota/test/modules/content/worker/Assert.js +++ b/dom/quota/test/modules/content/worker/Assert.js @@ -11,4 +11,12 @@ const Assert = { message, }); }, + equal(a, b, message) { + postMessage({ + op: "is", + a, + b, + message, + }); + }, }; diff --git a/dom/quota/test/modules/content/worker/head.js b/dom/quota/test/modules/content/worker/head.js index b649e5706f88..58d4591e4711 100644 --- a/dom/quota/test/modules/content/worker/head.js +++ b/dom/quota/test/modules/content/worker/head.js @@ -10,6 +10,7 @@ const Cr = { function add_task(func) { if (!add_task.tasks) { add_task.tasks = []; + add_task.index = 0; } add_task.tasks.push(func); @@ -20,17 +21,35 @@ addEventListener("message", async function onMessage(event) { postMessage({ op: "info", message }); } + function executeSoon(callback) { + const channel = new MessageChannel(); + channel.port1.postMessage(""); + channel.port2.onmessage = function() { + callback(); + }; + } + + function runNextTest() { + if (add_task.index < add_task.tasks.length) { + const task = add_task.tasks[add_task.index++]; + info("add_task | Entering test " + task.name); + task() + .then(function() { + executeSoon(runNextTest); + info("add_task | Leaving test " + task.name); + }) + .catch(function(ex) { + postMessage({ op: "failure", message: "" + ex }); + }); + } else { + postMessage({ op: "finish" }); + } + } + removeEventListener("message", onMessage); const data = event.data; importScripts(...data); - let task; - while ((task = add_task.tasks.shift())) { - info("add_task | Entering test " + task.name); - await task(); - info("add_task | Leaving test " + task.name); - } - - postMessage({ op: "finish" }); + executeSoon(runNextTest); });