[gjs/wip/ptomato/mozjs52: 6/38] js: Refactor global object creation



commit 32f9b811b91874708eaa9099d7f65dc99d73879b
Author: Philip Chimento <philip chimento gmail com>
Date:   Sun Jun 25 15:31:30 2017 -0700

    js: Refactor global object creation
    
    In order to more easily create global objects, we refactor code that
    deals with them into gjs/global.cpp and gjs/global.h. Previously global
    objects were set up partly in gjs_context_constructed() and partly in
    gjs_init_context_standard(); we disentangle that code and move everything
    dealing with setting up the GjsContext into gjs_context_constructed(),
    while global object setup moves to gjs_create_global_object().
    
    Since global objects all share the same root importer, we must split the
    setup into two. Creating the root importer is the responsibility of
    gjs_context_constructed(), and it requires that the first global has been
    created. After creating the root importer, the global object is finished
    with gjs_define_global_properties().
    
    In the case of global objects beyond the first, such as the global object
    for the coverage compartment, gjs_define_global_properties() will also
    wrap the root importer in a cross-compartment wrapper so that the new
    global can access it.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=781429

 gjs-srcs.mk                     |    2 +
 gjs/context.cpp                 |  270 +++++-------------------------
 gjs/coverage.cpp                |   75 +--------
 gjs/global.cpp                  |  352 +++++++++++++++++++++++++++++++++++++++
 gjs/global.h                    |   72 ++++++++
 gjs/importer.cpp                |   79 ++-------
 gjs/importer.h                  |   18 +--
 gjs/jsapi-class.h               |    1 +
 gjs/jsapi-constructor-proxy.cpp |    5 +-
 gjs/jsapi-constructor-proxy.h   |    3 +-
 gjs/jsapi-util.cpp              |   95 -----------
 gjs/jsapi-util.h                |   34 ----
 12 files changed, 493 insertions(+), 513 deletions(-)
---
diff --git a/gjs-srcs.mk b/gjs-srcs.mk
index 503c5c9..b7c1efd 100644
--- a/gjs-srcs.mk
+++ b/gjs-srcs.mk
@@ -54,6 +54,8 @@ gjs_srcs =                            \
        gjs/context-private.h           \
        gjs/coverage-internal.h         \
        gjs/coverage.cpp                \
+       gjs/global.cpp                  \
+       gjs/global.h                    \
        gjs/importer.cpp                \
        gjs/importer.h                  \
        gjs/jsapi-class.h               \
diff --git a/gjs/context.cpp b/gjs/context.cpp
index ca8d43d..d45c3f4 100644
--- a/gjs/context.cpp
+++ b/gjs/context.cpp
@@ -28,8 +28,8 @@
 #include <gio/gio.h>
 
 #include "context-private.h"
+#include "global.h"
 #include "importer.h"
-#include "jsapi-constructor-proxy.h"
 #include "jsapi-private.h"
 #include "jsapi-util.h"
 #include "jsapi-wrapper.h"
