Slight clarification to an exception message.
[WebKit-https.git] / JavaScriptCore / API / JSCallbackObject.cpp
1 // -*- mode: c++; c-basic-offset: 4 -*-
2 /*
3  * Copyright (C) 2006 Apple Computer, Inc.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
25  */
26
27 #include <wtf/Platform.h>
28 #include "JSCallbackObject.h"
29
30 #include "APICast.h"
31 #include "JSCallbackFunction.h"
32 #include "JSClassRef.h"
33 #include "JSObjectRef.h"
34 #include "JSStringRef.h"
35 #include "PropertyNameArray.h"
36 #include "internal.h"
37 #include <wtf/Vector.h>
38
39 namespace KJS {
40
41 const ClassInfo JSCallbackObject::info = { "CallbackObject", 0, 0, 0 };
42
43 JSCallbackObject::JSCallbackObject(ExecState* exec, JSClassRef jsClass, JSValue* prototype, void* data)
44     : JSObject(prototype)
45 {
46     init(exec, jsClass, data);
47 }
48
49 void JSCallbackObject::init(ExecState* exec, JSClassRef jsClass, void* data)
50 {
51     m_privateData = data;
52     m_class = JSClassRetain(jsClass);
53
54     Vector<JSObjectInitializeCallback, 16> initRoutines;
55     do {
56         if (JSObjectInitializeCallback initialize = jsClass->initialize)
57             initRoutines.append(initialize);
58     } while ((jsClass = jsClass->parentClass));
59     
60     // initialize from base to derived
61     for (int i = static_cast<int>(initRoutines.size()) - 1; i >= 0; i--) {
62         JSLock::DropAllLocks dropAllLocks;
63         JSObjectInitializeCallback initialize = initRoutines[i];
64         initialize(toRef(exec), toRef(this));
65     }
66 }
67
68 JSCallbackObject::~JSCallbackObject()
69 {
70     JSObjectRef thisRef = toRef(this);
71     
72     for (JSClassRef jsClass = m_class; jsClass; jsClass = jsClass->parentClass)
73         if (JSObjectFinalizeCallback finalize = jsClass->finalize) {
74             finalize(thisRef);
75         }
76     
77     JSClassRelease(m_class);
78 }
79
80 UString JSCallbackObject::className() const
81 {
82     if (!m_class->className.isNull())
83         return m_class->className;
84     
85     return JSObject::className();
86 }
87
88 bool JSCallbackObject::getOwnPropertySlot(ExecState* exec, const Identifier& propertyName, PropertySlot& slot)
89 {
90     JSContextRef ctx = toRef(exec);
91     JSObjectRef thisRef = toRef(this);
92     JSStringRef propertyNameRef = toRef(propertyName.ustring().rep());
93
94     for (JSClassRef jsClass = m_class; jsClass; jsClass = jsClass->parentClass) {
95         // optional optimization to bypass getProperty in cases when we only need to know if the property exists
96         if (JSObjectHasPropertyCallback hasProperty = jsClass->hasProperty) {
97             JSLock::DropAllLocks dropAllLocks;
98             if (hasProperty(ctx, thisRef, propertyNameRef)) {
99                 slot.setCustom(this, callbackGetter);
100                 return true;
101             }
102         } else if (JSObjectGetPropertyCallback getProperty = jsClass->getProperty) {
103             JSLock::DropAllLocks dropAllLocks;
104             if (JSValueRef value = getProperty(ctx, thisRef, propertyNameRef, toRef(exec->exceptionSlot()))) {
105                 // cache the value so we don't have to compute it again
106                 // FIXME: This violates the PropertySlot design a little bit.
107                 // We should either use this optimization everywhere, or nowhere.
108                 slot.setCustom(reinterpret_cast<JSObject*>(toJS(value)), cachedValueGetter);
109                 return true;
110             }
111         }
112
113         if (OpaqueJSClass::StaticValuesTable* staticValues = jsClass->staticValues) {
114             if (staticValues->contains(propertyName.ustring().rep())) {
115                 slot.setCustom(this, staticValueGetter);
116                 return true;
117             }
118         }
119         
120         if (OpaqueJSClass::StaticFunctionsTable* staticFunctions = jsClass->staticFunctions) {
121             if (staticFunctions->contains(propertyName.ustring().rep())) {
122                 slot.setCustom(this, staticFunctionGetter);
123                 return true;
124             }
125         }
126     }
127
128     return JSObject::getOwnPropertySlot(exec, propertyName, slot);
129 }
130
131 bool JSCallbackObject::getOwnPropertySlot(ExecState* exec, unsigned propertyName, PropertySlot& slot)
132 {
133     return getOwnPropertySlot(exec, Identifier::from(propertyName), slot);
134 }
135
136 void JSCallbackObject::put(ExecState* exec, const Identifier& propertyName, JSValue* value, int attr)
137 {
138     JSContextRef ctx = toRef(exec);
139     JSObjectRef thisRef = toRef(this);
140     JSStringRef propertyNameRef = toRef(propertyName.ustring().rep());
141     JSValueRef valueRef = toRef(value);
142
143     for (JSClassRef jsClass = m_class; jsClass; jsClass = jsClass->parentClass) {
144         if (JSObjectSetPropertyCallback setProperty = jsClass->setProperty) {
145             JSLock::DropAllLocks dropAllLocks;
146             if (setProperty(ctx, thisRef, propertyNameRef, valueRef, toRef(exec->exceptionSlot())))
147                 return;
148         }
149     
150         if (OpaqueJSClass::StaticValuesTable* staticValues = jsClass->staticValues) {
151             if (StaticValueEntry* entry = staticValues->get(propertyName.ustring().rep())) {
152                 if (entry->attributes & kJSPropertyAttributeReadOnly)
153                     return;
154                 if (JSObjectSetPropertyCallback setProperty = entry->setProperty) {
155                     JSLock::DropAllLocks dropAllLocks;
156                     if (setProperty(ctx, thisRef, propertyNameRef, valueRef, toRef(exec->exceptionSlot())))
157                         return;
158                 } else
159                     throwError(exec, ReferenceError, "Attempt to set a property that is not settable.");
160             }
161         }
162         
163         if (OpaqueJSClass::StaticFunctionsTable* staticFunctions = jsClass->staticFunctions) {
164             if (StaticFunctionEntry* entry = staticFunctions->get(propertyName.ustring().rep())) {
165                 if (entry->attributes & kJSPropertyAttributeReadOnly)
166                     return;
167                 putDirect(propertyName, value, attr); // put as override property
168                 return;
169             }
170         }
171     }
172
173     return JSObject::put(exec, propertyName, value, attr);
174 }
175
176 void JSCallbackObject::put(ExecState* exec, unsigned propertyName, JSValue* value, int attr)
177 {
178     return put(exec, Identifier::from(propertyName), value, attr);
179 }
180
181 bool JSCallbackObject::deleteProperty(ExecState* exec, const Identifier& propertyName)
182 {
183     JSContextRef ctx = toRef(exec);
184     JSObjectRef thisRef = toRef(this);
185     JSStringRef propertyNameRef = toRef(propertyName.ustring().rep());
186     
187     for (JSClassRef jsClass = m_class; jsClass; jsClass = jsClass->parentClass) {
188         if (JSObjectDeletePropertyCallback deleteProperty = jsClass->deleteProperty) {
189             JSLock::DropAllLocks dropAllLocks;
190             if (deleteProperty(ctx, thisRef, propertyNameRef, toRef(exec->exceptionSlot())))
191                 return true;
192         }
193
194         if (OpaqueJSClass::StaticValuesTable* staticValues = jsClass->staticValues) {
195             if (StaticValueEntry* entry = staticValues->get(propertyName.ustring().rep())) {
196                 if (entry->attributes & kJSPropertyAttributeDontDelete)
197                     return false;
198                 return true;
199             }
200         }
201         
202         if (OpaqueJSClass::StaticFunctionsTable* staticFunctions = jsClass->staticFunctions) {
203             if (StaticFunctionEntry* entry = staticFunctions->get(propertyName.ustring().rep())) {
204                 if (entry->attributes & kJSPropertyAttributeDontDelete)
205                     return false;
206                 return true;
207             }
208         }
209     }
210
211     return JSObject::deleteProperty(exec, propertyName);
212 }
213
214 bool JSCallbackObject::deleteProperty(ExecState* exec, unsigned propertyName)
215 {
216     return deleteProperty(exec, Identifier::from(propertyName));
217 }
218
219 bool JSCallbackObject::implementsConstruct() const
220 {
221     for (JSClassRef jsClass = m_class; jsClass; jsClass = jsClass->parentClass)
222         if (jsClass->callAsConstructor)
223             return true;
224
225     return false;
226 }
227
228 JSObject* JSCallbackObject::construct(ExecState* exec, const List& args)
229 {
230     JSContextRef execRef = toRef(exec);
231     JSObjectRef thisRef = toRef(this);
232     
233     for (JSClassRef jsClass = m_class; jsClass; jsClass = jsClass->parentClass) {
234         if (JSObjectCallAsConstructorCallback callAsConstructor = jsClass->callAsConstructor) {
235             int argumentCount = static_cast<int>(args.size());
236             Vector<JSValueRef, 16> arguments(argumentCount);
237             for (int i = 0; i < argumentCount; i++)
238                 arguments[i] = toRef(args[i]);
239             JSLock::DropAllLocks dropAllLocks;
240             return toJS(callAsConstructor(execRef, thisRef, argumentCount, arguments, toRef(exec->exceptionSlot())));
241         }
242     }
243     
244     ASSERT(0); // implementsConstruct should prevent us from reaching here
245     return 0;
246 }
247
248 bool JSCallbackObject::implementsHasInstance() const
249 {
250     for (JSClassRef jsClass = m_class; jsClass; jsClass = jsClass->parentClass)
251         if (jsClass->hasInstance)
252             return true;
253
254     return false;
255 }
256
257 bool JSCallbackObject::hasInstance(ExecState *exec, JSValue *value)
258 {
259     JSContextRef execRef = toRef(exec);
260     JSObjectRef thisRef = toRef(this);
261
262     for (JSClassRef jsClass = m_class; jsClass; jsClass = jsClass->parentClass)
263         if (JSObjectHasInstanceCallback hasInstance = jsClass->hasInstance) {
264             JSLock::DropAllLocks dropAllLocks;
265             return hasInstance(execRef, thisRef, toRef(value), toRef(exec->exceptionSlot()));
266         }
267
268     ASSERT(0); // implementsHasInstance should prevent us from reaching here
269     return 0;
270 }
271
272
273 bool JSCallbackObject::implementsCall() const
274 {
275     for (JSClassRef jsClass = m_class; jsClass; jsClass = jsClass->parentClass)
276         if (jsClass->callAsFunction)
277             return true;
278     
279     return false;
280 }
281
282 JSValue* JSCallbackObject::callAsFunction(ExecState* exec, JSObject* thisObj, const List &args)
283 {
284     JSContextRef execRef = toRef(exec);
285     JSObjectRef thisRef = toRef(this);
286     JSObjectRef thisObjRef = toRef(thisObj);
287
288     for (JSClassRef jsClass = m_class; jsClass; jsClass = jsClass->parentClass) {
289         if (JSObjectCallAsFunctionCallback callAsFunction = jsClass->callAsFunction) {
290             int argumentCount = static_cast<int>(args.size());
291             Vector<JSValueRef, 16> arguments(argumentCount);
292             for (int i = 0; i < argumentCount; i++)
293                 arguments[i] = toRef(args[i]);
294             JSLock::DropAllLocks dropAllLocks;
295             return toJS(callAsFunction(execRef, thisRef, thisObjRef, argumentCount, arguments, toRef(exec->exceptionSlot())));
296         }
297     }
298
299     ASSERT(0); // implementsCall should prevent us from reaching here
300     return 0;
301 }
302
303 void JSCallbackObject::getPropertyNames(ExecState* exec, PropertyNameArray& propertyNames)
304 {
305     JSContextRef execRef = toRef(exec);
306     JSObjectRef thisRef = toRef(this);
307
308     for (JSClassRef jsClass = m_class; jsClass; jsClass = jsClass->parentClass) {
309         if (JSObjectGetPropertyNamesCallback getPropertyNames = jsClass->getPropertyNames) {
310             JSLock::DropAllLocks dropAllLocks;
311             getPropertyNames(execRef, thisRef, toRef(&propertyNames));
312         }
313
314         if (OpaqueJSClass::StaticValuesTable* staticValues = jsClass->staticValues) {
315             typedef OpaqueJSClass::StaticValuesTable::const_iterator iterator;
316             iterator end = staticValues->end();
317             for (iterator it = staticValues->begin(); it != end; ++it) {
318                 UString::Rep* name = it->first.get();
319                 StaticValueEntry* entry = it->second;
320                 if (entry->getProperty && !(entry->attributes & kJSPropertyAttributeDontEnum))
321                     propertyNames.add(Identifier(name));
322             }
323         }
324
325         if (OpaqueJSClass::StaticFunctionsTable* staticFunctions = jsClass->staticFunctions) {
326             typedef OpaqueJSClass::StaticFunctionsTable::const_iterator iterator;
327             iterator end = staticFunctions->end();
328             for (iterator it = staticFunctions->begin(); it != end; ++it) {
329                 UString::Rep* name = it->first.get();
330                 StaticFunctionEntry* entry = it->second;
331                 if (!(entry->attributes & kJSPropertyAttributeDontEnum))
332                     propertyNames.add(Identifier(name));
333             }
334         }
335     }
336
337     JSObject::getPropertyNames(exec, propertyNames);
338 }
339
340 double JSCallbackObject::toNumber(ExecState* exec) const
341 {
342     JSContextRef ctx = toRef(exec);
343     JSObjectRef thisRef = toRef(this);
344
345     for (JSClassRef jsClass = m_class; jsClass; jsClass = jsClass->parentClass)
346         if (JSObjectConvertToTypeCallback convertToType = jsClass->convertToType) {
347             JSLock::DropAllLocks dropAllLocks;
348             if (JSValueRef value = convertToType(ctx, thisRef, kJSTypeNumber, toRef(exec->exceptionSlot())))
349                 return toJS(value)->getNumber();
350         }
351
352     return JSObject::toNumber(exec);
353 }
354
355 UString JSCallbackObject::toString(ExecState* exec) const
356 {
357     JSContextRef ctx = toRef(exec);
358     JSObjectRef thisRef = toRef(this);
359
360     for (JSClassRef jsClass = m_class; jsClass; jsClass = jsClass->parentClass)
361         if (JSObjectConvertToTypeCallback convertToType = jsClass->convertToType) {
362             JSLock::DropAllLocks dropAllLocks;
363             if (JSValueRef value = convertToType(ctx, thisRef, kJSTypeString, toRef(exec->exceptionSlot())))
364                 return toJS(value)->getString();
365         }
366
367     return JSObject::toString(exec);
368 }
369
370 void JSCallbackObject::setPrivate(void* data)
371 {
372     m_privateData = data;
373 }
374
375 void* JSCallbackObject::getPrivate()
376 {
377     return m_privateData;
378 }
379
380 bool JSCallbackObject::inherits(JSClassRef c) const
381 {
382     for (JSClassRef jsClass = m_class; jsClass; jsClass = jsClass->parentClass)
383         if (jsClass == c)
384             return true;
385
386     return false;
387 }
388
389 JSValue* JSCallbackObject::cachedValueGetter(ExecState*, JSObject*, const Identifier&, const PropertySlot& slot)
390 {
391     JSValue* v = slot.slotBase();
392     ASSERT(v);
393     return v;
394 }
395
396 JSValue* JSCallbackObject::staticValueGetter(ExecState* exec, JSObject*, const Identifier& propertyName, const PropertySlot& slot)
397 {
398     ASSERT(slot.slotBase()->inherits(&JSCallbackObject::info));
399     JSCallbackObject* thisObj = static_cast<JSCallbackObject*>(slot.slotBase());
400
401     JSObjectRef thisRef = toRef(thisObj);
402     JSStringRef propertyNameRef = toRef(propertyName.ustring().rep());
403
404     for (JSClassRef jsClass = thisObj->m_class; jsClass; jsClass = jsClass->parentClass)
405         if (OpaqueJSClass::StaticValuesTable* staticValues = jsClass->staticValues)
406             if (StaticValueEntry* entry = staticValues->get(propertyName.ustring().rep()))
407                 if (JSObjectGetPropertyCallback getProperty = entry->getProperty) {
408                     JSLock::DropAllLocks dropAllLocks;
409                     if (JSValueRef value = getProperty(toRef(exec), thisRef, propertyNameRef, toRef(exec->exceptionSlot())))
410                         return toJS(value);
411                 }
412
413     return throwError(exec, ReferenceError, "Static value property defined with NULL getProperty callback.");
414 }
415
416 JSValue* JSCallbackObject::staticFunctionGetter(ExecState* exec, JSObject*, const Identifier& propertyName, const PropertySlot& slot)
417 {
418     ASSERT(slot.slotBase()->inherits(&JSCallbackObject::info));
419     JSCallbackObject* thisObj = static_cast<JSCallbackObject*>(slot.slotBase());
420
421     if (JSValue* cachedOrOverrideValue = thisObj->getDirect(propertyName))
422         return cachedOrOverrideValue;
423
424     for (JSClassRef jsClass = thisObj->m_class; jsClass; jsClass = jsClass->parentClass) {
425         if (OpaqueJSClass::StaticFunctionsTable* staticFunctions = jsClass->staticFunctions) {
426             if (StaticFunctionEntry* entry = staticFunctions->get(propertyName.ustring().rep())) {
427                 if (JSObjectCallAsFunctionCallback callAsFunction = entry->callAsFunction) {
428                     JSObject* o = new JSCallbackFunction(exec, callAsFunction, propertyName);
429                     thisObj->putDirect(propertyName, o, entry->attributes);
430                     return o;
431                 }
432             }
433         }
434     }
435
436     return throwError(exec, ReferenceError, "Static function property defined with NULL callAsFunction callback.");
437 }
438
439 JSValue* JSCallbackObject::callbackGetter(ExecState* exec, JSObject*, const Identifier& propertyName, const PropertySlot& slot)
440 {
441     ASSERT(slot.slotBase()->inherits(&JSCallbackObject::info));
442     JSCallbackObject* thisObj = static_cast<JSCallbackObject*>(slot.slotBase());
443
444     JSObjectRef thisRef = toRef(thisObj);
445     JSStringRef propertyNameRef = toRef(propertyName.ustring().rep());
446
447     for (JSClassRef jsClass = thisObj->m_class; jsClass; jsClass = jsClass->parentClass)
448         if (JSObjectGetPropertyCallback getProperty = jsClass->getProperty) {
449             JSLock::DropAllLocks dropAllLocks;
450             if (JSValueRef value = getProperty(toRef(exec), thisRef, propertyNameRef, toRef(exec->exceptionSlot())))
451                 return toJS(value);
452         }
453
454     return throwError(exec, ReferenceError, "hasProperty callback returned true for a property that doesn't exist.");
455 }
456
457 } // namespace KJS