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