@@ -113,157 +113,6 @@ enum {
 static GMutex contexts_lock;
 static GList *all_contexts = NULL;
 
-static bool
-gjs_log(JSContext *context,
-        unsigned   argc,
-        JS::Value *vp)
-{
-    JS::CallArgs argv = JS::CallArgsFromVp (argc, vp);
-
-    if (argc != 1) {
-        gjs_throw(context, "Must pass a single argument to log()");
-        return false;
-    }
-
-    JS_BeginRequest(context);
-
-    /* JS::ToString might throw, in which case we will only log that the value
-     * could not be converted to string */
-    JS::AutoSaveExceptionState exc_state(context);
-    JS::RootedString jstr(context, JS::ToString(context, argv[0]));
-    exc_state.restore();
-
-    if (jstr == NULL) {
-        g_message("JS LOG: <cannot convert value to string>");
-        JS_EndRequest(context);
-        return true;
-    }
-
-    GjsAutoJSChar s(context);
-    if (!gjs_string_to_utf8(context, JS::StringValue(jstr), &s)) {
-        JS_EndRequest(context);
-        return false;
-    }
-    g_message("JS LOG: %s", s.get());
-
-    JS_EndRequest(context);
-    argv.rval().setUndefined();
-    return true;
-}
-
-static bool
-gjs_log_error(JSContext *context,
-              unsigned   argc,
-              JS::Value *vp)
-{
-    JS::CallArgs argv = JS::CallArgsFromVp (argc, vp);
-
-    if ((argc != 1 && argc != 2) || !argv[0].isObject()) {
-        gjs_throw(context, "Must pass an exception and optionally a message to logError()");
-        return false;
-    }
-
-    JS_BeginRequest(context);
-
-    JS::RootedString jstr(context);
-
-    if (argc == 2) {
-        /* JS::ToString might throw, in which case we will only log that the
-         * value could be converted to string */
-        JS::AutoSaveExceptionState exc_state(context);
-        jstr = JS::ToString(context, argv[1]);
-        exc_state.restore();
-    }
-
-    gjs_log_exception_full(context, argv[0], jstr);
-
-    JS_EndRequest(context);
-    argv.rval().setUndefined();
-    return true;
-}
-
-static bool
-gjs_print_parse_args(JSContext *context,
-                     JS::CallArgs &argv,
-                     char     **buffer)
-{
-    GString *str;
-    guint n;
-
-    JS_BeginRequest(context);
-
-    str = g_string_new("");
-    for (n = 0; n < argv.length(); ++n) {
-        /* JS::ToString might throw, in which case we will only log that the
-         * value could not be converted to string */
-        JS::AutoSaveExceptionState exc_state(context);
-        JS::RootedString jstr(context, JS::ToString(context, argv[n]));
-        exc_state.restore();
-
-        if (jstr != NULL) {
-            GjsAutoJSChar s(context);
-            if (!gjs_string_to_utf8(context, JS::StringValue(jstr), &s)) {
-                JS_EndRequest(context);
-                g_string_free(str, true);
-                return false;
-            }
-
-            g_string_append(str, s);
-            if (n < (argv.length()-1))
-                g_string_append_c(str, ' ');
-        } else {
-            JS_EndRequest(context);
-            *buffer = g_string_free(str, true);
-            if (!*buffer)
-                *buffer = g_strdup("<invalid string>");
-            return true;
-        }
-
-    }
-    *buffer = g_string_free(str, false);
-
-    JS_EndRequest(context);
-    return true;
-}
-
-static bool
-gjs_print(JSContext *context,
-          unsigned   argc,
-          JS::Value *vp)
-{
-    JS::CallArgs argv = JS::CallArgsFromVp (argc, vp);
-    char *buffer;
-
-    if (!gjs_print_parse_args(context, argv, &buffer)) {
-        return false;
-    }
-
-    g_print("%s\n", buffer);
-    g_free(buffer);
-
-    argv.rval().setUndefined();
-    return true;
-}
-
-static bool
-gjs_printerr(JSContext *context,
-             unsigned   argc,
-             JS::Value *vp)
-{
-    JS::CallArgs argv = JS::CallArgsFromVp (argc, vp);
-    char *buffer;
-
-    if (!gjs_print_parse_args(context, argv, &buffer)) {
-        return false;
-    }
-
-    g_printerr("%s\n", buffer);
-    g_free(buffer);
-
-    argv.rval().setUndefined();
-    return true;
-}
-
 static void
 on_garbage_collect(JSRuntime *rt,
                    JSGCStatus status,
@@ -277,45 +126,6 @@ on_garbage_collect(JSRuntime *rt,
         gjs_object_clear_toggles();
 }
 
-/* Requires request, does not throw error */
-static bool
-gjs_define_promise_object(JSContext       *cx,
-                          JS::HandleObject global)
-{
-    /* This is not a regular import, we just load the module's code from the
-     * GResource and evaluate it */
-
-    GError *error = NULL;
-    GBytes *lie_bytes = g_resources_lookup_data("/org/gnome/gjs/modules/_lie.js",
-                                                G_RESOURCE_LOOKUP_FLAGS_NONE,
-                                                &error);
-    if (lie_bytes == NULL) {
-        g_critical("Failed to load Promise resource: %s", error->message);
-        g_clear_error(&error);
-        return false;
-    }
-
-    /* It should be OK to cast these bytes to const char *, since the module is
-     * a text file and we setUTF8(true) below */
-    size_t lie_length;
-    const char *lie_code = static_cast<const char *>(g_bytes_get_data(lie_bytes,
-                                                                      &lie_length));
-    JS::CompileOptions options(cx);
-    options.setUTF8(true)
-        .setSourceIsLazy(true)
-        .setFile("<Promise>");
-
-    JS::RootedValue promise(cx);
-    if (!JS::Evaluate(cx, global, options, lie_code, lie_length, &promise)) {
-        g_bytes_unref(lie_bytes);
-        return false;
-    }
-    g_bytes_unref(lie_bytes);
-
-    return JS_DefineProperty(cx, global, "Promise", promise,
-                             JSPROP_READONLY | JSPROP_PERMANENT);
-}
-
 static void
 gjs_context_init(GjsContext *js_context)
 {
@@ -465,14 +275,6 @@ gjs_context_finalize(GObject *object)
     G_OBJECT_CLASS(gjs_context_parent_class)->finalize(object);
 }
 
-static JSFunctionSpec global_funcs[] = {
-    JS_FS("log", gjs_log, 1, GJS_MODULE_PROP_FLAGS),
-    JS_FS("logError", gjs_log_error, 2, GJS_MODULE_PROP_FLAGS),
-    JS_FS("print", gjs_print, 0, GJS_MODULE_PROP_FLAGS),
-    JS_FS("printerr", gjs_printerr, 0, GJS_MODULE_PROP_FLAGS),
-    JS_FS_END
-};
-
 static void
 gjs_context_constructed(GObject *object)
 {
@@ -502,47 +304,55 @@ gjs_context_constructed(GObject *object)
 
     /* set ourselves as the private data */
     JS_SetContextPrivate(js_context->context, js_context);
-    JS::RootedObject global(js_context->context);
 
-    if (!gjs_init_context_standard(js_context->context, &global))
-        g_error("Failed to initialize context");
+    /* setExtraWarnings: Be extra strict about code that might hide a bug */
+    if (!g_getenv("GJS_DISABLE_EXTRA_WARNINGS")) {
+        gjs_debug(GJS_DEBUG_CONTEXT, "Enabling extra warnings");
+        JS::RuntimeOptionsRef(js_context->context).setExtraWarnings(true);
+    }
 
-    JSAutoCompartment ac(js_context->context, global);
+    if (!g_getenv("GJS_DISABLE_JIT")) {
+        gjs_debug(GJS_DEBUG_CONTEXT, "Enabling JIT");
+        JS::RuntimeOptionsRef(js_context->context)
+            .setIon(true)
+            .setBaseline(true)
+            .setAsmJS(true);
+    }
 
-    if (!JS_DefineProperty(js_context->context, global, "window", global,
-                           JSPROP_READONLY | JSPROP_PERMANENT))
-        g_error("No memory to export global object as 'window'");
+    /* setDontReportUncaught: Don't send exceptions to our error report handler;
+     * instead leave them set. This allows us to get at the exception object. */
+    JS::ContextOptionsRef(js_context->context).setDontReportUncaught(true);
 
-    if (!JS_DefineFunctions(js_context->context, global, &global_funcs[0]))
-        g_error("Failed to define properties on the global object");
+    JS::RootedObject global(js_context->context,
+        gjs_create_global_object(js_context->context));
+    if (!global) {
+        gjs_log_exception(js_context->context);
+        g_error("Failed to initialize global object");
+    }
+
+    JSAutoCompartment ac(js_context->context, global);
 
     new (&js_context->global) JS::Heap<JSObject *>(global);
     JS_AddExtraGCRootsTracer(js_context->runtime, gjs_context_tracer, js_context);
 
-    gjs_define_constructor_proxy_factory(js_context->context);
-
-    /* We create the global-to-runtime root importer with the
-     * passed-in search path. If someone else already created
-     * the root importer, this is a no-op.
-     */
-    if (!gjs_create_root_importer(js_context->context,
-                                  js_context->search_path ?
-                                  (const char**) js_context->search_path :
-                                  NULL,
-                                  true))
+    JS::RootedObject importer(js_context->context,
+        gjs_create_root_importer(js_context->context, js_context->search_path ?
+                                 js_context->search_path : nullptr));
+    if (!importer)
         g_error("Failed to create root importer");
 
-    /* Now copy the global root importer (which we just created,
-     * if it didn't exist) to our global object
-     */
-    if (!gjs_define_root_importer(js_context->context, global))
-        g_error("Failed to point 'imports' property at root importer");
-
-    /* FIXME: We should define the Promise object before any imports, in case
-     * the imports want to use it. Currently that's not possible as it needs to
-     * import GLib */
-    if(!gjs_define_promise_object(js_context->context, global))
-        g_error("Failed to define global Promise object");
+    JS::Value v_importer = gjs_get_global_slot(js_context->context,
+                                               GJS_GLOBAL_SLOT_IMPORTS);
+    g_assert(((void) "Someone else already created root importer",
+              v_importer.isUndefined()));
+
+    gjs_set_global_slot(js_context->context, GJS_GLOBAL_SLOT_IMPORTS,
+                        JS::ObjectValue(*importer));
+
+    if (!gjs_define_global_properties(js_context->context, global)) {
+        gjs_log_exception(js_context->context);
+        g_error("Failed to define properties on global object");
+    }
 
     JS_EndRequest(js_context->context);
 
diff --git a/gjs/coverage.cpp b/gjs/coverage.cpp
index 52a39df..722e7e5 100644
--- a/gjs/coverage.cpp
+++ b/gjs/coverage.cpp
@@ -25,6 +25,7 @@
 
 #include "coverage.h"
 #include "coverage-internal.h"
+#include "global.h"
 #include "importer.h"
 #include "jsapi-util-args.h"
 #include "util/error.h"
@@ -1258,24 +1259,6 @@ gjs_coverage_init(GjsCoverage *self)
 {
 }
 
-static JSClass coverage_global_class = {
-    "GjsCoverageGlobal",
-    JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(GJS_GLOBAL_SLOT_LAST) |
-    JSCLASS_IMPLEMENTS_BARRIERS,
-    NULL,  /* addProperty */
-    NULL,  /* deleteProperty */
-    NULL,  /* getProperty */
-    NULL,  /* setProperty */
-    NULL,  /* enumerate */
-    NULL,  /* resolve */
-    NULL,  /* convert */
-    NULL,  /* finalize */
-    NULL,  /* call */
-    NULL,  /* hasInstance */
-    NULL,  /* construct */
-    JS_GlobalObjectTraceHook
-};
-
 static bool
 gjs_context_eval_file_in_compartment(GjsContext      *context,
                                      const char      *filename,
@@ -1557,27 +1540,6 @@ gjs_inject_value_into_coverage_compartment(GjsCoverage     *coverage,
     return true;
 }
 
-/* Gets the root import and wraps it into a cross-compartment
- * object so that it can be used in the debugger compartment */
-static JSObject *
-gjs_wrap_root_importer_in_compartment(JSContext *context,
-                                      JS::HandleObject compartment)
-{
-    JSAutoRequest ar(context);
-    JSAutoCompartment ac(context, compartment);
-    JS::RootedValue importer(context,
-        gjs_get_global_slot(context, GJS_GLOBAL_SLOT_IMPORTS));
-
-    g_assert(importer.isObject());
-
-    JS::RootedObject wrapped_importer(context, &importer.toObject());
-    if (!JS_WrapObject(context, &wrapped_importer)) {
-        return NULL;
-    }
-
-    return wrapped_importer;
-}
-
 static bool
 bootstrap_coverage(GjsCoverage *coverage)
 {
@@ -1593,8 +1555,7 @@ bootstrap_coverage(GjsCoverage *coverage)
     JS::CompartmentOptions options;
     options.setVersion(JSVERSION_LATEST);
     JS::RootedObject debugger_compartment(context,
-        JS_NewGlobalObject(context, &coverage_global_class, NULL,
-                           JS::FireOnNewGlobalHook, options));
+                                          gjs_create_global_object(context));
     {
         JSAutoCompartment compartment(context, debugger_compartment);
         JS::RootedObject debuggeeWrapper(context, debuggee);
@@ -1610,35 +1571,9 @@ bootstrap_coverage(GjsCoverage *coverage)
             return false;
         }
 
-        if (!JS_InitStandardClasses(context, debugger_compartment)) {
-            gjs_throw(context, "Failed to init standard classes");
-            return false;
-        }
-
-        if (!JS_InitReflect(context, debugger_compartment)) {
-            gjs_throw(context, "Failed to init Reflect");
-            return false;
-        }
-
-        if (!JS_DefineDebuggerObject(context, debugger_compartment)) {
-            gjs_throw(context, "Failed to init Debugger");
-            return false;
-        }
-
-        JS::RootedObject wrapped_importer(context,
-                                          gjs_wrap_root_importer_in_compartment(context,
-                                                                                debugger_compartment));;
-
-        if (!wrapped_importer) {
-            gjs_throw(context, "Failed to wrap root importer in debugger compartment");
-            return false;
-        }
-
-        /* Now copy the global root importer (which we just created,
-         * if it didn't exist) to our global object
-         */
-        if (!gjs_define_root_importer_object(context, debugger_compartment, wrapped_importer)) {
-            gjs_throw(context, "Failed to set 'imports' on debugger compartment");
+        if (!gjs_define_global_properties(context, debugger_compartment)) {
+            gjs_throw(context, "Failed to define global properties on debugger "
+                      "compartment");
             return false;
         }
 
diff --git a/gjs/global.cpp b/gjs/global.cpp
new file mode 100644
index 0000000..2256d71
--- /dev/null
+++ b/gjs/global.cpp
@@ -0,0 +1,352 @@
+/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2008  litl, LLC
+ * Copyright (c) 2009 Red Hat, Inc.
+ * Copyright (c) 2017  Philip Chimento <philip chimento gmail com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <gio/gio.h>
+
+#include "global.h"
+#include "importer.h"
+#include "jsapi-constructor-proxy.h"
+#include "jsapi-util.h"
+#include "jsapi-wrapper.h"
+
+static bool
+gjs_log(JSContext *cx,
+        unsigned   argc,
+        JS::Value *vp)
+{
+    JS::CallArgs argv = JS::CallArgsFromVp(argc, vp);
+
+    if (argc != 1) {
+        gjs_throw(cx, "Must pass a single argument to log()");
+        return false;
+    }
+
+    JSAutoRequest ar(cx);
+
+    /* JS::ToString might throw, in which case we will only log that the value
+     * could not be converted to string */
+    JS::AutoSaveExceptionState exc_state(cx);
+    JS::RootedString jstr(cx, JS::ToString(cx, argv[0]));
+    exc_state.restore();
+
+    if (jstr == NULL) {
+        g_message("JS LOG: <cannot convert value to string>");
+        return true;
+    }
+
+    GjsAutoJSChar s(cx);
+    if (!gjs_string_to_utf8(cx, JS::StringValue(jstr), &s))
+        return false;
+
+    g_message("JS LOG: %s", s.get());
+
+    argv.rval().setUndefined();
+    return true;
+}
+
+static bool
+gjs_log_error(JSContext *cx,
+              unsigned   argc,
+              JS::Value *vp)
+{
+    JS::CallArgs argv = JS::CallArgsFromVp(argc, vp);
+
+    if ((argc != 1 && argc != 2) || !argv[0].isObject()) {
+        gjs_throw(cx, "Must pass an exception and optionally a message to logError()");
+        return false;
+    }
+
+    JSAutoRequest ar(cx);
+
+    JS::RootedString jstr(cx);
+
+    if (argc == 2) {
+        /* JS::ToString might throw, in which case we will only log that the
+         * value could not be converted to string */
+        JS::AutoSaveExceptionState exc_state(cx);
+        jstr = JS::ToString(cx, argv[1]);
+        exc_state.restore();
+    }
+
+    gjs_log_exception_full(cx, argv[0], jstr);
+
+    argv.rval().setUndefined();
+    return true;
+}
+
+static bool
+gjs_print_parse_args(JSContext    *cx,
+                     JS::CallArgs& argv,
+                     GjsAutoChar  *buffer)
+{
+    GString *str;
+    guint n;
+
+    JSAutoRequest ar(cx);
+
+    str = g_string_new("");
+    for (n = 0; n < argv.length(); ++n) {
+        /* JS::ToString might throw, in which case we will only log that the
+         * value could not be converted to string */
+        JS::AutoSaveExceptionState exc_state(cx);
+        JS::RootedString jstr(cx, JS::ToString(cx, argv[n]));
+        exc_state.restore();
+
+        if (jstr != NULL) {
+            GjsAutoJSChar s(cx);
+            if (!gjs_string_to_utf8(cx, JS::StringValue(jstr), &s)) {
+                g_string_free(str, true);
+                return false;
+            }
+
+            g_string_append(str, s);
+            if (n < (argv.length()-1))
+                g_string_append_c(str, ' ');
+        } else {
+            *buffer = g_string_free(str, true);
+            if (!*buffer)
+                *buffer = g_strdup("<invalid string>");
+            return true;
+        }
+
+    }
+    *buffer = g_string_free(str, false);
+
+    return true;
+}
+
+static bool
+gjs_print(JSContext *context,
+          unsigned   argc,
+          JS::Value *vp)
+{
+    JS::CallArgs argv = JS::CallArgsFromVp (argc, vp);
+
+    GjsAutoChar buffer;
+    if (!gjs_print_parse_args(context, argv, &buffer))
+        return false;
+
+    g_print("%s\n", buffer.get());
+
+    argv.rval().setUndefined();
+    return true;
+}
+
+static bool
+gjs_printerr(JSContext *context,
+             unsigned   argc,
+             JS::Value *vp)
+{
+    JS::CallArgs argv = JS::CallArgsFromVp(argc, vp);
+
+    GjsAutoChar buffer;
+    if (!gjs_print_parse_args(context, argv, &buffer))
+        return false;
+
+    g_printerr("%s\n", buffer.get());
+
+    argv.rval().setUndefined();
+    return true;
+}
+
+class GjsGlobal {
+    static constexpr JSClass klass = {
+        "GjsGlobal",
+        JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(GJS_GLOBAL_SLOT_LAST) |
+        JSCLASS_IMPLEMENTS_BARRIERS,
+        nullptr,  /* addProperty */
+        nullptr,  /* deleteProperty */
+        nullptr,  /* getProperty */
+        nullptr,  /* setProperty */
+        nullptr,  /* enumerate */
+        nullptr,  /* resolve */
+        nullptr,  /* convert */
+        nullptr,  /* finalize */
+        nullptr,  /* call */
+        nullptr,  /* hasInstance */
+        nullptr,  /* construct */
+        JS_GlobalObjectTraceHook
+    };
+
+    static constexpr JSFunctionSpec static_funcs[] = {
+        JS_FS("log", gjs_log, 1, GJS_MODULE_PROP_FLAGS),
+        JS_FS("logError", gjs_log_error, 2, GJS_MODULE_PROP_FLAGS),
+        JS_FS("print", gjs_print, 0, GJS_MODULE_PROP_FLAGS),
+        JS_FS("printerr", gjs_printerr, 0, GJS_MODULE_PROP_FLAGS),
+        JS_FS_END
+    };
+
+    static bool
+    define_promise_object(JSContext       *cx,
+                          JS::HandleObject global)
+    {
+        /* This is not a regular import, we just load the module's code from the
+         * GResource and evaluate it */
+
+        GError *error = NULL;
+        GBytes *lie_bytes = g_resources_lookup_data("/org/gnome/gjs/modules/_lie.js",
+                                                    G_RESOURCE_LOOKUP_FLAGS_NONE,
+                                                    &error);
+        if (lie_bytes == NULL) {
+            g_critical("Failed to load Promise resource: %s", error->message);
+            g_clear_error(&error);
+            return false;
+        }
+
+        /* It should be OK to cast these bytes to const char *, since the module is
+         * a text file and we setUTF8(true) below */
+        size_t lie_length;
+        const char *lie_code = static_cast<const char *>(g_bytes_get_data(lie_bytes,
+                                                                          &lie_length));
+        JS::CompileOptions options(cx);
+        options.setUTF8(true)
+            .setSourceIsLazy(true)
+            .setFile("<Promise>");
+
+        JS::RootedValue promise(cx);
+        if (!JS::Evaluate(cx, global, options, lie_code, lie_length, &promise)) {
+            g_bytes_unref(lie_bytes);
+            return false;
+        }
+        g_bytes_unref(lie_bytes);
+
+        return JS_DefineProperty(cx, global, "Promise", promise,
+                                 JSPROP_READONLY | JSPROP_PERMANENT);
+    }
+
+public:
+
+    static JSObject *
+    create(JSContext *cx)
+    {
+        JS::CompartmentOptions compartment_options;
+        compartment_options.setVersion(JSVERSION_LATEST);
+        JS::RootedObject global(cx,
+            JS_NewGlobalObject(cx, &GjsGlobal::klass, nullptr,
+                               JS::FireOnNewGlobalHook, compartment_options));
+        if (!global)
+            return nullptr;
+
+        JSAutoCompartment ac(cx, global);
+
+        if (!JS_InitStandardClasses(cx, global) ||
+            !JS_InitReflect(cx, global) ||
+            !JS_DefineDebuggerObject(cx, global))
+            return nullptr;
+
+        return global;
+    }
+
+    static bool
+    define_properties(JSContext       *cx,
+                      JS::HandleObject global)
+    {
+        if (!JS_DefineProperty(cx, global, "window", global,
+                               JSPROP_READONLY | JSPROP_PERMANENT) ||
+            !JS_DefineFunctions(cx, global, GjsGlobal::static_funcs) ||
+            !gjs_define_constructor_proxy_factory(cx, global))
+            return false;
+
+        JS::Value v_importer = gjs_get_global_slot(cx, GJS_GLOBAL_SLOT_IMPORTS);
+        g_assert(((void) "importer should be defined before passing null "
+                  "importer to GjsGlobal::define_properties",
+                  v_importer.isObject()));
+        JS::RootedObject root_importer(cx, &v_importer.toObject());
+
+        /* Wrapping is a no-op if the importer is already in the same
+         * compartment. */
+        if (!JS_WrapObject(cx, &root_importer) ||
+            !gjs_object_define_property(cx, global, GJS_STRING_IMPORTS,
+                                        root_importer, GJS_MODULE_PROP_FLAGS))
+            return false;
+
+        /* FIXME: We should define the Promise object before any imports, in
+         * case the imports want to use it. Currently that's not possible as it
+         * needs to import GLib */
+        return define_promise_object(cx, global);
+    }
+};
+
+/**
+ * gjs_create_global_object:
+ * @cx: a #JSContext
+ *
+ * Creates a global object, and initializes it with the default API.
+ *
+ * Returns: the created global object on success, nullptr otherwise, in which
+ * case an exception is pending on @cx
+ */
+JSObject *
+gjs_create_global_object(JSContext *cx)
+{
+    return GjsGlobal::create(cx);
+}
+
+/**
+ * gjs_define_global_properties:
+ * @cx: a #JSContext
+ * @global: a JS global object that has not yet been passed to this function
+ *
+ * Defines properties on the global object such as 'window' and 'imports'.
+ * This function completes the initialization of a new global object, but it
+ * is separate from gjs_create_global_object() because all globals share the
+ * same root importer.
+ * The code creating the main global for the JS context needs to create the
+ * root importer in between calling gjs_create_global_object() and
+ * gjs_define_global_properties().
+ *
+ * The caller of this function should be in the compartment for @global.
+ * If the root importer object belongs to a different compartment, this
+ * function will create a cross-compartment wrapper for it.
+ *
+ * Returns: true on success, false otherwise, in which case an exception is
+ * pending on @cx
+ */
+bool
+gjs_define_global_properties(JSContext       *cx,
+                             JS::HandleObject global)
+{
+    return GjsGlobal::define_properties(cx, global);
+}
+
+void
+gjs_set_global_slot(JSContext    *cx,
+                    GjsGlobalSlot slot,
+                    JS::Value     value)
+{
+    JSObject *global = gjs_get_import_global(cx);
+    JS_SetReservedSlot(global, JSCLASS_GLOBAL_SLOT_COUNT + slot, value);
+}
+
+JS::Value
+gjs_get_global_slot(JSContext    *cx,
+                    GjsGlobalSlot slot)
+{
+    JSObject *global = gjs_get_import_global(cx);
+    return JS_GetReservedSlot(global, JSCLASS_GLOBAL_SLOT_COUNT + slot);
+}
+
+decltype(GjsGlobal::klass) constexpr GjsGlobal::klass;
+decltype(GjsGlobal::static_funcs) constexpr GjsGlobal::static_funcs;
diff --git a/gjs/global.h b/gjs/global.h
new file mode 100644
index 0000000..31056d6
--- /dev/null
+++ b/gjs/global.h
@@ -0,0 +1,72 @@
+/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2017  Philip Chimento <philip chimento gmail com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef GJS_GLOBAL_H
+#define GJS_GLOBAL_H
+
+#include <glib.h>
+
+#include "jsapi-wrapper.h"
+
+G_BEGIN_DECLS
+
+typedef enum {
+    GJS_GLOBAL_SLOT_IMPORTS,
+    GJS_GLOBAL_SLOT_PROTOTYPE_gtype,
+    GJS_GLOBAL_SLOT_PROTOTYPE_function,
+    GJS_GLOBAL_SLOT_PROTOTYPE_ns,
+    GJS_GLOBAL_SLOT_PROTOTYPE_repo,
+    GJS_GLOBAL_SLOT_PROTOTYPE_byte_array,
+    GJS_GLOBAL_SLOT_PROTOTYPE_importer,
+    GJS_GLOBAL_SLOT_PROTOTYPE_cairo_context,
+    GJS_GLOBAL_SLOT_PROTOTYPE_cairo_gradient,
+    GJS_GLOBAL_SLOT_PROTOTYPE_cairo_image_surface,
+    GJS_GLOBAL_SLOT_PROTOTYPE_cairo_linear_gradient,
+    GJS_GLOBAL_SLOT_PROTOTYPE_cairo_path,
+    GJS_GLOBAL_SLOT_PROTOTYPE_cairo_pattern,
+    GJS_GLOBAL_SLOT_PROTOTYPE_cairo_pdf_surface,
+    GJS_GLOBAL_SLOT_PROTOTYPE_cairo_ps_surface,
+    GJS_GLOBAL_SLOT_PROTOTYPE_cairo_radial_gradient,
+    GJS_GLOBAL_SLOT_PROTOTYPE_cairo_region,
+    GJS_GLOBAL_SLOT_PROTOTYPE_cairo_solid_pattern,
+    GJS_GLOBAL_SLOT_PROTOTYPE_cairo_surface,
+    GJS_GLOBAL_SLOT_PROTOTYPE_cairo_surface_pattern,
+    GJS_GLOBAL_SLOT_PROTOTYPE_cairo_svg_surface,
+    GJS_GLOBAL_SLOT_LAST,
+} GjsGlobalSlot;
+
+JSObject *gjs_create_global_object(JSContext *cx);
+
+bool gjs_define_global_properties(JSContext       *cx,
+                                  JS::HandleObject global);
+
+JS::Value gjs_get_global_slot(JSContext    *cx,
+                              GjsGlobalSlot slot);
+
+void gjs_set_global_slot(JSContext    *context,
+                         GjsGlobalSlot slot,
+                         JS::Value     value);
+
+G_END_DECLS
+
+#endif  /* GJS_GLOBAL_H */
diff --git a/gjs/importer.cpp b/gjs/importer.cpp
index 572331d..c8c0ecc 100644
--- a/gjs/importer.cpp
+++ b/gjs/importer.cpp
@@ -63,6 +63,9 @@ static const JSClass gjs_importer_class = *js::Jsvalify(&gjs_importer_real_class
 
 GJS_DEFINE_PRIV_FROM_JS(Importer, gjs_importer_class)
 
+static JSObject *gjs_define_importer(JSContext *, JS::HandleObject,
+    const char *, const char **, bool);
+
 static bool
 importer_to_string(JSContext *cx,
                    unsigned   argc,
@@ -966,12 +969,12 @@ gjs_get_search_path(void)
 }
 
 static JSObject*
-gjs_create_importer(JSContext       *context,
-                    const char      *importer_name,
-                    const char     **initial_search_path,
-                    bool             add_standard_search_path,
-                    bool             is_root,
-                    JS::HandleObject in_object)
+gjs_create_importer(JSContext          *context,
+                    const char         *importer_name,
+                    const char * const *initial_search_path,
+                    bool                add_standard_search_path,
+                    bool                is_root,
+                    JS::HandleObject    in_object)
 {
     char **paths[2] = {0};
     char **search_path;
@@ -1001,7 +1004,7 @@ gjs_create_importer(JSContext       *context,
     return importer;
 }
 
-JSObject*
+static JSObject *
 gjs_define_importer(JSContext       *context,
                     JS::HandleObject in_object,
                     const char      *importer_name,
@@ -1024,62 +1027,10 @@ gjs_define_importer(JSContext       *context,
     return importer;
 }
 
-/* If this were called twice for the same runtime with different args it
- * would basically be a bug, but checking for that is a lot of code so
- * we just ignore all calls after the first and hope the args are the same.
- */
-bool
-gjs_create_root_importer(JSContext   *context,
-                         const char **initial_search_path,
-                         bool         add_standard_search_path)
-{
-    JS::Value importer;
-
-    JS_BeginRequest(context);
-
-    importer = gjs_get_global_slot(context, GJS_GLOBAL_SLOT_IMPORTS);
-
-    if (G_UNLIKELY (!importer.isUndefined())) {
-        gjs_debug(GJS_DEBUG_IMPORTER,
-                  "Someone else already created root importer, ignoring second request");
-
-        JS_EndRequest(context);
-        return true;
-    }
-
-    importer = JS::ObjectValue(*gjs_create_importer(context, "imports",
-                                                    initial_search_path,
-                                                    add_standard_search_path,
-                                                    true, JS::NullPtr()));
-    gjs_set_global_slot(context, GJS_GLOBAL_SLOT_IMPORTS, importer);
-
-    JS_EndRequest(context);
-    return true;
-}
-
-bool
-gjs_define_root_importer_object(JSContext        *context,
-                                JS::HandleObject  in_object,
-                                JS::HandleObject  root_importer)
-{
-    JSAutoRequest ar(context);
-
-    if (!gjs_object_define_property(context, in_object,
-                                    GJS_STRING_IMPORTS, root_importer,
-                                    GJS_MODULE_PROP_FLAGS)) {
-        gjs_debug(GJS_DEBUG_IMPORTER, "DefineProperty imports on %p failed",
-                  in_object.get());
-        return false;
-    }
-
-    return true;
-}
-
-bool
-gjs_define_root_importer(JSContext       *cx,
-                         JS::HandleObject in_object)
+JSObject *
+gjs_create_root_importer(JSContext          *cx,
+                         const char * const *search_path)
 {
-    JS::Value importer = gjs_get_global_slot(cx, GJS_GLOBAL_SLOT_IMPORTS);
-    JS::RootedObject rooted_importer(cx, &importer.toObject());
-    return gjs_define_root_importer_object(cx, in_object, rooted_importer);
+    return gjs_create_importer(cx, "imports", search_path, true, true,
+                               JS::NullPtr());
 }
diff --git a/gjs/importer.h b/gjs/importer.h
index 0d0ee88..70d1718 100644
--- a/gjs/importer.h
+++ b/gjs/importer.h
@@ -30,22 +30,8 @@
 
 G_BEGIN_DECLS
 
-bool      gjs_create_root_importer (JSContext   *context,
-                                    const char **initial_search_path,
-                                    bool         add_standard_search_path);
-
-bool gjs_define_root_importer(JSContext       *cx,
-                              JS::HandleObject in_object);
-
-bool      gjs_define_root_importer_object(JSContext        *context,
-                                          JS::HandleObject  in_object,
-                                          JS::HandleObject  root_importer);
-
-JSObject *gjs_define_importer(JSContext       *context,
-                              JS::HandleObject in_object,
-                              const char      *importer_name,
-                              const char     **initial_search_path,
-                              bool             add_standard_search_path);
+JSObject *gjs_create_root_importer(JSContext          *cx,
+                                   const char * const *search_path);
 
 G_END_DECLS
 
diff --git a/gjs/jsapi-class.h b/gjs/jsapi-class.h
index 7797901..0d487e7 100644
--- a/gjs/jsapi-class.h
+++ b/gjs/jsapi-class.h
@@ -24,6 +24,7 @@
 #ifndef GJS_JSAPI_CLASS_H
 #define GJS_JSAPI_CLASS_H
 
+#include "global.h"
 #include "jsapi-util.h"
 #include "jsapi-wrapper.h"
 #include "util/log.h"
diff --git a/gjs/jsapi-constructor-proxy.cpp b/gjs/jsapi-constructor-proxy.cpp
index 6bb59b2..ea59bd3 100644
--- a/gjs/jsapi-constructor-proxy.cpp
+++ b/gjs/jsapi-constructor-proxy.cpp
@@ -173,11 +173,10 @@ create_gjs_constructor_proxy(JSContext *cx,
 }
 
 bool
-gjs_define_constructor_proxy_factory(JSContext *cx)
+gjs_define_constructor_proxy_factory(JSContext       *cx,
+                                     JS::HandleObject global)
 {
     bool found;
-    JS::RootedObject global(cx, gjs_get_import_global(cx));
-
     if (!JS_HasProperty(cx, global, constructor_proxy_create_name, &found))
         return false;
     if (found)
diff --git a/gjs/jsapi-constructor-proxy.h b/gjs/jsapi-constructor-proxy.h
index ef54145..bc45b07 100644
--- a/gjs/jsapi-constructor-proxy.h
+++ b/gjs/jsapi-constructor-proxy.h
@@ -30,7 +30,8 @@
 
 G_BEGIN_DECLS
 
-bool gjs_define_constructor_proxy_factory(JSContext *cx);
+bool gjs_define_constructor_proxy_factory(JSContext       *cx,
+                                          JS::HandleObject global);
 
 G_END_DECLS
 
diff --git a/gjs/jsapi-util.cpp b/gjs/jsapi-util.cpp
index 0caa514..183e40e 100644
--- a/gjs/jsapi-util.cpp
+++ b/gjs/jsapi-util.cpp
@@ -45,101 +45,6 @@ gjs_util_error_quark (void)
     return g_quark_from_static_string ("gjs-util-error-quark");
 }
 
-static JSClass global_class = {
-    "GjsGlobal",
-    JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(GJS_GLOBAL_SLOT_LAST) |
-    JSCLASS_IMPLEMENTS_BARRIERS,
-    NULL,  /* addProperty */
-    NULL,  /* deleteProperty */
-    NULL,  /* getProperty */
-    NULL,  /* setProperty */
-    NULL,  /* enumerate */
-    NULL,  /* resolve */
-    NULL,  /* convert */
-    NULL,  /* finalize */
-    NULL,  /* call */
-    NULL,  /* hasInstance */
-    NULL,  /* construct */
-    JS_GlobalObjectTraceHook
-};
-
-/**
- * gjs_init_context_standard:
- * @context: a #JSContext
- * @global_out: (out): The created global object.
-
- * This function creates a global object given the context,
- * and initializes it with the default API.
- *
- * Returns: true on success, false otherwise
- */
-bool
-gjs_init_context_standard (JSContext              *context,
-                           JS::MutableHandleObject global)
-{
-    JS::CompartmentOptions compartment_options;
-
-    bool extra_warnings = false;
-    if (!g_getenv("GJS_DISABLE_EXTRA_WARNINGS")) {
-        gjs_debug(GJS_DEBUG_CONTEXT, "Enabling extra warnings");
-        extra_warnings = true;
-    }
-
-    /* setDontReportUncaught: Don't send exceptions to our error report handler;
-     * instead leave them set. This allows us to get at the exception object.
-     *
-     * setExtraWarnings: Report warnings to error reporter function.
-     */
-    JS::ContextOptionsRef(context).setDontReportUncaught(true);
-    JS::RuntimeOptionsRef(context).setExtraWarnings(extra_warnings);
-
-    if (!g_getenv("GJS_DISABLE_JIT")) {
-        gjs_debug(GJS_DEBUG_CONTEXT, "Enabling JIT");
-        JS::RuntimeOptionsRef(context)
-            .setIon(true)
-            .setBaseline(true)
-            .setAsmJS(true);
-    }
-
-    compartment_options.setVersion(JSVERSION_LATEST);
-    global.set(JS_NewGlobalObject(context, &global_class, NULL,
-                                  JS::FireOnNewGlobalHook, compartment_options));
-    if (global == NULL)
-        return false;
-
-    JSAutoCompartment ac(context, global);
-
-    if (!JS_InitStandardClasses(context, global))
-        return false;
-
-    if (!JS_InitReflect(context, global))
-        return false;
-
-    if (!JS_DefineDebuggerObject(context, global))
-        return false;
-
-    return true;
-}
-
-void
-gjs_set_global_slot (JSContext     *context,
-                     GjsGlobalSlot  slot,
-                     JS::Value      value)
-{
-    JSObject *global;
-    global = gjs_get_import_global(context);
-    JS_SetReservedSlot(global, JSCLASS_GLOBAL_SLOT_COUNT + slot, value);
-}
-
-JS::Value
-gjs_get_global_slot (JSContext     *context,
-                     GjsGlobalSlot  slot)
-{
-    JSObject *global;
-    global = gjs_get_import_global(context);
-    return JS_GetReservedSlot(global, JSCLASS_GLOBAL_SLOT_COUNT + slot);
-}
-
 bool
 gjs_object_get_property(JSContext             *cx,
                         JS::HandleObject       obj,
diff --git a/gjs/jsapi-util.h b/gjs/jsapi-util.h
index b3b6efb..f1c53f4 100644
--- a/gjs/jsapi-util.h
+++ b/gjs/jsapi-util.h
@@ -127,31 +127,6 @@ enum {
   GJS_UTIL_ERROR_ARGUMENT_TYPE_MISMATCH
 };
 
-typedef enum {
-    GJS_GLOBAL_SLOT_IMPORTS,
-    GJS_GLOBAL_SLOT_PROTOTYPE_gtype,
-    GJS_GLOBAL_SLOT_PROTOTYPE_function,
-    GJS_GLOBAL_SLOT_PROTOTYPE_ns,
-    GJS_GLOBAL_SLOT_PROTOTYPE_repo,
-    GJS_GLOBAL_SLOT_PROTOTYPE_byte_array,
-    GJS_GLOBAL_SLOT_PROTOTYPE_importer,
-    GJS_GLOBAL_SLOT_PROTOTYPE_cairo_context,
-    GJS_GLOBAL_SLOT_PROTOTYPE_cairo_gradient,
-    GJS_GLOBAL_SLOT_PROTOTYPE_cairo_image_surface,
-    GJS_GLOBAL_SLOT_PROTOTYPE_cairo_linear_gradient,
-    GJS_GLOBAL_SLOT_PROTOTYPE_cairo_path,
-    GJS_GLOBAL_SLOT_PROTOTYPE_cairo_pattern,
-    GJS_GLOBAL_SLOT_PROTOTYPE_cairo_pdf_surface,
-    GJS_GLOBAL_SLOT_PROTOTYPE_cairo_ps_surface,
-    GJS_GLOBAL_SLOT_PROTOTYPE_cairo_radial_gradient,
-    GJS_GLOBAL_SLOT_PROTOTYPE_cairo_region,
-    GJS_GLOBAL_SLOT_PROTOTYPE_cairo_solid_pattern,
-    GJS_GLOBAL_SLOT_PROTOTYPE_cairo_surface,
-    GJS_GLOBAL_SLOT_PROTOTYPE_cairo_surface_pattern,
-    GJS_GLOBAL_SLOT_PROTOTYPE_cairo_svg_surface,
-    GJS_GLOBAL_SLOT_LAST,
-} GjsGlobalSlot;
-
 typedef struct GjsRootedArray GjsRootedArray;
 
 /* Flags that should be set on properties exported from native code modules.
@@ -178,17 +153,8 @@ typedef struct GjsRootedArray GjsRootedArray;
     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);          \
     JS::RootedObject to(cx, &args.computeThis(cx).toObject())
 
-bool gjs_init_context_standard(JSContext              *context,
-                               JS::MutableHandleObject global);
-
 JSObject*   gjs_get_import_global            (JSContext       *context);
 
-JS::Value   gjs_get_global_slot              (JSContext       *context,
-                                              GjsGlobalSlot    slot);
-void        gjs_set_global_slot              (JSContext       *context,
-                                              GjsGlobalSlot    slot,
-                                              JS::Value        value);
-
 void gjs_throw_constructor_error             (JSContext       *context);
 
 void gjs_throw_abstract_constructor_error(JSContext    *context,



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]