Replace WTF::move with WTFMove
[WebKit-https.git] / Source / WebCore / bridge / objc / objc_class.mm
1 /*
2  * Copyright (C) 2004, 2012 Apple Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #include "config.h"
27 #include "objc_class.h"
28
29 #include "objc_instance.h"
30 #include "WebScriptObject.h"
31 #include "WebScriptObjectProtocol.h"
32
33 namespace JSC {
34 namespace Bindings {
35
36 ObjcClass::ObjcClass(ClassStructPtr aClass)
37     : _isa(aClass)
38 {
39 }
40
41 static CFMutableDictionaryRef classesByIsA = 0;
42
43 static void _createClassesByIsAIfNecessary()
44 {
45     if (!classesByIsA)
46         classesByIsA = CFDictionaryCreateMutable(NULL, 0, NULL, NULL);
47 }
48
49 ObjcClass* ObjcClass::classForIsA(ClassStructPtr isa)
50 {
51     _createClassesByIsAIfNecessary();
52
53     ObjcClass* aClass = (ObjcClass*)CFDictionaryGetValue(classesByIsA, isa);
54     if (!aClass) {
55         aClass = new ObjcClass(isa);
56         CFDictionaryAddValue(classesByIsA, isa, aClass);
57     }
58
59     return aClass;
60 }
61
62 /*
63     By default, a JavaScript method name is produced by concatenating the
64     components of an ObjectiveC method name, replacing ':' with '_', and
65     escaping '_' and '$' with a leading '$', such that '_' becomes "$_" and
66     '$' becomes "$$". For example:
67
68     ObjectiveC name         Default JavaScript name
69         moveTo::                moveTo__
70         moveTo_                 moveTo$_
71         moveTo$_                moveTo$$$_
72
73     This function performs the inverse of that operation.
74
75     @result Fills 'buffer' with the ObjectiveC method name that corresponds to 'JSName'.
76 */
77 typedef Vector<char, 256> JSNameConversionBuffer;
78 static inline void convertJSMethodNameToObjc(const CString& jsName, JSNameConversionBuffer& buffer)
79 {
80     buffer.reserveInitialCapacity(jsName.length() + 1);
81
82     const char* source = jsName.data();
83     while (true) {
84         if (*source == '$') {
85             ++source;
86             buffer.uncheckedAppend(*source);
87         } else if (*source == '_')
88             buffer.uncheckedAppend(':');
89         else
90             buffer.uncheckedAppend(*source);
91
92         if (!*source)
93             return;
94
95         ++source;
96     }
97 }
98
99 Method* ObjcClass::methodNamed(PropertyName propertyName, Instance*) const
100 {
101     String name(propertyName.publicName());
102     if (name.isNull())
103         return nullptr;
104
105     if (Method* method = m_methodCache.get(name.impl()))
106         return method;
107
108     CString jsName = name.ascii();
109     JSNameConversionBuffer buffer;
110     convertJSMethodNameToObjc(jsName, buffer);
111     RetainPtr<CFStringRef> methodName = adoptCF(CFStringCreateWithCString(NULL, buffer.data(), kCFStringEncodingASCII));
112
113     Method* methodPtr = 0;
114     ClassStructPtr thisClass = _isa;
115     
116     while (thisClass && !methodPtr) {
117         unsigned numMethodsInClass = 0;
118         MethodStructPtr* objcMethodList = class_copyMethodList(thisClass, &numMethodsInClass);
119         for (unsigned i = 0; i < numMethodsInClass; i++) {
120             MethodStructPtr objcMethod = objcMethodList[i];
121             SEL objcMethodSelector = method_getName(objcMethod);
122             const char* objcMethodSelectorName = sel_getName(objcMethodSelector);
123             NSString* mappedName = nil;
124
125             // See if the class wants to exclude the selector from visibility in JavaScript.
126             if ([thisClass respondsToSelector:@selector(isSelectorExcludedFromWebScript:)])
127                 if ([thisClass isSelectorExcludedFromWebScript:objcMethodSelector])
128                     continue;
129
130             // See if the class want to provide a different name for the selector in JavaScript.
131             // Note that we do not do any checks to guarantee uniqueness. That's the responsiblity
132             // of the class.
133             if ([thisClass respondsToSelector:@selector(webScriptNameForSelector:)])
134                 mappedName = [thisClass webScriptNameForSelector:objcMethodSelector];
135
136             if ((mappedName && [mappedName isEqual:(NSString*)methodName.get()]) || strcmp(objcMethodSelectorName, buffer.data()) == 0) {
137                 auto method = std::make_unique<ObjcMethod>(thisClass, objcMethodSelector);
138                 methodPtr = method.get();
139                 m_methodCache.add(name.impl(), WTFMove(method));
140                 break;
141             }
142         }
143         thisClass = class_getSuperclass(thisClass);
144         free(objcMethodList);
145     }
146
147     return methodPtr;
148 }
149
150 Field* ObjcClass::fieldNamed(PropertyName propertyName, Instance* instance) const
151 {
152     String name(propertyName.publicName());
153     if (name.isNull())
154         return nullptr;
155
156     Field* field = m_fieldCache.get(name.impl());
157     if (field)
158         return field;
159
160     ClassStructPtr thisClass = _isa;
161
162     CString jsName = name.ascii();
163     RetainPtr<CFStringRef> fieldName = adoptCF(CFStringCreateWithCString(NULL, jsName.data(), kCFStringEncodingASCII));
164     id targetObject = (static_cast<ObjcInstance*>(instance))->getObject();
165 #if PLATFORM(IOS)
166 #pragma clang diagnostic push
167 #pragma clang diagnostic ignored "-Wundeclared-selector"
168     id attributes = [targetObject respondsToSelector:@selector(attributeKeys)] ? [targetObject performSelector:@selector(attributeKeys)] : nil;
169 #pragma clang diagnostic pop
170 #else
171     id attributes = [targetObject attributeKeys];
172 #endif
173     if (attributes) {
174         // Class overrides attributeKeys, use that array of key names.
175         unsigned count = [attributes count];
176         for (unsigned i = 0; i < count; i++) {
177             NSString* keyName = [attributes objectAtIndex:i];
178             const char* UTF8KeyName = [keyName UTF8String]; // ObjC actually only supports ASCII names.
179
180             // See if the class wants to exclude the selector from visibility in JavaScript.
181             if ([thisClass respondsToSelector:@selector(isKeyExcludedFromWebScript:)])
182                 if ([thisClass isKeyExcludedFromWebScript:UTF8KeyName])
183                     continue;
184
185             // See if the class want to provide a different name for the selector in JavaScript.
186             // Note that we do not do any checks to guarantee uniqueness. That's the responsiblity
187             // of the class.
188             NSString* mappedName = nil;
189             if ([thisClass respondsToSelector:@selector(webScriptNameForKey:)])
190                 mappedName = [thisClass webScriptNameForKey:UTF8KeyName];
191
192             if ((mappedName && [mappedName isEqual:(NSString*)fieldName.get()]) || [keyName isEqual:(NSString*)fieldName.get()]) {
193                 auto newField = std::make_unique<ObjcField>((CFStringRef)keyName);
194                 field = newField.get();
195                 m_fieldCache.add(name.impl(), WTFMove(newField));
196                 break;
197             }
198         }
199     } else {
200         // Class doesn't override attributeKeys, so fall back on class runtime
201         // introspection.
202
203         while (thisClass) {
204             unsigned numFieldsInClass = 0;
205             IvarStructPtr* ivarsInClass = class_copyIvarList(thisClass, &numFieldsInClass);
206
207             for (unsigned i = 0; i < numFieldsInClass; i++) {
208                 IvarStructPtr objcIVar = ivarsInClass[i];
209                 const char* objcIvarName = ivar_getName(objcIVar);
210                 NSString* mappedName = 0;
211
212                 // See if the class wants to exclude the selector from visibility in JavaScript.
213                 if ([thisClass respondsToSelector:@selector(isKeyExcludedFromWebScript:)])
214                     if ([thisClass isKeyExcludedFromWebScript:objcIvarName])
215                         continue;
216
217                 // See if the class want to provide a different name for the selector in JavaScript.
218                 // Note that we do not do any checks to guarantee uniqueness. That's the responsiblity
219                 // of the class.
220                 if ([thisClass respondsToSelector:@selector(webScriptNameForKey:)])
221                     mappedName = [thisClass webScriptNameForKey:objcIvarName];
222
223                 if ((mappedName && [mappedName isEqual:(NSString*)fieldName.get()]) || strcmp(objcIvarName, jsName.data()) == 0) {
224                     auto newField = std::make_unique<ObjcField>(objcIVar);
225                     field = newField.get();
226                     m_fieldCache.add(name.impl(), WTFMove(newField));
227                     break;
228                 }
229             }
230
231             thisClass = class_getSuperclass(thisClass);
232             free(ivarsInClass);
233         }
234     }
235
236     return field;
237 }
238
239 JSValue ObjcClass::fallbackObject(ExecState* exec, Instance* instance, PropertyName propertyName)
240 {
241     ObjcInstance* objcInstance = static_cast<ObjcInstance*>(instance);
242     id targetObject = objcInstance->getObject();
243     
244     if (![targetObject respondsToSelector:@selector(invokeUndefinedMethodFromWebScript:withArguments:)])
245         return jsUndefined();
246
247     if (!propertyName.publicName())
248         return jsUndefined();
249
250     return ObjcFallbackObjectImp::create(exec, exec->lexicalGlobalObject(), objcInstance, propertyName.publicName());
251 }
252
253 }
254 }