Objective-C API: JSObjCClassInfo creates reference cycle with JSContext
[WebKit-https.git] / Source / JavaScriptCore / API / JSWrapperMap.mm
index 5529a32..a268a56 100644 (file)
 
 #if JS_OBJC_API_ENABLED
 
+#import "APICast.h"
 #import "JSContextInternal.h"
 #import "JSWrapperMap.h"
 #import "ObjCCallbackFunction.h"
 #import "ObjcRuntimeExtras.h"
+#import "Operations.h"
+#import "WeakGCMap.h"
 #import <wtf/TCSpinLock.h>
 #import "wtf/Vector.h"
 
+@class JSObjCClassInfo;
+
+@interface JSWrapperMap () 
+
+- (JSObjCClassInfo*)classInfoForClass:(Class)cls;
+
+@end
+
 static void wrapperFinalize(JSObjectRef object)
 {
     [(id)JSObjectGetPrivate(object) release];
@@ -288,8 +299,8 @@ static void copyPrototypeProperties(JSContext *context, Class objcClass, Protoco
     Class m_class;
     bool m_block;
     JSClassRef m_classRef;
-    JSValue *m_prototype;
-    JSValue *m_constructor;
+    JSC::Weak<JSC::JSObject> m_prototype;
+    JSC::Weak<JSC::JSObject> m_constructor;
 }
 
 - (id)initWithContext:(JSContext *)context forClass:(Class)cls superClassInfo:(JSObjCClassInfo*)superClassInfo;
@@ -316,36 +327,60 @@ static void copyPrototypeProperties(JSContext *context, Class objcClass, Protoco
     definition.parentClass = wrapperClass();
     m_classRef = JSClassCreate(&definition);
 
-    ASSERT((cls == [NSObject class]) == !superClassInfo);
+    [self allocateConstructorAndPrototypeWithSuperClassInfo:superClassInfo];
+
+    return self;
+}
+
+- (void)dealloc
+{
+    JSClassRelease(m_classRef);
+    [super dealloc];
+}
+
+- (void)allocateConstructorAndPrototypeWithSuperClassInfo:(JSObjCClassInfo*)superClassInfo
+{
+    ASSERT((m_class == [NSObject class]) == !superClassInfo);
     if (!superClassInfo) {
-        m_constructor = [context[@"Object"] retain];
-        m_prototype = [m_constructor[@"prototype"] retain];
+        JSContextRef cContext = contextInternalContext(m_context);
+        JSValue *constructor = m_context[@"Object"];
+        m_constructor = toJS(JSValueToObject(cContext, valueInternalValue(constructor), 0));
+
+        JSValue *prototype = constructor[@"prototype"];
+        m_prototype = toJS(JSValueToObject(cContext, valueInternalValue(prototype), 0));
     } else {
+        const char* className = class_getName(m_class);
+
         // Create the prototype/constructor pair.
-        m_prototype = createObjectWithCustomBrand(context, [NSString stringWithFormat:@"%sPrototype", className]);
-        m_constructor = createObjectWithCustomBrand(context, [NSString stringWithFormat:@"%sConstructor", className], wrapperClass(), [cls retain]);
-        putNonEnumerable(m_prototype, @"constructor", m_constructor);
-        putNonEnumerable(m_constructor, @"prototype", m_prototype);
+        JSValue *prototype = createObjectWithCustomBrand(m_context, [NSString stringWithFormat:@"%sPrototype", className]);
+        JSValue *constructor = createObjectWithCustomBrand(m_context, [NSString stringWithFormat:@"%sConstructor", className], wrapperClass(), [m_class retain]);
+
+        JSContextRef cContext = contextInternalContext(m_context);
+        m_prototype = toJS(JSValueToObject(cContext, valueInternalValue(prototype), 0));
+        m_constructor = toJS(JSValueToObject(cContext, valueInternalValue(constructor), 0));
+
+        putNonEnumerable(prototype, @"constructor", constructor);
+        putNonEnumerable(constructor, @"prototype", prototype);
 
         Protocol *exportProtocol = getJSExportProtocol();
-        forEachProtocolImplementingProtocol(cls, exportProtocol, ^(Protocol *protocol){
-            copyPrototypeProperties(context, cls, protocol, m_prototype);
-            copyMethodsToObject(context, cls, protocol, NO, m_constructor);
+        forEachProtocolImplementingProtocol(m_class, exportProtocol, ^(Protocol *protocol){
+            copyPrototypeProperties(m_context, m_class, protocol, prototype);
+            copyMethodsToObject(m_context, m_class, protocol, NO, constructor);
         });
 
         // Set [Prototype].
-        m_prototype[@"__proto__"] = superClassInfo->m_prototype;
-    }
+        prototype[@"__proto__"] = [JSValue valueWithValue:toRef(superClassInfo->m_prototype.get()) inContext:m_context];
 
-    return self;
+        [constructor release];
+        [prototype release];
+    }
 }
 
-- (void)dealloc
+- (void)allocateConstructorAndPrototype
 {
-    JSClassRelease(m_classRef);
-    [m_prototype release];
-    [m_constructor release];
-    [super dealloc];
+    ASSERT(!m_constructor.get());
+    ASSERT(!m_prototype.get());
+    [self allocateConstructorAndPrototypeWithSuperClassInfo:[m_context.wrapperMap classInfoForClass:class_getSuperclass(m_class)]];
 }
 
 - (JSValue *)wrapperForObject:(id)object
@@ -357,18 +392,27 @@ static void copyPrototypeProperties(JSContext *context, Class objcClass, Protoco
             return [JSValue valueWithValue:method inContext:m_context];
     }
 
-    JSValueRef prototypeValue = valueInternalValue(m_prototype);
-    ASSERT(JSValueIsObject(contextInternalContext(m_context), prototypeValue));
-    JSObjectRef prototype = JSValueToObject(contextInternalContext(m_context), prototypeValue, 0);
+    JSC::JSObject* prototype = m_prototype.get();
+    if (!prototype) {
+        [self allocateConstructorAndPrototype];
+        prototype = m_prototype.get();
+        ASSERT(prototype);
+    }
 
     JSObjectRef wrapper = JSObjectMake(contextInternalContext(m_context), m_classRef, [object retain]);
-    JSObjectSetPrototype(contextInternalContext(m_context), wrapper, prototype);
+    JSObjectSetPrototype(contextInternalContext(m_context), wrapper, toRef(prototype));
     return [JSValue valueWithValue:wrapper inContext:m_context];
 }
 
 - (JSValue *)constructor
 {
-    return m_constructor;
+    JSC::JSObject* constructor = m_constructor.get();
+    if (!constructor) {
+        [self allocateConstructorAndPrototype];
+        constructor = m_constructor.get();
+        ASSERT(constructor);
+    }
+    return [JSValue valueWithValue:toRef(constructor) inContext:m_context];
 }
 
 @end
@@ -376,6 +420,7 @@ static void copyPrototypeProperties(JSContext *context, Class objcClass, Protoco
 @implementation JSWrapperMap {
     JSContext *m_context;
     NSMutableDictionary *m_classMap;
+    JSC::WeakGCMap<id, JSC::JSObject> m_cachedWrappers;
 }
 
 - (id)initWithContext:(JSContext *)context
@@ -413,10 +458,11 @@ static void copyPrototypeProperties(JSContext *context, Class objcClass, Protoco
 
 - (JSValue *)wrapperForObject:(id)object
 {
-    JSValue *wrapper = objc_getAssociatedObject(object, m_context);
-    if (wrapper && wrapper.context)
-        return wrapper;
+    JSC::JSObject* jsWrapper = m_cachedWrappers.get(object);
+    if (jsWrapper)
+        return [JSValue valueWithValue:toRef(jsWrapper) inContext:m_context];
 
+    JSValue *wrapper;
     if (class_isMetaClass(object_getClass(object)))
         wrapper = [[self classInfoForClass:(Class)object] constructor];
     else {
@@ -429,7 +475,9 @@ static void copyPrototypeProperties(JSContext *context, Class objcClass, Protoco
     // (1) For immortal objects JSValues will effectively leak and this results in error output being logged - we should avoid adding associated objects to immortal objects.
     // (2) A long lived object may rack up many JSValues. When the contexts are released these will unproctect the associated JavaScript objects,
     //     but still, would probably nicer if we made it so that only one associated object was required, broadcasting object dealloc.
-    objc_setAssociatedObject(object, m_context, wrapper, OBJC_ASSOCIATION_RETAIN);
+    JSC::ExecState* exec = toJS(contextInternalContext(m_context));
+    jsWrapper = toJS(exec, valueInternalValue(wrapper)).toObject(exec);
+    m_cachedWrappers.set(object, jsWrapper);
     return wrapper;
 }