[GLIB] Add API to evaluate code using a given object to store global symbols
authorcarlosgc@webkit.org <carlosgc@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 16 Jul 2018 11:58:11 +0000 (11:58 +0000)
committercarlosgc@webkit.org <carlosgc@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 16 Jul 2018 11:58:11 +0000 (11:58 +0000)
https://bugs.webkit.org/show_bug.cgi?id=187639

Reviewed by Michael Catanzaro.

Source/JavaScriptCore:

Add jsc_context_evaluate_in_object(). It returns a new object as an out parameter. Global symbols in the
evaluated script are added as properties to the new object instead of to the context global object. This is
similar to JS::Evaluate in spider monkey when a scopeChain parameter is passed, but JSC doesn't support using a
scope for assignments, so we have to create a new context and get its global object. This patch also updates
jsc_context_evaluate_with_source_uri() to receive the starting line number for consistency with the new
jsc_context_evaluate_in_object().

* API/glib/JSCContext.cpp:
(jsc_context_evaluate): Pass 0 as line number to jsc_context_evaluate_with_source_uri().
(evaluateScriptInContext): Helper function to evaluate a script in a JSGlobalContextRef.
(jsc_context_evaluate_with_source_uri): Use evaluateScriptInContext().
(jsc_context_evaluate_in_object): Create a new context and set the main context global object as extension
scope of it. Evaluate the script in the new context and get its global object to be returned as parameter.
* API/glib/JSCContext.h:
* API/glib/docs/jsc-glib-4.0-sections.txt:

Tools:

Add a new test case.

* TestWebKitAPI/Tests/JavaScriptCore/glib/TestJSC.cpp:
(testJSCEvaluateInObject):
(testJSCExceptions):
(main):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@233844 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/JavaScriptCore/API/glib/JSCContext.cpp
Source/JavaScriptCore/API/glib/JSCContext.h
Source/JavaScriptCore/API/glib/docs/jsc-glib-4.0-sections.txt
Source/JavaScriptCore/ChangeLog
Tools/ChangeLog
Tools/TestWebKitAPI/Tests/JavaScriptCore/glib/TestJSC.cpp

index 32d6630..b35a283 100644 (file)
@@ -28,6 +28,7 @@
 #include "JSCVirtualMachinePrivate.h"
 #include "JSCWrapperMap.h"
 #include "JSRetainPtr.h"
+#include "JSWithScope.h"
 #include "OpaqueJSString.h"
 #include <wtf/glib/GUniquePtr.h>
 #include <wtf/glib/WTFGType.h>
@@ -751,7 +752,14 @@ JSCContext* jsc_context_get_current()
  */
 JSCValue* jsc_context_evaluate(JSCContext* context, const char* code, gssize length)
 {
-    return jsc_context_evaluate_with_source_uri(context, code, length, nullptr);
+    return jsc_context_evaluate_with_source_uri(context, code, length, nullptr, 0);
+}
+
+static JSValueRef evaluateScriptInContext(JSGlobalContextRef jsContext, String&& script, const char* uri, unsigned lineNumber, JSValueRef* exception)
+{
+    JSRetainPtr<JSStringRef> scriptJS(Adopt, OpaqueJSString::create(WTFMove(script)).leakRef());
+    JSRetainPtr<JSStringRef> sourceURI = uri ? adopt(JSStringCreateWithUTF8CString(uri)) : nullptr;
+    return JSEvaluateScript(jsContext, scriptJS.get(), nullptr, sourceURI.get(), lineNumber, exception);
 }
 
 /**
@@ -760,28 +768,62 @@ JSCValue* jsc_context_evaluate(JSCContext* context, const char* code, gssize len
  * @code: a JavaScript script to evaluate
  * @length: length of @code, or -1 if @code is a nul-terminated string
  * @uri: the source URI
+ * @line_number: the starting line number
  *
- * Evaluate @code in @context using @uri as the source URI. This is exactly the same as
- * jsc_context_evaluate() but @uri will be shown in exceptions. The source @uri doesn't
- * affect the behavior of the script.
+ * Evaluate @code in @context using @uri as the source URI. The @line_number is the starting line number
+ * in @uri; the value is one-based so the first line is 1. @uri and @line_number will be shown in exceptions and
+ * they don't affect the behavior of the script.
  *
  * Returns: (transfer full): a #JSCValue representing the last value generated by the script.
  */
