#if JSC_OBJC_API_ENABLED
+#import "APICallbackFunction.h"
#import "APICast.h"
-#import "APIShims.h"
+#import "DelayedReleaseScope.h"
#import "Error.h"
#import "JSCJSValueInlines.h"
#import "JSCell.h"
return;
}
- *exception = toRef(JSC::createTypeError(toJS(contextRef), "Argument does not match Objective-C Class"));
+ *exception = toRef(JSC::createTypeError(toJS(contextRef), ASCIILiteral("Argument does not match Objective-C Class")));
}
Class m_class;
};
enum CallbackType {
+ CallbackInitMethod,
CallbackInstanceMethod,
CallbackClassMethod,
CallbackBlock
class ObjCCallbackFunctionImpl {
public:
- ObjCCallbackFunctionImpl(JSContext *context, NSInvocation *invocation, CallbackType type, Class instanceClass, PassOwnPtr<CallbackArgument> arguments, PassOwnPtr<CallbackResult> result)
- : m_context(context)
- , m_type(type)
+ ObjCCallbackFunctionImpl(NSInvocation *invocation, CallbackType type, Class instanceClass, PassOwnPtr<CallbackArgument> arguments, PassOwnPtr<CallbackResult> result)
+ : m_type(type)
, m_instanceClass([instanceClass retain])
, m_invocation(invocation)
, m_arguments(arguments)
, m_result(result)
{
- ASSERT(type != CallbackInstanceMethod || instanceClass);
+ ASSERT((type != CallbackInstanceMethod && type != CallbackInitMethod) || instanceClass);
}
- ~ObjCCallbackFunctionImpl()
+ void destroy(Heap& heap)
{
- if (m_type != CallbackInstanceMethod)
- [[m_invocation.get() target] release];
+ // 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 release];
}
JSValueRef call(JSContext *context, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception);
- JSContext *context()
+ id wrappedBlock()
{
- return m_context.get();
+ return m_type == CallbackBlock ? [m_invocation target] : nil;
}
- void setContext(JSContext *context)
+ id wrappedConstructor()
{
- ASSERT(!m_context.get());
- m_context.set(context);
+ switch (m_type) {
+ case CallbackBlock:
+ return [m_invocation target];
+ case CallbackInitMethod:
+ return m_instanceClass;
+ default:
+ return nil;
+ }
}
- id wrappedBlock()
+ bool isConstructible()
{
- return m_type == CallbackBlock ? [m_invocation target] : nil;
+ return !!wrappedBlock() || m_type == CallbackInitMethod;
}
+ String name();
+
private:
- WeakContextRef m_context;
CallbackType m_type;
Class m_instanceClass;
RetainPtr<NSInvocation> m_invocation;
// (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::APIEntryShim entryShim(toJS(callerContext));
+ JSC::JSLockHolder locker(toJS(callerContext));
ObjCCallbackFunction* callback = static_cast<ObjCCallbackFunction*>(toJS(function));
ObjCCallbackFunctionImpl* impl = callback->impl();
- JSContext *context = impl->context();
- if (!context) {
- context = [JSContext contextWithJSGlobalContextRef:toGlobalRef(toJS(callerContext)->lexicalGlobalObject()->globalExec())];
- impl->setContext(context);
- }
+ JSContext *context = [JSContext contextWithJSGlobalContextRef:toGlobalRef(callback->globalObject()->globalExec())];
CallbackData callbackData;
JSValueRef result;
@autoreleasepool {
- [context beginCallbackWithData:&callbackData thisValue:thisObject argumentCount:argumentCount arguments:arguments];
+ [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);
return result;
}
+static JSObjectRef objCCallbackFunctionCallAsConstructor(JSContextRef callerContext, JSObjectRef constructor, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
+{
+ JSC::JSLockHolder locker(toJS(callerContext));
+
+ ObjCCallbackFunction* callback = static_cast<ObjCCallbackFunction*>(toJS(constructor));
+ ObjCCallbackFunctionImpl* impl = callback->impl();
+ JSContext *context = [JSContext contextWithJSGlobalContextRef:toGlobalRef(toJS(callerContext)->lexicalGlobalObject()->globalExec())];
+
+ CallbackData callbackData;
+ JSValueRef result;
+ @autoreleasepool {
+ [context beginCallbackWithData:&callbackData calleeValue:constructor thisValue:nil argumentCount:argumentCount arguments:arguments];
+ result = impl->call(context, NULL, argumentCount, arguments, exception);
+ if (context.exception)
+ *exception = valueInternalValue(context.exception);
+ [context endCallbackWithData:&callbackData];
+ }
+
+ JSGlobalContextRef contextRef = [context JSGlobalContextRef];
+ if (*exception)
+ return 0;
+
+ if (!JSValueIsObject(contextRef, result)) {
+ *exception = toRef(JSC::createTypeError(toJS(contextRef), ASCIILiteral("Objective-C blocks called as constructors must return an object.")));
+ return 0;
+ }
+ return (JSObjectRef)result;
+}
+
const JSC::ClassInfo ObjCCallbackFunction::s_info = { "CallbackFunction", &Base::s_info, 0, 0, CREATE_METHOD_TABLE(ObjCCallbackFunction) };
-ObjCCallbackFunction::ObjCCallbackFunction(JSC::JSGlobalObject* globalObject, JSObjectCallAsFunctionCallback callback, PassOwnPtr<ObjCCallbackFunctionImpl> impl)
- : Base(globalObject, globalObject->objcCallbackFunctionStructure(), callback)
+ObjCCallbackFunction::ObjCCallbackFunction(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSObjectCallAsFunctionCallback functionCallback, JSObjectCallAsConstructorCallback constructCallback, PassOwnPtr<ObjCCallbackFunctionImpl> impl)
+ : Base(vm, globalObject->objcCallbackFunctionStructure())
+ , m_functionCallback(functionCallback)
+ , m_constructCallback(constructCallback)
, m_impl(impl)
{
}
-ObjCCallbackFunction* ObjCCallbackFunction::create(JSC::ExecState* exec, JSC::JSGlobalObject* globalObject, const String& name, PassOwnPtr<ObjCCallbackFunctionImpl> impl)
+ObjCCallbackFunction* ObjCCallbackFunction::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, const String& name, PassOwnPtr<ObjCCallbackFunctionImpl> impl)
{
- ObjCCallbackFunction* function = new (NotNull, allocateCell<ObjCCallbackFunction>(*exec->heap())) ObjCCallbackFunction(globalObject, objCCallbackFunctionCallAsFunction, impl);
- function->finishCreation(exec->vm(), name);
+ ObjCCallbackFunction* function = new (NotNull, allocateCell<ObjCCallbackFunction>(vm.heap)) ObjCCallbackFunction(vm, globalObject, objCCallbackFunctionCallAsFunction, objCCallbackFunctionCallAsConstructor, impl);
+ function->finishCreation(vm, name);
return function;
}
void ObjCCallbackFunction::destroy(JSCell* cell)
{
- static_cast<ObjCCallbackFunction*>(cell)->ObjCCallbackFunction::~ObjCCallbackFunction();
+ ObjCCallbackFunction& function = *jsCast<ObjCCallbackFunction*>(cell);
+ function.impl()->destroy(*Heap::heap(cell));
+ function.~ObjCCallbackFunction();
+}
+
+
+CallType ObjCCallbackFunction::getCallData(JSCell*, CallData& callData)
+{
+ callData.native.function = APICallbackFunction::call<ObjCCallbackFunction>;
+ return CallTypeHost;
+}
+
+ConstructType ObjCCallbackFunction::getConstructData(JSCell* cell, ConstructData& constructData)
+{
+ ObjCCallbackFunction* callback = jsCast<ObjCCallbackFunction*>(cell);
+ if (!callback->impl()->isConstructible())
+ return Base::getConstructData(cell, constructData);
+ constructData.native.function = APICallbackFunction::construct<ObjCCallbackFunction>;
+ return ConstructTypeHost;
+}
+
+String ObjCCallbackFunctionImpl::name()
+{
+ if (m_type == CallbackInitMethod)
+ return class_getName(m_instanceClass);
+ // 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)
{
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]) {
+ *exception = toRef(JSC::createTypeError(toJS(contextRef), ASCIILiteral("self type check failed for Objective-C instance method")));
+ return JSValueMakeUndefined(contextRef);
+ }
+ [m_invocation setTarget:target];
+ firstArgument = 2;
+ break;
+ }
case CallbackInstanceMethod: {
- id target = tryUnwrapObjcObject(contextRef, thisObject);
+ target = tryUnwrapObjcObject(contextRef, thisObject);
if (!target || ![target isKindOfClass:m_instanceClass]) {
- *exception = toRef(JSC::createTypeError(toJS(contextRef), "self type check failed for Objective-C instance method"));
+ *exception = toRef(JSC::createTypeError(toJS(contextRef), ASCIILiteral("self type check failed for Objective-C instance method")));
return JSValueMakeUndefined(contextRef);
}
[m_invocation setTarget:target];
+ firstArgument = 2;
+ break;
}
- // fallthrough - firstArgument for CallbackInstanceMethod is also 2!
case CallbackClassMethod:
firstArgument = 2;
break;
[m_invocation invoke];
- return m_result->get(m_invocation.get(), context, exception);
+ JSValueRef result = m_result->get(m_invocation.get(), context, exception);
+
+ // 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 JSObjectRef objCCallbackFunctionForInvocation(JSContext *context, NSInvocation *invocation, CallbackType type, Class instanceClass, const char* signatureWithObjcClasses)
{
+ if (!signatureWithObjcClasses)
+ return nil;
+
const char* position = signatureWithObjcClasses;
OwnPtr<CallbackResult> result = adoptPtr(parseObjCType<ResultTypeDelegate>(position));
return nil;
switch (type) {
+ case CallbackInitMethod:
case CallbackInstanceMethod:
case CallbackClassMethod:
// Methods are passed two implicit arguments - (id)self, and the selector.
}
JSC::ExecState* exec = toJS([context JSGlobalContextRef]);
- JSC::APIEntryShim shim(exec);
- OwnPtr<JSC::ObjCCallbackFunctionImpl> impl = adoptPtr(new JSC::ObjCCallbackFunctionImpl(context, invocation, type, instanceClass, arguments.release(), result.release()));
- // FIXME: Maybe we could support having the selector as the name of the function to make it a bit more user-friendly from the JS side?
- return toRef(JSC::ObjCCallbackFunction::create(exec, exec->lexicalGlobalObject(), "", impl.release()));
+ JSC::JSLockHolder locker(exec);
+ OwnPtr<JSC::ObjCCallbackFunctionImpl> impl = adoptPtr(new JSC::ObjCCallbackFunctionImpl(invocation, type, instanceClass, arguments.release(), result.release()));
+ return toRef(JSC::ObjCCallbackFunction::create(exec->vm(), exec->lexicalGlobalObject(), impl->name(), impl.release()));
+}
+
+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];
+ // We need to retain the target Class because m_invocation doesn't retain it
+ // by default (and we don't want it to).
if (!isInstanceMethod)
- [invocation setTarget:cls];
+ [invocation setTarget:[cls retain]];
return objCCallbackFunctionForInvocation(context, invocation, isInstanceMethod ? CallbackInstanceMethod : CallbackClassMethod, isInstanceMethod ? cls : nil, _protocol_getMethodTypeEncoding(protocol, sel, YES, isInstanceMethod));
}
return 0;
const char* signature = _Block_signature(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 tryUnwrapBlock(JSObjectRef object)
+id tryUnwrapConstructor(JSObjectRef object)
{
- if (!toJS(object)->inherits(&JSC::ObjCCallbackFunction::s_info))
+ if (!toJS(object)->inherits(JSC::ObjCCallbackFunction::info()))
+ return nil;
+ JSC::ObjCCallbackFunctionImpl* impl = static_cast<JSC::ObjCCallbackFunction*>(toJS(object))->impl();
+ if (!impl->isConstructible())
return nil;
- return static_cast<JSC::ObjCCallbackFunction*>(toJS(object))->impl()->wrappedBlock();
+ return impl->wrappedConstructor();
}
#endif