fa355a595de23ea516fc4ae07b8c71d7b7a097df
[WebKit-https.git] / Source / WebCore / bridge / objc / objc_runtime.mm
1 /*
2  * Copyright (C) 2004, 2008 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 COMPUTER, 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 COMPUTER, 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_runtime.h"
28
29 #include "JSDOMBinding.h"
30 #include "ObjCRuntimeObject.h"
31 #include "WebScriptObject.h"
32 #include "objc_instance.h"
33 #include "runtime_array.h"
34 #include "runtime_object.h"
35 #include <runtime/Error.h>
36 #include <runtime/JSGlobalObject.h>
37 #include <runtime/JSLock.h>
38 #include <wtf/RetainPtr.h>
39
40 using namespace WebCore;
41
42 namespace JSC {
43 namespace Bindings {
44
45 ClassStructPtr webScriptObjectClass()
46 {
47     static ClassStructPtr<WebScriptObject> webScriptObjectClass = NSClassFromString(@"WebScriptObject");
48     return webScriptObjectClass;
49 }
50
51 ClassStructPtr webUndefinedClass()
52 {
53     static ClassStructPtr<WebUndefined> webUndefinedClass = NSClassFromString(@"WebUndefined");
54     return webUndefinedClass;
55 }
56
57 // ---------------------- ObjcMethod ----------------------
58
59 ObjcMethod::ObjcMethod(ClassStructPtr aClass, SEL selector)
60     : _objcClass(aClass)
61     , _selector(selector)
62 {
63 }
64
65 int ObjcMethod::numParameters() const
66 {
67     return [getMethodSignature() numberOfArguments] - 2;
68 }
69
70 NSMethodSignature* ObjcMethod::getMethodSignature() const
71 {
72     return [_objcClass instanceMethodSignatureForSelector:_selector];
73 }
74
75 // ---------------------- ObjcField ----------------------
76
77 ObjcField::ObjcField(Ivar ivar) 
78     : _ivar(ivar)
79     , _name(adoptCF(CFStringCreateWithCString(0, ivar_getName(_ivar), kCFStringEncodingASCII)))
80 {
81 }
82
83 ObjcField::ObjcField(CFStringRef name)
84     : _ivar(0)
85     , _name(name)
86 {
87 }
88
89 JSValue ObjcField::valueFromInstance(ExecState* exec, const Instance* instance) const
90 {
91     JSValue result = jsUndefined();
92     
93     id targetObject = (static_cast<const ObjcInstance*>(instance))->getObject();
94
95     JSLock::DropAllLocks dropAllLocks(exec); // Can't put this inside the @try scope because it unwinds incorrectly.
96
97     @try {
98         if (id objcValue = [targetObject valueForKey:(NSString *)_name.get()])
99             result = convertObjcValueToValue(exec, &objcValue, ObjcObjectType, instance->rootObject());
100         {
101             JSLockHolder lock(exec);
102             ObjcInstance::moveGlobalExceptionToExecState(exec);
103         }
104     } @catch(NSException* localException) {
105         JSLockHolder lock(exec);
106         throwError(exec, [localException reason]);
107     }
108
109     // Work around problem in some versions of GCC where result gets marked volatile and
110     // it can't handle copying from a volatile to non-volatile.
111     return const_cast<JSValue&>(result);
112 }
113
114 static id convertValueToObjcObject(ExecState* exec, JSValue value)
115 {
116     RefPtr<RootObject> rootObject = findRootObject(exec->dynamicGlobalObject());
117     if (!rootObject)
118         return nil;
119     return [webScriptObjectClass() _convertValueToObjcValue:value originRootObject:rootObject.get() rootObject:rootObject.get()];
120 }
121
122 void ObjcField::setValueToInstance(ExecState* exec, const Instance* instance, JSValue aValue) const
123 {
124     id targetObject = (static_cast<const ObjcInstance*>(instance))->getObject();
125     id value = convertValueToObjcObject(exec, aValue);
126
127     JSLock::DropAllLocks dropAllLocks(exec); // Can't put this inside the @try scope because it unwinds incorrectly.
128
129     @try {
130         [targetObject setValue:value forKey:(NSString *)_name.get()];
131         {
132             JSLockHolder lock(exec);
133             ObjcInstance::moveGlobalExceptionToExecState(exec);
134         }
135     } @catch(NSException* localException) {
136         JSLockHolder lock(exec);
137         throwError(exec, [localException reason]);
138     }
139 }
140
141 // ---------------------- ObjcArray ----------------------
142
143 ObjcArray::ObjcArray(ObjectStructPtr a, PassRefPtr<RootObject> rootObject)
144     : Array(rootObject)
145     , _array(a)
146 {
147 }
148
149 void ObjcArray::setValueAt(ExecState* exec, unsigned int index, JSValue aValue) const
150 {
151     if (![_array.get() respondsToSelector:@selector(insertObject:atIndex:)]) {
152         throwError(exec, createTypeError(exec, "Array is not mutable."));
153         return;
154     }
155
156     if (index > [_array.get() count]) {
157         throwError(exec, createRangeError(exec, "Index exceeds array size."));
158         return;
159     }
160     
161     // Always try to convert the value to an ObjC object, so it can be placed in the
162     // array.
163     ObjcValue oValue = convertValueToObjcValue (exec, aValue, ObjcObjectType);
164
165     @try {
166         [_array.get() insertObject:oValue.objectValue atIndex:index];
167     } @catch(NSException* localException) {
168         throwError(exec, createError(exec, "Objective-C exception."));
169     }
170 }
171
172 JSValue ObjcArray::valueAt(ExecState* exec, unsigned int index) const
173 {
174     if (index > [_array.get() count])
175         return throwError(exec, createRangeError(exec, "Index exceeds array size."));
176     @try {
177         id obj = [_array.get() objectAtIndex:index];
178         if (obj)
179             return convertObjcValueToValue (exec, &obj, ObjcObjectType, m_rootObject.get());
180     } @catch(NSException* localException) {
181         return throwError(exec, createError(exec, "Objective-C exception."));
182     }
183     return jsUndefined();
184 }
185
186 unsigned int ObjcArray::getLength() const
187 {
188     return [_array.get() count];
189 }
190
191 const ClassInfo ObjcFallbackObjectImp::s_info = { "ObjcFallbackObject", &Base::s_info, 0, 0, CREATE_METHOD_TABLE(ObjcFallbackObjectImp) };
192
193 ObjcFallbackObjectImp::ObjcFallbackObjectImp(JSGlobalObject* globalObject, Structure* structure, ObjcInstance* i, const String& propertyName)
194     : JSDestructibleObject(globalObject->vm(), structure)
195     , _instance(i)
196     , m_item(propertyName)
197 {
198 }
199
200 void ObjcFallbackObjectImp::destroy(JSCell* cell)
201 {
202     static_cast<ObjcFallbackObjectImp*>(cell)->ObjcFallbackObjectImp::~ObjcFallbackObjectImp();
203 }
204
205 void ObjcFallbackObjectImp::finishCreation(JSGlobalObject* globalObject)
206 {
207     Base::finishCreation(globalObject->vm());
208     ASSERT(inherits(info()));
209 }
210
211 bool ObjcFallbackObjectImp::getOwnPropertySlot(JSObject*, ExecState*, PropertyName, PropertySlot& slot)
212 {
213     // keep the prototype from getting called instead of just returning false
214     slot.setUndefined();
215     return true;
216 }
217
218 void ObjcFallbackObjectImp::put(JSCell*, ExecState*, PropertyName, JSValue, PutPropertySlot&)
219 {
220 }
221
222 static EncodedJSValue JSC_HOST_CALL callObjCFallbackObject(ExecState* exec)
223 {
224     JSValue thisValue = exec->hostThisValue();
225     if (!thisValue.inherits(ObjCRuntimeObject::info()))
226         return throwVMTypeError(exec);
227
228     JSValue result = jsUndefined();
229
230     ObjCRuntimeObject* runtimeObject = static_cast<ObjCRuntimeObject*>(asObject(thisValue));
231     ObjcInstance* objcInstance = runtimeObject->getInternalObjCInstance();
232
233     if (!objcInstance)
234         return JSValue::encode(RuntimeObject::throwInvalidAccessError(exec));
235     
236     objcInstance->begin();
237
238     id targetObject = objcInstance->getObject();
239     
240     if ([targetObject respondsToSelector:@selector(invokeUndefinedMethodFromWebScript:withArguments:)]){
241         ObjcClass* objcClass = static_cast<ObjcClass*>(objcInstance->getClass());
242         OwnPtr<ObjcMethod> fallbackMethod(adoptPtr(new ObjcMethod(objcClass->isa(), @selector(invokeUndefinedMethodFromWebScript:withArguments:))));
243         const String& nameIdentifier = static_cast<ObjcFallbackObjectImp*>(exec->callee())->propertyName();
244         fallbackMethod->setJavaScriptName(nameIdentifier.createCFString().get());
245         result = objcInstance->invokeObjcMethod(exec, fallbackMethod.get());
246     }
247             
248     objcInstance->end();
249
250     return JSValue::encode(result);
251 }
252
253 CallType ObjcFallbackObjectImp::getCallData(JSCell* cell, CallData& callData)
254 {
255     ObjcFallbackObjectImp* thisObject = jsCast<ObjcFallbackObjectImp*>(cell);
256     id targetObject = thisObject->_instance->getObject();
257     if (![targetObject respondsToSelector:@selector(invokeUndefinedMethodFromWebScript:withArguments:)])
258         return CallTypeNone;
259     callData.native.function = callObjCFallbackObject;
260     return CallTypeHost;
261 }
262
263 bool ObjcFallbackObjectImp::deleteProperty(JSCell*, ExecState*, PropertyName)
264 {
265     return false;
266 }
267
268 JSValue ObjcFallbackObjectImp::defaultValue(const JSObject* object, ExecState* exec, PreferredPrimitiveType)
269 {
270     const ObjcFallbackObjectImp* thisObject = jsCast<const ObjcFallbackObjectImp*>(object);
271     return thisObject->_instance->getValueOfUndefinedField(exec, Identifier(exec, thisObject->m_item));
272 }
273
274 bool ObjcFallbackObjectImp::toBoolean(ExecState *) const
275 {
276     id targetObject = _instance->getObject();
277     
278     if ([targetObject respondsToSelector:@selector(invokeUndefinedMethodFromWebScript:withArguments:)])
279         return true;
280     
281     return false;
282 }
283
284 }
285 }