mirror of
https://github.com/darlinghq/darling-JavaScriptCore.git
synced 2024-11-23 04:09:40 +00:00
766 lines
27 KiB
Plaintext
766 lines
27 KiB
Plaintext
/*
|
|
* Copyright (C) 2013-2020 Apple Inc. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
|
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
|
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#import "config.h"
|
|
#import "JavaScriptCore.h"
|
|
|
|
#if JSC_OBJC_API_ENABLED
|
|
|
|
#import "APICallbackFunction.h"
|
|
#import "APICast.h"
|
|
#import "Error.h"
|
|
#import "JSCell.h"
|
|
#import "JSCInlines.h"
|
|
#import "JSContextInternal.h"
|
|
#import "JSWrapperMap.h"
|
|
#import "JSValueInternal.h"
|
|
#import "ObjCCallbackFunction.h"
|
|
#import "ObjcRuntimeExtras.h"
|
|
#import "StructureInlines.h"
|
|
#import <objc/runtime.h>
|
|
#import <wtf/RetainPtr.h>
|
|
|
|
class CallbackArgument {
|
|
WTF_MAKE_FAST_ALLOCATED;
|
|
public:
|
|
virtual ~CallbackArgument();
|
|
virtual void set(NSInvocation *, NSInteger, JSContext *, JSValueRef, JSValueRef*) = 0;
|
|
|
|
std::unique_ptr<CallbackArgument> m_next;
|
|
};
|
|
|
|
CallbackArgument::~CallbackArgument()
|
|
{
|
|
}
|
|
|
|
class CallbackArgumentBoolean final : public CallbackArgument {
|
|
void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef*) final
|
|
{
|
|
bool value = JSValueToBoolean([context JSGlobalContextRef], argument);
|
|
[invocation setArgument:&value atIndex:argumentNumber];
|
|
}
|
|
};
|
|
|
|
template<typename T>
|
|
class CallbackArgumentInteger final : public CallbackArgument {
|
|
void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef* exception) final
|
|
{
|
|
ASSERT(exception && !*exception);
|
|
T value = (T)JSC::toInt32(JSValueToNumber([context JSGlobalContextRef], argument, exception));
|
|
if (*exception)
|
|
return;
|
|
[invocation setArgument:&value atIndex:argumentNumber];
|
|
}
|
|
};
|
|
|
|
template<typename T>
|
|
class CallbackArgumentDouble final : public CallbackArgument {
|
|
void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef* exception) final
|
|
{
|
|
ASSERT(exception && !*exception);
|
|
T value = (T)JSValueToNumber([context JSGlobalContextRef], argument, exception);
|
|
if (*exception)
|
|
return;
|
|
[invocation setArgument:&value atIndex:argumentNumber];
|
|
}
|
|
};
|
|
|
|
class CallbackArgumentJSValue final : public CallbackArgument {
|
|
void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef*) final
|
|
{
|
|
JSValue *value = [JSValue valueWithJSValueRef:argument inContext:context];
|
|
[invocation setArgument:&value atIndex:argumentNumber];
|
|
}
|
|
};
|
|
|
|
class CallbackArgumentId final : public CallbackArgument {
|
|
void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef*) final
|
|
{
|
|
id value = valueToObject(context, argument);
|
|
[invocation setArgument:&value atIndex:argumentNumber];
|
|
}
|
|
};
|
|
|
|
class CallbackArgumentOfClass final : public CallbackArgument {
|
|
public:
|
|
CallbackArgumentOfClass(Class cls)
|
|
: m_class(cls)
|
|
{
|
|
}
|
|
|
|
private:
|
|
void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef* exception) final
|
|
{
|
|
ASSERT(exception && !*exception);
|
|
JSGlobalContextRef contextRef = [context JSGlobalContextRef];
|
|
|
|
id object = tryUnwrapObjcObject(contextRef, argument);
|
|
if (object && [object isKindOfClass:m_class.get()]) {
|
|
[invocation setArgument:&object atIndex:argumentNumber];
|
|
return;
|
|
}
|
|
|
|
if (JSValueIsNull(contextRef, argument) || JSValueIsUndefined(contextRef, argument)) {
|
|
object = nil;
|
|
[invocation setArgument:&object atIndex:argumentNumber];
|
|
return;
|
|
}
|
|
|
|
*exception = toRef(JSC::createTypeError(toJS(contextRef), "Argument does not match Objective-C Class"_s));
|
|
}
|
|
|
|
RetainPtr<Class> m_class;
|
|
};
|
|
|
|
class CallbackArgumentNSNumber final : public CallbackArgument {
|
|
void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef* exception) final
|
|
{
|
|
ASSERT(exception && !*exception);
|
|
id value = valueToNumber([context JSGlobalContextRef], argument, exception);
|
|
if (*exception)
|
|
return;
|
|
[invocation setArgument:&value atIndex:argumentNumber];
|
|
}
|
|
};
|
|
|
|
class CallbackArgumentNSString final : public CallbackArgument {
|
|
void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef* exception) final
|
|
{
|
|
ASSERT(exception && !*exception);
|
|
id value = valueToString([context JSGlobalContextRef], argument, exception);
|
|
if (*exception)
|
|
return;
|
|
[invocation setArgument:&value atIndex:argumentNumber];
|
|
}
|
|
};
|
|
|
|
class CallbackArgumentNSDate final : public CallbackArgument {
|
|
void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef* exception) final
|
|
{
|
|
ASSERT(exception && !*exception);
|
|
id value = valueToDate([context JSGlobalContextRef], argument, exception);
|
|
if (*exception)
|
|
return;
|
|
[invocation setArgument:&value atIndex:argumentNumber];
|
|
}
|
|
};
|
|
|
|
class CallbackArgumentNSArray final : public CallbackArgument {
|
|
void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef* exception) final
|
|
{
|
|
ASSERT(exception && !*exception);
|
|
id value = valueToArray([context JSGlobalContextRef], argument, exception);
|
|
if (*exception)
|
|
return;
|
|
[invocation setArgument:&value atIndex:argumentNumber];
|
|
}
|
|
};
|
|
|
|
class CallbackArgumentNSDictionary final : public CallbackArgument {
|
|
void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef* exception) final
|
|
{
|
|
ASSERT(exception && !*exception);
|
|
id value = valueToDictionary([context JSGlobalContextRef], argument, exception);
|
|
if (*exception)
|
|
return;
|
|
[invocation setArgument:&value atIndex:argumentNumber];
|
|
}
|
|
};
|
|
|
|
class CallbackArgumentStruct final : public CallbackArgument {
|
|
public:
|
|
CallbackArgumentStruct(NSInvocation *conversionInvocation, const char* encodedType)
|
|
: m_conversionInvocation(conversionInvocation)
|
|
, m_buffer(encodedType)
|
|
{
|
|
}
|
|
|
|
private:
|
|
void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef*) final
|
|
{
|
|
JSValue *value = [JSValue valueWithJSValueRef:argument inContext:context];
|
|
[m_conversionInvocation invokeWithTarget:value];
|
|
[m_conversionInvocation getReturnValue:m_buffer];
|
|
[invocation setArgument:m_buffer atIndex:argumentNumber];
|
|
}
|
|
|
|
RetainPtr<NSInvocation> m_conversionInvocation;
|
|
StructBuffer m_buffer;
|
|
};
|
|
|
|
class ArgumentTypeDelegate final {
|
|
public:
|
|
typedef std::unique_ptr<CallbackArgument> ResultType;
|
|
|
|
template<typename T>
|
|
static ResultType typeInteger()
|
|
{
|
|
return makeUnique<CallbackArgumentInteger<T>>();
|
|
}
|
|
|
|
template<typename T>
|
|
static ResultType typeDouble()
|
|
{
|
|
return makeUnique<CallbackArgumentDouble<T>>();
|
|
}
|
|
|
|
static ResultType typeBool()
|
|
{
|
|
return makeUnique<CallbackArgumentBoolean>();
|
|
}
|
|
|
|
static ResultType typeVoid()
|
|
{
|
|
RELEASE_ASSERT_NOT_REACHED();
|
|
return nullptr;
|
|
}
|
|
|
|
static ResultType typeId()
|
|
{
|
|
return makeUnique<CallbackArgumentId>();
|
|
}
|
|
|
|
static ResultType typeOfClass(const char* begin, const char* end)
|
|
{
|
|
StringRange copy(begin, end);
|
|
Class cls = objc_getClass(copy);
|
|
if (!cls)
|
|
return nullptr;
|
|
|
|
if (cls == [JSValue class])
|
|
return makeUnique<CallbackArgumentJSValue>();
|
|
if (cls == [NSString class])
|
|
return makeUnique<CallbackArgumentNSString>();
|
|
if (cls == [NSNumber class])
|
|
return makeUnique<CallbackArgumentNSNumber>();
|
|
if (cls == [NSDate class])
|
|
return makeUnique<CallbackArgumentNSDate>();
|
|
if (cls == [NSArray class])
|
|
return makeUnique<CallbackArgumentNSArray>();
|
|
if (cls == [NSDictionary class])
|
|
return makeUnique<CallbackArgumentNSDictionary>();
|
|
|
|
return makeUnique<CallbackArgumentOfClass>(cls);
|
|
}
|
|
|
|
static ResultType typeBlock(const char*, const char*)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
static ResultType typeStruct(const char* begin, const char* end)
|
|
{
|
|
StringRange copy(begin, end);
|
|
if (NSInvocation *invocation = valueToTypeInvocationFor(copy))
|
|
return makeUnique<CallbackArgumentStruct>(invocation, copy);
|
|
return nullptr;
|
|
}
|
|
};
|
|
|
|
class CallbackResult {
|
|
WTF_MAKE_FAST_ALLOCATED;
|
|
public:
|
|
virtual ~CallbackResult()
|
|
{
|
|
}
|
|
|
|
virtual JSValueRef get(NSInvocation *, JSContext *, JSValueRef*) = 0;
|
|
};
|
|
|
|
class CallbackResultVoid final : public CallbackResult {
|
|
JSValueRef get(NSInvocation *, JSContext *context, JSValueRef*) final
|
|
{
|
|
return JSValueMakeUndefined([context JSGlobalContextRef]);
|
|
}
|
|
};
|
|
|
|
class CallbackResultId final : public CallbackResult {
|
|
JSValueRef get(NSInvocation *invocation, JSContext *context, JSValueRef*) final
|
|
{
|
|
id value;
|
|
[invocation getReturnValue:&value];
|
|
return objectToValue(context, value);
|
|
}
|
|
};
|
|
|
|
template<typename T>
|
|
class CallbackResultNumeric final : public CallbackResult {
|
|
JSValueRef get(NSInvocation *invocation, JSContext *context, JSValueRef*) final
|
|
{
|
|
T value;
|
|
[invocation getReturnValue:&value];
|
|
return JSValueMakeNumber([context JSGlobalContextRef], value);
|
|
}
|
|
};
|
|
|
|
class CallbackResultBoolean final : public CallbackResult {
|
|
JSValueRef get(NSInvocation *invocation, JSContext *context, JSValueRef*) final
|
|
{
|
|
bool value;
|
|
[invocation getReturnValue:&value];
|
|
return JSValueMakeBoolean([context JSGlobalContextRef], value);
|
|
}
|
|
};
|
|
|
|
class CallbackResultStruct final : public CallbackResult {
|
|
public:
|
|
CallbackResultStruct(NSInvocation *conversionInvocation, const char* encodedType)
|
|
: m_conversionInvocation(conversionInvocation)
|
|
, m_buffer(encodedType)
|
|
{
|
|
}
|
|
|
|
private:
|
|
JSValueRef get(NSInvocation *invocation, JSContext *context, JSValueRef*) final
|
|
{
|
|
[invocation getReturnValue:m_buffer];
|
|
|
|
[m_conversionInvocation setArgument:m_buffer atIndex:2];
|
|
[m_conversionInvocation setArgument:&context atIndex:3];
|
|
[m_conversionInvocation invokeWithTarget:[JSValue class]];
|
|
|
|
JSValue *value;
|
|
[m_conversionInvocation getReturnValue:&value];
|
|
return valueInternalValue(value);
|
|
}
|
|
|
|
RetainPtr<NSInvocation> m_conversionInvocation;
|
|
StructBuffer m_buffer;
|
|
};
|
|
|
|
class ResultTypeDelegate final {
|
|
public:
|
|
typedef std::unique_ptr<CallbackResult> ResultType;
|
|
|
|
template<typename T>
|
|
static ResultType typeInteger()
|
|
{
|
|
return makeUnique<CallbackResultNumeric<T>>();
|
|
}
|
|
|
|
template<typename T>
|
|
static ResultType typeDouble()
|
|
{
|
|
return makeUnique<CallbackResultNumeric<T>>();
|
|
}
|
|
|
|
static ResultType typeBool()
|
|
{
|
|
return makeUnique<CallbackResultBoolean>();
|
|
}
|
|
|
|
static ResultType typeVoid()
|
|
{
|
|
return makeUnique<CallbackResultVoid>();
|
|
}
|
|
|
|
static ResultType typeId()
|
|
{
|
|
return makeUnique<CallbackResultId>();
|
|
}
|
|
|
|
static ResultType typeOfClass(const char*, const char*)
|
|
{
|
|
return makeUnique<CallbackResultId>();
|
|
}
|
|
|
|
static ResultType typeBlock(const char*, const char*)
|
|
{
|
|
return makeUnique<CallbackResultId>();
|
|
}
|
|
|
|
static ResultType typeStruct(const char* begin, const char* end)
|
|
{
|
|
StringRange copy(begin, end);
|
|
if (NSInvocation *invocation = typeToValueInvocationFor(copy))
|
|
return makeUnique<CallbackResultStruct>(invocation, copy);
|
|
return nullptr;
|
|
}
|
|
};
|
|
|
|
enum CallbackType {
|
|
CallbackInitMethod,
|
|
CallbackInstanceMethod,
|
|
CallbackClassMethod,
|
|
CallbackBlock
|
|
};
|
|
|
|
namespace JSC {
|
|
|
|
class ObjCCallbackFunctionImpl final {
|
|
WTF_MAKE_FAST_ALLOCATED;
|
|
public:
|
|
ObjCCallbackFunctionImpl(NSInvocation *invocation, CallbackType type, Class instanceClass, std::unique_ptr<CallbackArgument> arguments, std::unique_ptr<CallbackResult> result)
|
|
: m_type(type)
|
|
, m_instanceClass(instanceClass)
|
|
, m_invocation(invocation)
|
|
, m_arguments(WTFMove(arguments))
|
|
, m_result(WTFMove(result))
|
|
{
|
|
ASSERT((type != CallbackInstanceMethod && type != CallbackInitMethod) || instanceClass);
|
|
}
|
|
|
|
void destroy(Heap& heap)
|
|
{
|
|
// We need to explicitly release the target since we didn't call
|
|
// -retainArguments on m_invocation (and we don't want to do so).
|
|
if (m_type == CallbackBlock || m_type == CallbackClassMethod)
|
|
heap.releaseSoon(adoptNS([m_invocation.get() target]));
|
|
m_instanceClass = nil;
|
|
}
|
|
|
|
JSValueRef call(JSContext *context, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception);
|
|
|
|
id wrappedBlock()
|
|
{
|
|
return m_type == CallbackBlock ? [m_invocation target] : nil;
|
|
}
|
|
|
|
id wrappedConstructor()
|
|
{
|
|
switch (m_type) {
|
|
case CallbackBlock:
|
|
return [m_invocation target];
|
|
case CallbackInitMethod:
|
|
return m_instanceClass.get();
|
|
default:
|
|
return nil;
|
|
}
|
|
}
|
|
|
|
CallbackType type() const { return m_type; }
|
|
|
|
bool isConstructible()
|
|
{
|
|
return !!wrappedBlock() || m_type == CallbackInitMethod;
|
|
}
|
|
|
|
String name();
|
|
|
|
private:
|
|
CallbackType m_type;
|
|
RetainPtr<Class> m_instanceClass;
|
|
RetainPtr<NSInvocation> m_invocation;
|
|
std::unique_ptr<CallbackArgument> m_arguments;
|
|
std::unique_ptr<CallbackResult> m_result;
|
|
};
|
|
|
|
static JSValueRef objCCallbackFunctionCallAsFunction(JSContextRef callerContext, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
|
|
{
|
|
ASSERT(exception && !*exception);
|
|
|
|
// Retake the API lock - we need this for a few reasons:
|
|
// (1) We don't want to support the C-API's confusing drops-locks-once policy - should only drop locks if we can do so recursively.
|
|
// (2) We're calling some JSC internals that require us to be on the 'inside' - e.g. createTypeError.
|
|
// (3) We need to be locked (per context would be fine) against conflicting usage of the ObjCCallbackFunction's NSInvocation.
|
|
JSC::JSLockHolder locker(toJS(callerContext));
|
|
|
|
ObjCCallbackFunction* callback = static_cast<ObjCCallbackFunction*>(toJS(function));
|
|
ObjCCallbackFunctionImpl* impl = callback->impl();
|
|
JSContext *context = [JSContext contextWithJSGlobalContextRef:toGlobalRef(callback->globalObject())];
|
|
|
|
if (impl->type() == CallbackInitMethod) {
|
|
JSGlobalContextRef contextRef = [context JSGlobalContextRef];
|
|
*exception = toRef(JSC::createTypeError(toJS(contextRef), "Cannot call a class constructor without |new|"_s));
|
|
if (*exception)
|
|
return nullptr;
|
|
return JSValueMakeUndefined(contextRef);
|
|
}
|
|
|
|
CallbackData callbackData;
|
|
JSValueRef result;
|
|
@autoreleasepool {
|
|
[context beginCallbackWithData:&callbackData calleeValue:function thisValue:thisObject argumentCount:argumentCount arguments:arguments];
|
|
result = impl->call(context, thisObject, argumentCount, arguments, exception);
|
|
if (context.exception)
|
|
*exception = valueInternalValue(context.exception);
|
|
[context endCallbackWithData:&callbackData];
|
|
}
|
|
if (*exception)
|
|
return nullptr;
|
|
return result;
|
|
}
|
|
|
|
static JSObjectRef objCCallbackFunctionCallAsConstructor(JSContextRef callerContext, JSObjectRef constructor, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
|
|
{
|
|
ASSERT(exception && !*exception);
|
|
JSC::JSLockHolder locker(toJS(callerContext));
|
|
|
|
ObjCCallbackFunction* callback = static_cast<ObjCCallbackFunction*>(toJS(constructor));
|
|
ObjCCallbackFunctionImpl* impl = callback->impl();
|
|
JSContext *context = [JSContext contextWithJSGlobalContextRef:toGlobalRef(toJS(callerContext))];
|
|
|
|
CallbackData callbackData;
|
|
JSValueRef result;
|
|
@autoreleasepool {
|
|
[context beginCallbackWithData:&callbackData calleeValue:constructor thisValue:nullptr argumentCount:argumentCount arguments:arguments];
|
|
result = impl->call(context, nullptr, argumentCount, arguments, exception);
|
|
if (context.exception)
|
|
*exception = valueInternalValue(context.exception);
|
|
[context endCallbackWithData:&callbackData];
|
|
}
|
|
if (*exception)
|
|
return nullptr;
|
|
|
|
JSGlobalContextRef contextRef = [context JSGlobalContextRef];
|
|
if (!JSValueIsObject(contextRef, result)) {
|
|
*exception = toRef(JSC::createTypeError(toJS(contextRef), "Objective-C blocks called as constructors must return an object."_s));
|
|
return nullptr;
|
|
}
|
|
ASSERT(!*exception);
|
|
return const_cast<JSObjectRef>(result);
|
|
}
|
|
|
|
const JSC::ClassInfo ObjCCallbackFunction::s_info = { "CallbackFunction", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(ObjCCallbackFunction) };
|
|
|
|
static JSC_DECLARE_HOST_FUNCTION(callObjCCallbackFunction);
|
|
static JSC_DECLARE_HOST_FUNCTION(constructObjCCallbackFunction);
|
|
|
|
JSC_DEFINE_HOST_FUNCTION(callObjCCallbackFunction, (JSGlobalObject* globalObject, CallFrame* callFrame))
|
|
{
|
|
return APICallbackFunction::callImpl<ObjCCallbackFunction>(globalObject, callFrame);
|
|
}
|
|
|
|
JSC_DEFINE_HOST_FUNCTION(constructObjCCallbackFunction, (JSGlobalObject* globalObject, CallFrame* callFrame))
|
|
{
|
|
return APICallbackFunction::constructImpl<ObjCCallbackFunction>(globalObject, callFrame);
|
|
}
|
|
|
|
ObjCCallbackFunction::ObjCCallbackFunction(JSC::VM& vm, JSC::Structure* structure, JSObjectCallAsFunctionCallback functionCallback, JSObjectCallAsConstructorCallback constructCallback, std::unique_ptr<ObjCCallbackFunctionImpl> impl)
|
|
: Base(vm, structure, callObjCCallbackFunction, impl->isConstructible() ? constructObjCCallbackFunction : nullptr)
|
|
, m_functionCallback(functionCallback)
|
|
, m_constructCallback(constructCallback)
|
|
, m_impl(WTFMove(impl))
|
|
{
|
|
}
|
|
|
|
ObjCCallbackFunction* ObjCCallbackFunction::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, const String& name, std::unique_ptr<ObjCCallbackFunctionImpl> impl)
|
|
{
|
|
Structure* structure = globalObject->objcCallbackFunctionStructure();
|
|
ObjCCallbackFunction* function = new (NotNull, allocateCell<ObjCCallbackFunction>(vm.heap)) ObjCCallbackFunction(vm, structure, objCCallbackFunctionCallAsFunction, objCCallbackFunctionCallAsConstructor, WTFMove(impl));
|
|
function->finishCreation(vm, 0, name);
|
|
return function;
|
|
}
|
|
|
|
void ObjCCallbackFunction::destroy(JSCell* cell)
|
|
{
|
|
ObjCCallbackFunction& function = *static_cast<ObjCCallbackFunction*>(cell);
|
|
function.impl()->destroy(*Heap::heap(cell));
|
|
function.~ObjCCallbackFunction();
|
|
}
|
|
|
|
String ObjCCallbackFunctionImpl::name()
|
|
{
|
|
if (m_type == CallbackInitMethod)
|
|
return class_getName(m_instanceClass.get());
|
|
// FIXME: Maybe we could support having the selector as the name of the non-init
|
|
// functions to make it a bit more user-friendly from the JS side?
|
|
return "";
|
|
}
|
|
|
|
JSValueRef ObjCCallbackFunctionImpl::call(JSContext *context, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
|
|
{
|
|
ASSERT(exception && !*exception);
|
|
JSGlobalContextRef contextRef = [context JSGlobalContextRef];
|
|
|
|
id target;
|
|
size_t firstArgument;
|
|
switch (m_type) {
|
|
case CallbackInitMethod: {
|
|
RELEASE_ASSERT(!thisObject);
|
|
target = [m_instanceClass alloc];
|
|
if (!target || ![target isKindOfClass:m_instanceClass.get()]) {
|
|
*exception = toRef(JSC::createTypeError(toJS(contextRef), "self type check failed for Objective-C instance method"_s));
|
|
if (*exception)
|
|
return nullptr;
|
|
return JSValueMakeUndefined(contextRef);
|
|
}
|
|
[m_invocation setTarget:target];
|
|
firstArgument = 2;
|
|
break;
|
|
}
|
|
case CallbackInstanceMethod: {
|
|
target = tryUnwrapObjcObject(contextRef, thisObject);
|
|
if (!target || ![target isKindOfClass:m_instanceClass.get()]) {
|
|
*exception = toRef(JSC::createTypeError(toJS(contextRef), "self type check failed for Objective-C instance method"_s));
|
|
if (*exception)
|
|
return nullptr;
|
|
return JSValueMakeUndefined(contextRef);
|
|
}
|
|
[m_invocation setTarget:target];
|
|
firstArgument = 2;
|
|
break;
|
|
}
|
|
case CallbackClassMethod:
|
|
firstArgument = 2;
|
|
break;
|
|
case CallbackBlock:
|
|
firstArgument = 1;
|
|
}
|
|
|
|
size_t argumentNumber = 0;
|
|
for (CallbackArgument* argument = m_arguments.get(); argument; argument = argument->m_next.get()) {
|
|
JSValueRef value = argumentNumber < argumentCount ? arguments[argumentNumber] : JSValueMakeUndefined(contextRef);
|
|
argument->set(m_invocation.get(), argumentNumber + firstArgument, context, value, exception);
|
|
if (*exception)
|
|
return nullptr;
|
|
++argumentNumber;
|
|
}
|
|
|
|
[m_invocation invoke];
|
|
|
|
JSValueRef result = m_result->get(m_invocation.get(), context, exception);
|
|
if (*exception)
|
|
return nullptr;
|
|
|
|
// Balance our call to -alloc with a call to -autorelease. We have to do this after calling -init
|
|
// because init family methods are allowed to release the allocated object and return something
|
|
// else in its place.
|
|
if (m_type == CallbackInitMethod) {
|
|
id objcResult = tryUnwrapObjcObject(contextRef, result);
|
|
if (objcResult)
|
|
[objcResult autorelease];
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
} // namespace JSC
|
|
|
|
static bool blockSignatureContainsClass()
|
|
{
|
|
static bool containsClass = ^{
|
|
id block = ^(NSString *string){ return string; };
|
|
return _Block_has_signature((__bridge void*)block) && strstr(_Block_signature((__bridge void*)block), "NSString");
|
|
}();
|
|
return containsClass;
|
|
}
|
|
|
|
static inline bool skipNumber(const char*& position)
|
|
{
|
|
if (!isASCIIDigit(*position))
|
|
return false;
|
|
while (isASCIIDigit(*++position)) { }
|
|
return true;
|
|
}
|
|
|
|
static JSObjectRef objCCallbackFunctionForInvocation(JSContext *context, NSInvocation *invocation, CallbackType type, Class instanceClass, const char* signatureWithObjcClasses)
|
|
{
|
|
if (!signatureWithObjcClasses)
|
|
return nullptr;
|
|
|
|
const char* position = signatureWithObjcClasses;
|
|
|
|
auto result = parseObjCType<ResultTypeDelegate>(position);
|
|
if (!result || !skipNumber(position))
|
|
return nullptr;
|
|
|
|
switch (type) {
|
|
case CallbackInitMethod:
|
|
case CallbackInstanceMethod:
|
|
case CallbackClassMethod:
|
|
// Methods are passed two implicit arguments - (id)self, and the selector.
|
|
if ('@' != *position++ || !skipNumber(position) || ':' != *position++ || !skipNumber(position))
|
|
return nullptr;
|
|
break;
|
|
case CallbackBlock:
|
|
// Blocks are passed one implicit argument - the block, of type "@?".
|
|
if (('@' != *position++) || ('?' != *position++) || !skipNumber(position))
|
|
return nullptr;
|
|
// Only allow arguments of type 'id' if the block signature contains the NS type information.
|
|
if ((!blockSignatureContainsClass() && strchr(position, '@')))
|
|
return nullptr;
|
|
break;
|
|
}
|
|
|
|
std::unique_ptr<CallbackArgument> arguments;
|
|
auto* nextArgument = &arguments;
|
|
unsigned argumentCount = 0;
|
|
while (*position) {
|
|
auto argument = parseObjCType<ArgumentTypeDelegate>(position);
|
|
if (!argument || !skipNumber(position))
|
|
return nullptr;
|
|
|
|
*nextArgument = WTFMove(argument);
|
|
nextArgument = &(*nextArgument)->m_next;
|
|
++argumentCount;
|
|
}
|
|
|
|
JSC::JSGlobalObject* globalObject = toJS([context JSGlobalContextRef]);
|
|
JSC::VM& vm = globalObject->vm();
|
|
JSC::JSLockHolder locker(vm);
|
|
auto impl = makeUnique<JSC::ObjCCallbackFunctionImpl>(invocation, type, instanceClass, WTFMove(arguments), WTFMove(result));
|
|
const String& name = impl->name();
|
|
return toRef(JSC::ObjCCallbackFunction::create(vm, globalObject, name, WTFMove(impl)));
|
|
}
|
|
|
|
JSObjectRef objCCallbackFunctionForInit(JSContext *context, Class cls, Protocol *protocol, SEL sel, const char* types)
|
|
{
|
|
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:types]];
|
|
[invocation setSelector:sel];
|
|
return objCCallbackFunctionForInvocation(context, invocation, CallbackInitMethod, cls, _protocol_getMethodTypeEncoding(protocol, sel, YES, YES));
|
|
}
|
|
|
|
JSObjectRef objCCallbackFunctionForMethod(JSContext *context, Class cls, Protocol *protocol, BOOL isInstanceMethod, SEL sel, const char* types)
|
|
{
|
|
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:types]];
|
|
[invocation setSelector:sel];
|
|
if (!isInstanceMethod) {
|
|
[invocation setTarget:cls];
|
|
// We need to retain the target Class because m_invocation doesn't retain it by default (and we don't want it to).
|
|
// FIXME: What releases it?
|
|
CFRetain((__bridge CFTypeRef)cls);
|
|
}
|
|
return objCCallbackFunctionForInvocation(context, invocation, isInstanceMethod ? CallbackInstanceMethod : CallbackClassMethod, isInstanceMethod ? cls : nil, _protocol_getMethodTypeEncoding(protocol, sel, YES, isInstanceMethod));
|
|
}
|
|
|
|
JSObjectRef objCCallbackFunctionForBlock(JSContext *context, id target)
|
|
{
|
|
if (!_Block_has_signature((__bridge void*)target))
|
|
return nullptr;
|
|
const char* signature = _Block_signature((__bridge void*)target);
|
|
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:signature]];
|
|
|
|
// We don't want to use -retainArguments because that leaks memory. Arguments
|
|
// would be retained indefinitely between invocations of the callback.
|
|
// Additionally, we copy the target because we want the block to stick around
|
|
// until the ObjCCallbackFunctionImpl is destroyed.
|
|
[invocation setTarget:[target copy]];
|
|
|
|
return objCCallbackFunctionForInvocation(context, invocation, CallbackBlock, nil, signature);
|
|
}
|
|
|
|
id tryUnwrapConstructor(JSC::VM* vm, JSObjectRef object)
|
|
{
|
|
if (!toJS(object)->inherits<JSC::ObjCCallbackFunction>(*vm))
|
|
return nil;
|
|
JSC::ObjCCallbackFunctionImpl* impl = static_cast<JSC::ObjCCallbackFunction*>(toJS(object))->impl();
|
|
if (!impl->isConstructible())
|
|
return nil;
|
|
return impl->wrappedConstructor();
|
|
}
|
|
|
|
#endif
|