[gjs/esm/dynamic-imports: 1/5] Implement loadResourceOrFileAsync for dynamic imports
- From: Philip Chimento <pchimento src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gjs/esm/dynamic-imports: 1/5] Implement loadResourceOrFileAsync for dynamic imports
- Date: Mon, 8 Feb 2021 01:46:37 +0000 (UTC)
commit 405c3dcf691d88106309e23f2cca0655cc422e5b
Author: Philip Chimento <philip chimento gmail com>
Date: Sat Dec 12 21:59:26 2020 -0800
Implement loadResourceOrFileAsync for dynamic imports
gjs/global.cpp | 2 +
gjs/internal.cpp | 146 +++++++++++++++++++++++++++++++++++++++++
modules/internal/.eslintrc.yml | 1 +
3 files changed, 149 insertions(+)
---
diff --git a/gjs/global.cpp b/gjs/global.cpp
index 6e0dac3b..95a39399 100644
--- a/gjs/global.cpp
+++ b/gjs/global.cpp
@@ -270,6 +270,8 @@ class GjsInternalGlobal : GjsBaseGlobal {
0),
JS_FN("getRegistry", gjs_internal_get_registry, 1, 0),
JS_FN("loadResourceOrFile", gjs_internal_load_resource_or_file, 1, 0),
+ JS_FN("loadResourceOrFileAsync",
+ gjs_internal_load_resource_or_file_async, 1, 0),
JS_FN("parseURI", gjs_internal_parse_uri, 1, 0),
JS_FN("resolveRelativeResourceOrFile",
gjs_internal_resolve_relative_resource_or_file, 2, 0),
diff --git a/gjs/internal.cpp b/gjs/internal.cpp
index 184f2457..9e172806 100644
--- a/gjs/internal.cpp
+++ b/gjs/internal.cpp
@@ -28,6 +28,7 @@
#include <codecvt> // for codecvt_utf8_utf16
#include <locale> // for wstring_convert
+#include <memory> // for unique_ptr
#include <string> // for u16string
#include <vector>
@@ -459,3 +460,148 @@ bool gjs_internal_uri_exists(JSContext* cx, unsigned argc, JS::Value* vp) {
args.rval().setBoolean(g_file_query_exists(file, nullptr));
return true;
}
+
+class PromiseData {
+ public:
+ JSContext* cx;
+
+ private:
+ JS::Heap<JSFunction*> m_resolve;
+ JS::Heap<JSFunction*> m_reject;
+
+ JS::HandleFunction resolver() {
+ return JS::HandleFunction::fromMarkedLocation(m_resolve.address());
+ }
+ JS::HandleFunction rejecter() {
+ return JS::HandleFunction::fromMarkedLocation(m_reject.address());
+ }
+
+ static void trace(JSTracer* trc, void* data) {
+ auto* self = PromiseData::from_ptr(data);
+ JS::TraceEdge(trc, &self->m_resolve, "loadResourceOrFileAsync resolve");
+ JS::TraceEdge(trc, &self->m_reject, "loadResourceOrFileAsync reject");
+ }
+
+ public:
+ explicit PromiseData(JSContext* a_cx, JSFunction* resolve,
+ JSFunction* reject)
+ : cx(a_cx), m_resolve(resolve), m_reject(reject) {
+ JS_AddExtraGCRootsTracer(cx, &PromiseData::trace, this);
+ }
+
+ ~PromiseData() {
+ JS_RemoveExtraGCRootsTracer(cx, &PromiseData::trace, this);
+ }
+
+ static PromiseData* from_ptr(void* ptr) {
+ return static_cast<PromiseData*>(ptr);
+ }
+
+ // Adapted from SpiderMonkey js::RejectPromiseWithPendingError()
+ //
https://searchfox.org/mozilla-central/rev/95cf843de977805a3951f9137f5ff1930599d94e/js/src/builtin/Promise.cpp#4435
+ void reject_with_pending_exception() {
+ JS::RootedValue exception(cx);
+ bool ok = JS_GetPendingException(cx, &exception);
+ g_assert(ok && "Cannot reject a promise with an uncatchable exception");
+
+ JS::RootedValueArray<1> args(cx);
+ args[0].set(exception);
+ JS::RootedValue ignored_rval(cx);
+ ok = JS_CallFunction(cx, /* this_obj = */ nullptr, rejecter(), args,
+ &ignored_rval);
+ g_assert(ok && "Failed rejecting promise");
+ }
+
+ void resolve(JS::Value result) {
+ JS::RootedValueArray<1> args(cx);
+ args[0].set(result);
+ JS::RootedValue ignored_rval(cx);
+ bool ok = JS_CallFunction(cx, /* this_obj = */ nullptr, resolver(),
+ args, &ignored_rval);
+ g_assert(ok && "Failed resolving promise");
+ }
+};
+
+static void load_async_callback(GObject* file, GAsyncResult* res, void* data) {
+ std::unique_ptr<PromiseData> promise(PromiseData::from_ptr(data));
+
+ char* contents;
+ size_t length;
+ GError* error = nullptr;
+ if (!g_file_load_contents_finish(G_FILE(file), res, &contents, &length,
+ /* etag_out = */ nullptr, &error)) {
+ gjs_throw_gerror_message(promise->cx, error);
+ promise->reject_with_pending_exception();
+ return;
+ }
+
+ JS::RootedValue text(promise->cx);
+ bool ok = gjs_string_from_utf8_n(promise->cx, contents, length, &text);
+ g_free(contents);
+ if (!ok) {
+ promise->reject_with_pending_exception();
+ return;
+ }
+
+ promise->resolve(text);
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool load_async_executor(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+
+ g_assert(args.length() == 2 && "Executor called weirdly");
+ g_assert(args[0].isObject() && "Executor called weirdly");
+ g_assert(args[1].isObject() && "Executor called weirdly");
+ g_assert(JS_ObjectIsFunction(&args[0].toObject()) &&
+ "Executor called weirdly");
+ g_assert(JS_ObjectIsFunction(&args[1].toObject()) &&
+ "Executor called weirdly");
+
+ JS::Value priv_value = js::GetFunctionNativeReserved(&args.callee(), 0);
+ g_assert(!priv_value.isNull() && "Executor called twice");
+ GjsAutoUnref<GFile> file = G_FILE(priv_value.toPrivate());
+ g_assert(file && "Executor called twice");
+ // We now own the GFile, and will pass the reference to the GAsyncResult, so
+ // remove it from the executor's private slot so it doesn't become dangling
+ js::SetFunctionNativeReserved(&args.callee(), 0, JS::NullValue());
+
+ auto* data = new PromiseData(cx, JS_GetObjectFunction(&args[0].toObject()),
+ JS_GetObjectFunction(&args[1].toObject()));
+ g_file_load_contents_async(file, nullptr, load_async_callback, data);
+
+ args.rval().setUndefined();
+ return true;
+}
+
+bool gjs_internal_load_resource_or_file_async(JSContext* cx, unsigned argc,
+ JS::Value* vp) {
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+
+ g_assert(args.length() == 1 && "loadResourceOrFileAsync(str)");
+ g_assert(args[0].isString() && "loadResourceOrFileAsync(str)");
+
+ JS::RootedString string_arg(cx, args[0].toString());
+ JS::UniqueChars uri = JS_EncodeStringToUTF8(cx, string_arg);
+ if (!uri)
+ return false;
+
+ GjsAutoUnref<GFile> file = g_file_new_for_uri(uri.get());
+
+ JS::RootedObject executor(cx,
+ JS_GetFunctionObject(js::NewFunctionWithReserved(
+ cx, load_async_executor, 2, 0,
+ "loadResourceOrFileAsync executor")));
+ if (!executor)
+ return false;
+
+ // Stash the file object for the callback to find later; executor owns it
+ js::SetFunctionNativeReserved(executor, 0, JS::PrivateValue(file.copy()));
+
+ JSObject* promise = JS::NewPromiseObject(cx, executor);
+ if (!promise)
+ return false;
+
+ args.rval().setObject(*promise);
+ return true;
+}
diff --git a/modules/internal/.eslintrc.yml b/modules/internal/.eslintrc.yml
index b06bc212..2889cc64 100644
--- a/modules/internal/.eslintrc.yml
+++ b/modules/internal/.eslintrc.yml
@@ -19,6 +19,7 @@ globals:
compileModule: readonly
compileInternalModule: readonly
loadResourceOrFile: readonly
+ loadResourceOrFileAsync: readonly
parseURI: readonly
uriExists: readonly
resolveRelativeResourceOrFile: readonly
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]