-JSCValue* jsc_context_evaluate_with_source_uri(JSCContext* context, const char* code, gssize length, const char* uri)
+JSCValue* jsc_context_evaluate_with_source_uri(JSCContext* context, const char* code, gssize length, const char* uri, unsigned lineNumber)
 {
     g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr);
     g_return_val_if_fail(code, nullptr);
 
-    if (length < 0)
-        length = strlen(code);
-    auto script = String::fromUTF8(code, length);
-    JSRetainPtr<JSStringRef> scriptJS(Adopt, OpaqueJSString::create(WTFMove(script)).leakRef());
-    JSRetainPtr<JSStringRef> sourceURI = uri ? adopt(JSStringCreateWithUTF8CString(uri)) : nullptr;
     JSValueRef exception = nullptr;
-    JSValueRef result = JSEvaluateScript(context->priv->jsContext.get(), scriptJS.get(), nullptr, sourceURI.get(), 1, &exception);
+    JSValueRef result = evaluateScriptInContext(context->priv->jsContext.get(), String::fromUTF8(code, length < 0 ? strlen(code) : length), uri, lineNumber, &exception);
+    if (jscContextHandleExceptionIfNeeded(context, exception))
+        return jsc_value_new_undefined(context);
+
+    return jscContextGetOrCreateValue(context, result).leakRef();
+}
+
+/**
+ * jsc_context_evaluate_in_object:
+ * @context: a #JSCContext
+ * @code: a JavaScript script to evaluate
+ * @length: length of @code, or -1 if @code is a nul-terminated string
+ * @object_class: (nullable): a #JSCClass or %NULL to use the default
+ * @uri: the source URI
+ * @line_number: the starting line number
+ * @object: (out) (transfer full): return location for a #JSCValue.
+ *
+ * Evaluate @code and create an new object where symbols defined in @code will be added as properties,
+ * instead of being added to @context global object. The new object is returned as @object parameter.
+ * The @line_number is the starting line number in @uri; the value is one-based so the first line is 1.
+ * @uri and @line_number will be shown in exceptions and they don't affect the behavior of the script.
+ *
+ * Returns: (transfer full): a #JSCValue representing the last value generated by the script.
+ */
+JSCValue* jsc_context_evaluate_in_object(JSCContext* context, const char* code, gssize length, JSCClass* objectClass, const char* uri, unsigned lineNumber, JSCValue** object)
+{
+    g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr);
+    g_return_val_if_fail(code, nullptr);
+    g_return_val_if_fail(!objectClass || JSC_IS_CLASS(objectClass), nullptr);
+    g_return_val_if_fail(object && !*object, nullptr);
+
+    JSRetainPtr<JSGlobalContextRef> objectContext(Adopt, JSGlobalContextCreateInGroup(jscVirtualMachineGetContextGroup(context->priv->vm.get()), objectClass ? jscClassGetJSClass(objectClass) : nullptr));
+    JSC::ExecState* exec = toJS(objectContext.get());
+    auto* jsObject = exec->vmEntryGlobalObject();
+    jsObject->setGlobalScopeExtension(JSC::JSWithScope::create(exec->vm(), jsObject, jsObject->globalScope(), toJS(JSContextGetGlobalObject(context->priv->jsContext.get()))));
+    JSValueRef exception = nullptr;
+    JSValueRef result = evaluateScriptInContext(objectContext.get(), String::fromUTF8(code, length < 0 ? strlen(code) : length), uri, lineNumber, &exception);
     if (jscContextHandleExceptionIfNeeded(context, exception))
         return jsc_value_new_undefined(context);
 
