Changeset 266236 in webkit for trunk/Source/JavaScriptCore


Ignore:
Timestamp:
Aug 27, 2020, 9:41:27 AM (5 years ago)
Author:
[email protected]
Message:

JSClassRef should work with JS class syntax.
https://bugs.webkit.org/show_bug.cgi?id=215047

Reviewed by Darin Adler.

This is done by checking if value returned by the
callAsConstructor parameter to JSObjectMakeConstructor returns an
object allocated as the jsClass parameter. When that happens we
replace the prototype of the returned object with the prototype of
the new.target. Ideally we would have passed the derived classes
constructor from the beginning of our support for JS subclassing
but at this point that's probably not compatible with too many
applications.

  • API/APICallbackFunction.h:

(JSC::APICallbackFunction::construct):

  • API/JSObjectRef.h:
  • API/tests/testapi.cpp:

(APIString::APIString):
(TestAPI::markedJSValueArrayAndGC):
(TestAPI::classDefinitionWithJSSubclass):
(testCAPIViaCpp):

  • API/tests/testapi.mm:

(testObjectiveCAPI):

Location:
trunk/Source/JavaScriptCore
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/API/APICallbackFunction.h

    r261464 r266236  
    8080    VM& vm = getVM(globalObject);
    8181    auto scope = DECLARE_THROW_SCOPE(vm);
    82     JSObject* constructor = callFrame->jsCallee();
     82    JSValue callee = callFrame->jsCallee();
     83    T* constructor = jsCast<T*>(callFrame->jsCallee());
    8384    JSContextRef ctx = toRef(globalObject);
    8485    JSObjectRef constructorRef = toRef(constructor);
    8586
    86     JSObjectCallAsConstructorCallback callback = jsCast<T*>(constructor)->constructCallback();
     87    JSObjectCallAsConstructorCallback callback = constructor->constructCallback();
    8788    if (callback) {
     89        JSValue prototype;
     90        JSValue newTarget = callFrame->newTarget();
     91        // If we are doing a derived class construction get the .prototype property off the new target first so we behave closer to normal JS.
     92        if (newTarget != constructor) {
     93            prototype = newTarget.get(globalObject, vm.propertyNames->prototype);
     94            RETURN_IF_EXCEPTION(scope, { });
     95        }
     96
    8897        size_t argumentCount = callFrame->argumentCount();
    8998        Vector<JSValueRef, 16> arguments;
     
    98107            result = callback(ctx, constructorRef, argumentCount, arguments.data(), &exception);
    99108        }
     109
    100110        if (exception) {
    101111            throwException(globalObject, scope, toJS(globalObject, exception));
     
    105115        if (!result)
    106116            return throwVMTypeError(globalObject, scope);
    107         return JSValue::encode(toJS(result));
     117
     118        JSObject* newObject = toJS(result);
     119        // This won't trigger proxy traps on newObject's prototype handler but that's probably desirable here anyway.
     120        if (newTarget != constructor && newObject->getPrototypeDirect(vm) == constructor->get(globalObject, vm.propertyNames->prototype)) {
     121            RETURN_IF_EXCEPTION(scope, { });
     122            newObject->setPrototype(vm, globalObject, prototype);
     123            RETURN_IF_EXCEPTION(scope, { });
     124        }
     125
     126        return JSValue::encode(newObject);
    108127    }
    109128   
    110     return JSValue::encode(toJS(JSObjectMake(ctx, jsCast<JSCallbackConstructor*>(constructor)->classRef(), nullptr)));
     129    return JSValue::encode(toJS(JSObjectMake(ctx, jsCast<JSCallbackConstructor*>(callee)->classRef(), nullptr)));
    111130}
    112131
  • trunk/Source/JavaScriptCore/API/JSObjectRef.h

    r248833 r266236  
    340340
    341341A NULL callback specifies that the default object callback should substitute, except in the case of hasProperty, where it specifies that getProperty should substitute.
     342
     343It is not possible to use JS subclassing with objects created from a class definition that sets callAsConstructor by default. Subclassing is supported via the JSObjectMakeConstructor function, however.
    342344*/
    343345typedef struct {
     
    427429@param callAsConstructor A JSObjectCallAsConstructorCallback to invoke when your constructor is used in a 'new' expression. Pass NULL to use the default object constructor.
    428430@result A JSObject that is a constructor. The object's prototype will be the default object prototype.
    429 @discussion The default object constructor takes no arguments and constructs an object of class jsClass with no private data.
     431@discussion The default object constructor takes no arguments and constructs an object of class jsClass with no private data. If the constructor is inherited via JS subclassing and the value returned from callAsConstructor was created with jsClass, then the returned object will have it's prototype overridden to the derived class's prototype.
    430432*/
    431433JS_EXPORT JSObjectRef JSObjectMakeConstructor(JSContextRef ctx, JSClassRef jsClass, JSObjectCallAsConstructorCallback callAsConstructor);
  • trunk/Source/JavaScriptCore/API/tests/testapi.cpp

    r261755 r266236  
    3030#include "MarkedJSValueRefArray.h"
    3131#include <JavaScriptCore/JSContextRefPrivate.h>
     32#include <JavaScriptCore/JSObjectRefPrivate.h>
    3233#include <JavaScriptCore/JavaScript.h>
    3334#include <wtf/DataLog.h>
     
    4849    APIString(const char* string)
    4950        : m_string(JSStringCreateWithUTF8CString(string))
     51    {
     52    }
     53
     54    APIString(const String& string)
     55        : APIString(string.utf8().data())
    5056    {
    5157    }
     
    144150    void topCallFrameAccess();
    145151    void markedJSValueArrayAndGC();
     152    void classDefinitionWithJSSubclass();
     153    void proxyReturnedWithJSSubclassing();
    146154
    147155    int failed() const { return m_failed; }
     
    163171    bool functionReturnsTrue(const char* functionSource, ArgumentTypes... arguments);
    164172
     173    bool scriptResultIs(ScriptResult, JSValueRef);
     174
    165175    // Ways to make sets of interesting things.
    166176    APIVector<JSObjectRef> interestingObjects();
     
    237247        return false;
    238248    return JSValueIsStrictEqual(context, trueValue, result.value());
     249}
     250
     251bool TestAPI::scriptResultIs(ScriptResult result, JSValueRef value)
     252{
     253    if (!result)
     254        return false;
     255    return JSValueIsStrictEqual(context, result.value(), value);
    239256}
    240257
     
    622639void TestAPI::markedJSValueArrayAndGC()
    623640{
    624     auto testMarkedJSValueArray = [&](unsigned count) {
     641    auto testMarkedJSValueArray = [&] (unsigned count) {
    625642        auto* globalObject = toJS(context);
    626643        JSC::JSLockHolder locker(globalObject->vm());
    627644        JSC::MarkedJSValueRefArray values(context, count);
    628645        for (unsigned index = 0; index < count; ++index) {
    629             String target = makeString("Prefix", index);
    630             auto holder = OpaqueJSString::tryCreate(target);
    631             JSValueRef string = JSValueMakeString(context, holder.get());
     646            JSValueRef string = JSValueMakeString(context, APIString(makeString("Prefix", index)));
    632647            values[index] = string;
    633648        }
     
    635650        bool ok = true;
    636651        for (unsigned index = 0; index < count; ++index) {
    637             String target = makeString("Prefix", index);
    638             auto holder = OpaqueJSString::tryCreate(target);
    639             JSValueRef string = JSValueMakeString(context, holder.get());
     652            JSValueRef string = JSValueMakeString(context, APIString(makeString("Prefix", index)));
    640653            if (!JSValueIsStrictEqual(context, values[index], string))
    641654                ok = false;
     
    645658    testMarkedJSValueArray(4);
    646659    testMarkedJSValueArray(1000);
     660}
     661
     662void TestAPI::classDefinitionWithJSSubclass()
     663{
     664    const static JSClassDefinition definition = kJSClassDefinitionEmpty;
     665    static JSClassRef jsClass = JSClassCreate(&definition);
     666
     667    auto constructor = [] (JSContextRef ctx, JSObjectRef, size_t, const JSValueRef*, JSValueRef*) -> JSObjectRef {
     668        return JSObjectMake(ctx, jsClass, nullptr);
     669    };
     670
     671    JSObjectRef Superclass = JSObjectMakeConstructor(context, jsClass, constructor);
     672
     673    ScriptResult result = callFunction("(function (Superclass) { class Subclass extends Superclass { method() { return 'value'; } }; return new Subclass(); })", Superclass);
     674    check(!!result, "creating a subclass should not throw.");
     675    check(JSValueIsObject(context, result.value()), "result of construction should have been an object.");
     676    JSObjectRef subclass = const_cast<JSObjectRef>(result.value());
     677
     678    check(JSObjectHasProperty(context, subclass, APIString("method")), "subclass should have derived classes functions.");
     679    check(functionReturnsTrue("(function (subclass, Superclass) { return subclass instanceof Superclass; })", subclass, Superclass), "JS subclass should instanceof the Superclass");
     680
     681    JSClassRelease(jsClass);
     682}
     683
     684void TestAPI::proxyReturnedWithJSSubclassing()
     685{
     686    const static JSClassDefinition definition = kJSClassDefinitionEmpty;
     687    static JSClassRef jsClass = JSClassCreate(&definition);
     688    static TestAPI& test = *this;
     689
     690    auto constructor = [] (JSContextRef ctx, JSObjectRef, size_t, const JSValueRef*, JSValueRef*) -> JSObjectRef {
     691        ScriptResult result = test.callFunction("(function (object) { return new Proxy(object, { getPrototypeOf: () => { globalThis.triggeredProxy = true; return object.__proto__; }}); })", JSObjectMake(ctx, jsClass, nullptr));
     692        test.check(!!result, "creating a proxy should not throw");
     693        test.check(JSValueIsObject(ctx, result.value()), "result of proxy creation should have been an object.");
     694        return const_cast<JSObjectRef>(result.value());
     695    };
     696
     697    JSObjectRef Superclass = JSObjectMakeConstructor(context, jsClass, constructor);
     698
     699    ScriptResult result = callFunction("(function (Superclass) { class Subclass extends Superclass { method() { return 'value'; } }; return new Subclass(); })", Superclass);
     700    check(!!result, "creating a subclass should not throw.");
     701    check(JSValueIsObject(context, result.value()), "result of construction should have been an object.");
     702    JSObjectRef subclass = const_cast<JSObjectRef>(result.value());
     703
     704    check(scriptResultIs(evaluateScript("globalThis.triggeredProxy"), JSValueMakeUndefined(context)), "creating a subclass should not have triggered the proxy");
     705    check(functionReturnsTrue("(function (subclass, Superclass) { return subclass.__proto__ == Superclass.prototype; })", subclass, Superclass), "proxy's prototype should match Superclass.prototype");
    647706}
    648707
     
    687746    RUN(promiseEarlyHandledRejections());
    688747    RUN(markedJSValueArrayAndGC());
     748    RUN(classDefinitionWithJSSubclass());
     749    RUN(proxyReturnedWithJSSubclassing());
    689750
    690751    if (tasks.isEmpty()) {
  • trunk/Source/JavaScriptCore/API/tests/testapi.mm

    r266030 r266236  
    28602860    RUN(parallelPromiseResolveTest());
    28612861
    2862     testObjectiveCAPIMain();
     2862    if (!filter)
     2863        testObjectiveCAPIMain();
    28632864}
    28642865
  • trunk/Source/JavaScriptCore/ChangeLog

    r266223 r266236  
     12020-08-27  Keith Miller  <[email protected]>
     2
     3        JSClassRef should work with JS class syntax.
     4        https://bugs.webkit.org/show_bug.cgi?id=215047
     5
     6        Reviewed by Darin Adler.
     7
     8        This is done by checking if value returned by the
     9        callAsConstructor parameter to JSObjectMakeConstructor returns an
     10        object allocated as the jsClass parameter. When that happens we
     11        replace the prototype of the returned object with the prototype of
     12        the new.target. Ideally we would have passed the derived classes
     13        constructor from the beginning of our support for JS subclassing
     14        but at this point that's probably not compatible with too many
     15        applications.
     16
     17        * API/APICallbackFunction.h:
     18        (JSC::APICallbackFunction::construct):
     19        * API/JSObjectRef.h:
     20        * API/tests/testapi.cpp:
     21        (APIString::APIString):
     22        (TestAPI::markedJSValueArrayAndGC):
     23        (TestAPI::classDefinitionWithJSSubclass):
     24        (testCAPIViaCpp):
     25        * API/tests/testapi.mm:
     26        (testObjectiveCAPI):
     27
    1282020-08-26  Alexey Shvayka  <[email protected]>
    229
Note: See TracChangeset for help on using the changeset viewer.