123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522 |
- /*
- * Copyright (C) 2013 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.
- */
- #include "config.h"
- #import "JavaScriptCore.h"
- #if JSC_OBJC_API_ENABLED
- #import "APICast.h"
- #import "APIShims.h"
- #import "JSAPIWrapperObject.h"
- #import "JSCallbackObject.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
- // Default conversion of selectors to property names.
- // All semicolons are removed, lowercase letters following a semicolon are capitalized.
- static NSString *selectorToPropertyName(const char* start)
- {
- // Use 'index' to check for colons, if there are none, this is easy!
- const char* firstColon = index(start, ':');
- if (!firstColon)
- return [NSString stringWithUTF8String:start];
- // 'header' is the length of string up to the first colon.
- size_t header = firstColon - start;
- // The new string needs to be long enough to hold 'header', plus the remainder of the string, excluding
- // at least one ':', but including a '\0'. (This is conservative if there are more than one ':').
- char* buffer = static_cast<char*>(malloc(header + strlen(firstColon + 1) + 1));
- // Copy 'header' characters, set output to point to the end of this & input to point past the first ':'.
- memcpy(buffer, start, header);
- char* output = buffer + header;
- const char* input = start + header + 1;
- // On entry to the loop, we have already skipped over a ':' from the input.
- while (true) {
- char c;
- // Skip over any additional ':'s. We'll leave c holding the next character after the
- // last ':', and input pointing past c.
- while ((c = *(input++)) == ':');
- // Copy the character, converting to upper case if necessary.
- // If the character we copy is '\0', then we're done!
- if (!(*(output++) = toupper(c)))
- goto done;
- // Loop over characters other than ':'.
- while ((c = *(input++)) != ':') {
- // Copy the character.
- // If the character we copy is '\0', then we're done!
- if (!(*(output++) = c))
- goto done;
- }
- // If we get here, we've consumed a ':' - wash, rinse, repeat.
- }
- done:
- NSString *result = [NSString stringWithUTF8String:buffer];
- free(buffer);
- return result;
- }
- static JSObjectRef makeWrapper(JSContextRef ctx, JSClassRef jsClass, id wrappedObject)
- {
- JSC::ExecState* exec = toJS(ctx);
- JSC::APIEntryShim entryShim(exec);
- ASSERT(jsClass);
- JSC::JSCallbackObject<JSC::JSAPIWrapperObject>* object = JSC::JSCallbackObject<JSC::JSAPIWrapperObject>::create(exec, exec->lexicalGlobalObject(), exec->lexicalGlobalObject()->objcWrapperObjectStructure(), jsClass, 0);
- object->setWrappedObject(wrappedObject);
- if (JSC::JSObject* prototype = jsClass->prototype(exec))
- object->setPrototype(exec->vm(), prototype);
- return toRef(object);
- }
- // Make an object that is in all ways a completely vanilla JavaScript object,
- // other than that it has a native brand set that will be displayed by the default
- // Object.prototype.toString conversion.
- static JSValue *objectWithCustomBrand(JSContext *context, NSString *brand, Class cls = 0)
- {
- JSClassDefinition definition;
- definition = kJSClassDefinitionEmpty;
- definition.className = [brand UTF8String];
- JSClassRef classRef = JSClassCreate(&definition);
- JSObjectRef result = makeWrapper([context JSGlobalContextRef], classRef, cls);
- JSClassRelease(classRef);
- return [JSValue valueWithJSValueRef:result inContext:context];
- }
- // Look for @optional properties in the prototype containing a selector to property
- // name mapping, separated by a __JS_EXPORT_AS__ delimiter.
- static NSMutableDictionary *createRenameMap(Protocol *protocol, BOOL isInstanceMethod)
- {
- NSMutableDictionary *renameMap = [[NSMutableDictionary alloc] init];
- forEachMethodInProtocol(protocol, NO, isInstanceMethod, ^(SEL sel, const char*){
- NSString *rename = @(sel_getName(sel));
- NSRange range = [rename rangeOfString:@"__JS_EXPORT_AS__"];
- if (range.location == NSNotFound)
- return;
- NSString *selector = [rename substringToIndex:range.location];
- NSUInteger begin = range.location + range.length;
- NSUInteger length = [rename length] - begin - 1;
- NSString *name = [rename substringWithRange:(NSRange){ begin, length }];
- renameMap[selector] = name;
- });
- return renameMap;
- }
- inline void putNonEnumerable(JSValue *base, NSString *propertyName, JSValue *value)
- {
- [base defineProperty:propertyName descriptor:@{
- JSPropertyDescriptorValueKey: value,
- JSPropertyDescriptorWritableKey: @YES,
- JSPropertyDescriptorEnumerableKey: @NO,
- JSPropertyDescriptorConfigurableKey: @YES
- }];
- }
- // This method will iterate over the set of required methods in the protocol, and:
- // * Determine a property name (either via a renameMap or default conversion).
- // * If an accessorMap is provided, and contains this name, store the method in the map.
- // * Otherwise, if the object doesn't already contain a property with name, create it.
- static void copyMethodsToObject(JSContext *context, Class objcClass, Protocol *protocol, BOOL isInstanceMethod, JSValue *object, NSMutableDictionary *accessorMethods = nil)
- {
- NSMutableDictionary *renameMap = createRenameMap(protocol, isInstanceMethod);
- forEachMethodInProtocol(protocol, YES, isInstanceMethod, ^(SEL sel, const char* types){
- const char* nameCStr = sel_getName(sel);
- NSString *name = @(nameCStr);
- if (accessorMethods && accessorMethods[name]) {
- JSObjectRef method = objCCallbackFunctionForMethod(context, objcClass, protocol, isInstanceMethod, sel, types);
- if (!method)
- return;
- accessorMethods[name] = [JSValue valueWithJSValueRef:method inContext:context];
- } else {
- name = renameMap[name];
- if (!name)
- name = selectorToPropertyName(nameCStr);
- if ([object hasProperty:name])
- return;
- JSObjectRef method = objCCallbackFunctionForMethod(context, objcClass, protocol, isInstanceMethod, sel, types);
- if (method)
- putNonEnumerable(object, name, [JSValue valueWithJSValueRef:method inContext:context]);
- }
- });
- [renameMap release];
- }
- static bool parsePropertyAttributes(objc_property_t property, char*& getterName, char*& setterName)
- {
- bool readonly = false;
- unsigned attributeCount;
- objc_property_attribute_t* attributes = property_copyAttributeList(property, &attributeCount);
- if (attributeCount) {
- for (unsigned i = 0; i < attributeCount; ++i) {
- switch (*(attributes[i].name)) {
- case 'G':
- getterName = strdup(attributes[i].value);
- break;
- case 'S':
- setterName = strdup(attributes[i].value);
- break;
- case 'R':
- readonly = true;
- break;
- default:
- break;
- }
- }
- free(attributes);
- }
- return readonly;
- }
- static char* makeSetterName(const char* name)
- {
- size_t nameLength = strlen(name);
- char* setterName = (char*)malloc(nameLength + 5); // "set" Name ":\0"
- setterName[0] = 's';
- setterName[1] = 'e';
- setterName[2] = 't';
- setterName[3] = toupper(*name);
- memcpy(setterName + 4, name + 1, nameLength - 1);
- setterName[nameLength + 3] = ':';
- setterName[nameLength + 4] = '\0';
- return setterName;
- }
- static void copyPrototypeProperties(JSContext *context, Class objcClass, Protocol *protocol, JSValue *prototypeValue)
- {
- // First gather propreties into this list, then handle the methods (capturing the accessor methods).
- struct Property {
- const char* name;
- char* getterName;
- char* setterName;
- };
- __block Vector<Property> propertyList;
- // Map recording the methods used as getters/setters.
- NSMutableDictionary *accessorMethods = [NSMutableDictionary dictionary];
- // Useful value.
- JSValue *undefined = [JSValue valueWithUndefinedInContext:context];
- forEachPropertyInProtocol(protocol, ^(objc_property_t property){
- char* getterName = 0;
- char* setterName = 0;
- bool readonly = parsePropertyAttributes(property, getterName, setterName);
- const char* name = property_getName(property);
- // Add the names of the getter & setter methods to
- if (!getterName)
- getterName = strdup(name);
- accessorMethods[@(getterName)] = undefined;
- if (!readonly) {
- if (!setterName)
- setterName = makeSetterName(name);
- accessorMethods[@(setterName)] = undefined;
- }
- // Add the properties to a list.
- propertyList.append((Property){ name, getterName, setterName });
- });
- // Copy methods to the prototype, capturing accessors in the accessorMethods map.
- copyMethodsToObject(context, objcClass, protocol, YES, prototypeValue, accessorMethods);
- // Iterate the propertyList & generate accessor properties.
- for (size_t i = 0; i < propertyList.size(); ++i) {
- Property& property = propertyList[i];
- JSValue *getter = accessorMethods[@(property.getterName)];
- free(property.getterName);
- ASSERT(![getter isUndefined]);
- JSValue *setter = undefined;
- if (property.setterName) {
- setter = accessorMethods[@(property.setterName)];
- free(property.setterName);
- ASSERT(![setter isUndefined]);
- }
-
- [prototypeValue defineProperty:@(property.name) descriptor:@{
- JSPropertyDescriptorGetKey: getter,
- JSPropertyDescriptorSetKey: setter,
- JSPropertyDescriptorEnumerableKey: @NO,
- JSPropertyDescriptorConfigurableKey: @YES
- }];
- }
- }
- @interface JSObjCClassInfo : NSObject {
- JSContext *m_context;
- Class m_class;
- bool m_block;
- JSClassRef m_classRef;
- JSC::Weak<JSC::JSObject> m_prototype;
- JSC::Weak<JSC::JSObject> m_constructor;
- }
- - (id)initWithContext:(JSContext *)context forClass:(Class)cls superClassInfo:(JSObjCClassInfo*)superClassInfo;
- - (JSValue *)wrapperForObject:(id)object;
- - (JSValue *)constructor;
- @end
- @implementation JSObjCClassInfo
- - (id)initWithContext:(JSContext *)context forClass:(Class)cls superClassInfo:(JSObjCClassInfo*)superClassInfo
- {
- self = [super init];
- if (!self)
- return nil;
- const char* className = class_getName(cls);
- m_context = context;
- m_class = cls;
- m_block = [cls isSubclassOfClass:getNSBlockClass()];
- JSClassDefinition definition;
- definition = kJSClassDefinitionEmpty;
- definition.className = className;
- m_classRef = JSClassCreate(&definition);
- [self allocateConstructorAndPrototypeWithSuperClassInfo:superClassInfo];
- return self;
- }
- - (void)dealloc
- {
- JSClassRelease(m_classRef);
- [super dealloc];
- }
- - (void)allocateConstructorAndPrototypeWithSuperClassInfo:(JSObjCClassInfo*)superClassInfo
- {
- ASSERT(!m_constructor || !m_prototype);
- ASSERT((m_class == [NSObject class]) == !superClassInfo);
- if (!superClassInfo) {
- JSContextRef cContext = [m_context JSGlobalContextRef];
- JSValue *constructor = m_context[@"Object"];
- if (!m_constructor)
- m_constructor = toJS(JSValueToObject(cContext, valueInternalValue(constructor), 0));
- if (!m_prototype) {
- JSValue *prototype = constructor[@"prototype"];
- m_prototype = toJS(JSValueToObject(cContext, valueInternalValue(prototype), 0));
- }
- } else {
- const char* className = class_getName(m_class);
- // Create or grab the prototype/constructor pair.
- JSValue *prototype;
- JSValue *constructor;
- if (m_prototype)
- prototype = [JSValue valueWithJSValueRef:toRef(m_prototype.get()) inContext:m_context];
- else
- prototype = objectWithCustomBrand(m_context, [NSString stringWithFormat:@"%sPrototype", className]);
- if (m_constructor)
- constructor = [JSValue valueWithJSValueRef:toRef(m_constructor.get()) inContext:m_context];
- else
- constructor = objectWithCustomBrand(m_context, [NSString stringWithFormat:@"%sConstructor", className], m_class);
- JSContextRef cContext = [m_context JSGlobalContextRef];
- 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(m_class, exportProtocol, ^(Protocol *protocol){
- copyPrototypeProperties(m_context, m_class, protocol, prototype);
- copyMethodsToObject(m_context, m_class, protocol, NO, constructor);
- });
- // Set [Prototype].
- JSObjectSetPrototype([m_context JSGlobalContextRef], toRef(m_prototype.get()), toRef(superClassInfo->m_prototype.get()));
- }
- }
- - (void)reallocateConstructorAndOrPrototype
- {
- [self allocateConstructorAndPrototypeWithSuperClassInfo:[m_context.wrapperMap classInfoForClass:class_getSuperclass(m_class)]];
- }
- - (JSValue *)wrapperForObject:(id)object
- {
- ASSERT([object isKindOfClass:m_class]);
- ASSERT(m_block == [object isKindOfClass:getNSBlockClass()]);
- if (m_block) {
- if (JSObjectRef method = objCCallbackFunctionForBlock(m_context, object))
- return [JSValue valueWithJSValueRef:method inContext:m_context];
- }
- if (!m_prototype)
- [self reallocateConstructorAndOrPrototype];
- ASSERT(!!m_prototype);
- JSObjectRef wrapper = makeWrapper([m_context JSGlobalContextRef], m_classRef, object);
- JSObjectSetPrototype([m_context JSGlobalContextRef], wrapper, toRef(m_prototype.get()));
- return [JSValue valueWithJSValueRef:wrapper inContext:m_context];
- }
- - (JSValue *)constructor
- {
- if (!m_constructor)
- [self reallocateConstructorAndOrPrototype];
- ASSERT(!!m_constructor);
- return [JSValue valueWithJSValueRef:toRef(m_constructor.get()) inContext:m_context];
- }
- @end
- @implementation JSWrapperMap {
- JSContext *m_context;
- NSMutableDictionary *m_classMap;
- JSC::WeakGCMap<id, JSC::JSObject> m_cachedJSWrappers;
- NSMapTable *m_cachedObjCWrappers;
- }
- - (id)initWithContext:(JSContext *)context
- {
- self = [super init];
- if (!self)
- return nil;
- NSPointerFunctionsOptions keyOptions = NSPointerFunctionsOpaqueMemory | NSPointerFunctionsOpaquePersonality;
- NSPointerFunctionsOptions valueOptions = NSPointerFunctionsWeakMemory | NSPointerFunctionsObjectPersonality;
- m_cachedObjCWrappers = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:valueOptions capacity:0];
-
- m_context = context;
- m_classMap = [[NSMutableDictionary alloc] init];
- return self;
- }
- - (void)dealloc
- {
- [m_cachedObjCWrappers release];
- [m_classMap release];
- [super dealloc];
- }
- - (JSObjCClassInfo*)classInfoForClass:(Class)cls
- {
- if (!cls)
- return nil;
- // Check if we've already created a JSObjCClassInfo for this Class.
- if (JSObjCClassInfo* classInfo = (JSObjCClassInfo*)m_classMap[cls])
- return classInfo;
- // Skip internal classes beginning with '_' - just copy link to the parent class's info.
- if ('_' == *class_getName(cls))
- return m_classMap[cls] = [self classInfoForClass:class_getSuperclass(cls)];
- return m_classMap[cls] = [[[JSObjCClassInfo alloc] initWithContext:m_context forClass:cls superClassInfo:[self classInfoForClass:class_getSuperclass(cls)]] autorelease];
- }
- - (JSValue *)jsWrapperForObject:(id)object
- {
- JSC::JSObject* jsWrapper = m_cachedJSWrappers.get(object);
- if (jsWrapper)
- return [JSValue valueWithJSValueRef:toRef(jsWrapper) inContext:m_context];
- JSValue *wrapper;
- if (class_isMetaClass(object_getClass(object)))
- wrapper = [[self classInfoForClass:(Class)object] constructor];
- else {
- JSObjCClassInfo* classInfo = [self classInfoForClass:[object class]];
- wrapper = [classInfo wrapperForObject:object];
- }
- // FIXME: https://bugs.webkit.org/show_bug.cgi?id=105891
- // This general approach to wrapper caching is pretty effective, but there are a couple of problems:
- // (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 unprotect the associated JavaScript objects,
- // but still, would probably nicer if we made it so that only one associated object was required, broadcasting object dealloc.
- JSC::ExecState* exec = toJS([m_context JSGlobalContextRef]);
- jsWrapper = toJS(exec, valueInternalValue(wrapper)).toObject(exec);
- m_cachedJSWrappers.set(object, jsWrapper);
- return wrapper;
- }
- - (JSValue *)objcWrapperForJSValueRef:(JSValueRef)value
- {
- JSValue *wrapper = static_cast<JSValue *>(NSMapGet(m_cachedObjCWrappers, value));
- if (!wrapper) {
- wrapper = [[[JSValue alloc] initWithValue:value inContext:m_context] autorelease];
- NSMapInsert(m_cachedObjCWrappers, value, wrapper);
- }
- return wrapper;
- }
- @end
- id tryUnwrapObjcObject(JSGlobalContextRef context, JSValueRef value)
- {
- if (!JSValueIsObject(context, value))
- return nil;
- JSValueRef exception = 0;
- JSObjectRef object = JSValueToObject(context, value, &exception);
- ASSERT(!exception);
- if (toJS(object)->inherits(&JSC::JSCallbackObject<JSC::JSAPIWrapperObject>::s_info))
- return (id)JSC::jsCast<JSC::JSAPIWrapperObject*>(toJS(object))->wrappedObject();
- if (id target = tryUnwrapBlock(object))
- return target;
- return nil;
- }
- Protocol *getJSExportProtocol()
- {
- static Protocol *protocol = objc_getProtocol("JSExport");
- return protocol;
- }
- Class getNSBlockClass()
- {
- static Class cls = objc_getClass("NSBlock");
- return cls;
- }
- #endif
|