+    *object = jscContextGetOrCreateValue(context, JSContextGetGlobalObject(objectContext.get())).leakRef();
+
     return jscContextGetOrCreateValue(context, result).leakRef();
 }
 
index af66dd1..e67cbbc 100644 (file)
@@ -111,7 +111,17 @@ JSC_API JSCValue *
 jsc_context_evaluate_with_source_uri (JSCContext         *context,
                                       const char         *code,
                                       gssize              length,
-                                      const char         *uri);
+                                      const char         *uri,
+                                      guint               line_number);
+
+JSC_API JSCValue *
+jsc_context_evaluate_in_object       (JSCContext         *context,
+                                      const char         *code,
+                                      gssize              length,
+                                      JSCClass           *object_class,
+                                      const char         *uri,
+                                      guint               line_number,
+                                      JSCValue          **object);
 
 JSC_API JSCValue *
 jsc_context_get_global_object        (JSCContext         *context);
index 8d9800a..905046c 100644 (file)
@@ -38,6 +38,7 @@ jsc_context_pop_exception_handler
 jsc_context_get_current
 jsc_context_evaluate
 jsc_context_evaluate_with_source_uri
+jsc_context_evaluate_in_object
 jsc_context_get_global_object
 jsc_context_set_value
 jsc_context_get_value
index 6d20a95..7bbb04e 100644 (file)
@@ -1,3 +1,26 @@
+2018-07-15  Carlos Garcia Campos  <cgarcia@igalia.com>
+
+        [GLIB] Add API to evaluate code using a given object to store global symbols
+        https://bugs.webkit.org/show_bug.cgi?id=187639
+
+        Reviewed by Michael Catanzaro.
+
+        Add jsc_context_evaluate_in_object(). It returns a new object as an out parameter. Global symbols in the
+        evaluated script are added as properties to the new object instead of to the context global object. This is
+        similar to JS::Evaluate in spider monkey when a scopeChain parameter is passed, but JSC doesn't support using a
+        scope for assignments, so we have to create a new context and get its global object. This patch also updates
+        jsc_context_evaluate_with_source_uri() to receive the starting line number for consistency with the new
+        jsc_context_evaluate_in_object().
+
+        * API/glib/JSCContext.cpp:
+        (jsc_context_evaluate): Pass 0 as line number to jsc_context_evaluate_with_source_uri().
+        (evaluateScriptInContext): Helper function to evaluate a script in a JSGlobalContextRef.
+        (jsc_context_evaluate_with_source_uri): Use evaluateScriptInContext().
+        (jsc_context_evaluate_in_object): Create a new context and set the main context global object as extension
+        scope of it. Evaluate the script in the new context and get its global object to be returned as parameter.
+        * API/glib/JSCContext.h:
+        * API/glib/docs/jsc-glib-4.0-sections.txt:
+
 2018-07-13  Yusuke Suzuki  <utatane.tea@gmail.com>
 
         [32bit JSC tests]  stress/cow-convert-double-to-contiguous.js and stress/cow-convert-int32-to-contiguous.js are failing
index 6be4f45..42cc068 100644 (file)
@@ -1,3 +1,17 @@
+2018-07-15  Carlos Garcia Campos  <cgarcia@igalia.com>
+
+        [GLIB] Add API to evaluate code using a given object to store global symbols
+        https://bugs.webkit.org/show_bug.cgi?id=187639
+
+        Reviewed by Michael Catanzaro.
+
+        Add a new test case.
+
+        * TestWebKitAPI/Tests/JavaScriptCore/glib/TestJSC.cpp:
+        (testJSCEvaluateInObject):
+        (testJSCExceptions):
+        (main):
+
 2018-07-15  Zan Dobersek  <zdobersek@igalia.com>
 
         [webkitpy] run-web-platform-tests should allow specifying custom WPT metadata directories
index 9dceaad..f122a4e 100644 (file)
@@ -607,6 +607,118 @@ static void testJSCGlobalObject()
     g_assert_true(window2.get() == globalObject.get());
 }
 
+static void testJSCEvaluateInObject()
+{
+    LeakChecker checker;
+    GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
+    checker.watch(context.get());
+    ExceptionHandler exceptionHandler(context.get());
+
+    GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "g = 54; function foo() { return 42; }", -1));
+    checker.watch(result.get());
+
+    GRefPtr<JSCValue> globalObject = adoptGRef(jsc_context_get_global_object(context.get()));
+    checker.watch(globalObject.get());
+
+    GRefPtr<JSCValue> rootFoo = adoptGRef(jsc_value_object_get_property(globalObject.get(), "foo"));
+    checker.watch(rootFoo.get());
+    g_assert(jsc_value_is_function(rootFoo.get()));
+    result = adoptGRef(jsc_value_function_call(rootFoo.get(), G_TYPE_NONE));
+    checker.watch(result.get());
+    g_assert_true(jsc_value_is_number(result.get()));
+    g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 42);
+    GRefPtr<JSCValue> value = adoptGRef(jsc_context_evaluate(context.get(), "foo()", -1));
+    checker.watch(value.get());
+    g_assert_true(value.get() == result.get());
+
+    GRefPtr<JSCValue> module;
+    result = adoptGRef(jsc_context_evaluate_in_object(context.get(), "function bar() { return g; }", -1, nullptr, nullptr, 1, &module.outPtr()));
+    checker.watch(result.get());
+    checker.watch(module.get());
+    g_assert_true(JSC_IS_VALUE(module.get()));
+    g_assert_true(jsc_value_is_object(module.get()));
+    GUniquePtr<char> valueString(jsc_value_to_string(module.get()));
+    g_assert_cmpstr(valueString.get(), ==, "[object GlobalObject]");
+    jsc_context_set_value(context.get(), "module", module.get());
+
+    GRefPtr<JSCValue> bar = adoptGRef(jsc_value_object_get_property(module.get(), "bar"));
+    checker.watch(bar.get());
+    g_assert_true(jsc_value_is_function(bar.get()));
+    result = adoptGRef(jsc_value_function_call(bar.get(), G_TYPE_NONE));
+    checker.watch(result.get());
+    g_assert_true(jsc_value_is_number(result.get()));
+    g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 54);
+    value = adoptGRef(jsc_context_evaluate(context.get(), "module.bar()", -1));
+    checker.watch(value.get());
+    g_assert_true(value.get() == result.get());
+
+    bar = adoptGRef(jsc_context_get_value(context.get(), "bar"));
+    checker.watch(bar.get());
+    g_assert_true(jsc_value_is_undefined(bar.get()));
+
+    JSCClass* jscClass = jsc_context_register_class(context.get(), "Module", nullptr, nullptr, nullptr);
+    checker.watch(jscClass);
+    GRefPtr<JSCValue> moduleWithClass;
+    result = adoptGRef(jsc_context_evaluate_in_object(context.get(), "function baz() { return foo(); }", -1, jscClass, nullptr, 1, &moduleWithClass.outPtr()));
+    checker.watch(result.get());
+    checker.watch(moduleWithClass.get());
+    g_assert_true(JSC_IS_VALUE(moduleWithClass.get()));
+    g_assert_true(jsc_value_is_object(moduleWithClass.get()));
+    valueString.reset(jsc_value_to_string(moduleWithClass.get()));
+    g_assert_cmpstr(valueString.get(), ==, "[object Module]");
+    jsc_context_set_value(context.get(), "moduleWithClass", moduleWithClass.get());
+
+    GRefPtr<JSCValue> baz = adoptGRef(jsc_value_object_get_property(moduleWithClass.get(), "baz"));
+    checker.watch(baz.get());
+    g_assert_true(jsc_value_is_function(baz.get()));
+    result = adoptGRef(jsc_value_function_call(baz.get(), G_TYPE_NONE));
+    checker.watch(result.get());
+    g_assert_true(jsc_value_is_number(result.get()));
+    g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 42);
+    value = adoptGRef(jsc_context_evaluate(context.get(), "moduleWithClass.baz()", -1));
+    checker.watch(value.get());
+    g_assert_true(value.get() == result.get());
+
+    bar = adoptGRef(jsc_value_object_get_property(moduleWithClass.get(), "bar"));
+    checker.watch(bar.get());
+    g_assert_true(jsc_value_is_undefined(bar.get()));
+
+    baz = adoptGRef(jsc_value_object_get_property(module.get(), "baz"));
+    checker.watch(baz.get());
+    g_assert_true(jsc_value_is_undefined(baz.get()));
+
+    baz = adoptGRef(jsc_context_get_value(context.get(), "baz"));
+    checker.watch(baz.get());
+    g_assert_true(jsc_value_is_undefined(baz.get()));
+
+    GRefPtr<JSCValue> jsNamespace = adoptGRef(jsc_value_new_object(context.get(), nullptr, nullptr));
+    checker.watch(jsNamespace.get());
+    jsc_context_set_value(context.get(), "wk", jsNamespace.get());
+
+    GRefPtr<JSCValue> moduleInWk;
+    result = adoptGRef(jsc_context_evaluate_in_object(context.get(), "function bar() { return g; }", -1, nullptr, nullptr, 1, &moduleInWk.outPtr()));
+    checker.watch(result.get());
+    checker.watch(moduleInWk.get());
+    g_assert_true(JSC_IS_VALUE(moduleInWk.get()));
+    g_assert_true(jsc_value_is_object(moduleInWk.get()));
+    jsc_value_object_set_property(jsNamespace.get(), "moduleInWk", moduleInWk.get());
+
+    bar = adoptGRef(jsc_value_object_get_property(moduleInWk.get(), "bar"));
+    checker.watch(bar.get());
+    g_assert_true(jsc_value_is_function(bar.get()));
+    result = adoptGRef(jsc_value_function_call(bar.get(), G_TYPE_NONE));
+    checker.watch(result.get());
+    g_assert_true(jsc_value_is_number(result.get()));
+    g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 54);
+    value = adoptGRef(jsc_context_evaluate(context.get(), "wk.moduleInWk.bar()", -1));
+    checker.watch(value.get());
+    g_assert_true(value.get() == result.get());
+
+    moduleInWk = adoptGRef(jsc_context_get_value(context.get(), "moduleInWk"));
+    checker.watch(moduleInWk.get());
+    g_assert_true(jsc_value_is_undefined(moduleInWk.get()));
+}
+
 static int foo(int n)
 {
     return n * 2;
@@ -2334,7 +2446,7 @@ static void testJSCExceptions()
         checker.watch(context.get());
         g_assert_false(jsc_context_get_exception(context.get()));
 
-        GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate_with_source_uri(context.get(), "foo", -1, "file:///foo/script.js"));
+        GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate_with_source_uri(context.get(), "foo", -1, "file:///foo/script.js", 3));
         checker.watch(result.get());
 
         g_assert_true(jsc_value_is_undefined(result.get()));
@@ -2342,6 +2454,7 @@ static void testJSCExceptions()
         g_assert_true(JSC_IS_EXCEPTION(exception));
         checker.watch(exception);
         g_assert_cmpstr(jsc_exception_get_source_uri(exception), ==, "file:///foo/script.js");
+        g_assert_cmpuint(jsc_exception_get_line_number(exception), ==, 3);
 
         jsc_context_clear_exception(context.get());
         g_assert_null(jsc_context_get_exception(context.get()));
@@ -2884,6 +2997,7 @@ int main(int argc, char** argv)
     g_test_add_func("/jsc/basic", testJSCBasic);
     g_test_add_func("/jsc/types", testJSCTypes);
     g_test_add_func("/jsc/global-object", testJSCGlobalObject);
+    g_test_add_func("/jsc/evaluate-in-object", testJSCEvaluateInObject);
     g_test_add_func("/jsc/function", testJSCFunction);
     g_test_add_func("/jsc/object", testJSCObject);
     g_test_add_func("/jsc/class", testJSCClass);