2008-07-02 Geoffrey Garen <ggaren@apple.com>
[WebKit-https.git] / JavaScriptGlue / JSUtils.cpp
1 /*
2  * Copyright (C) 2005 Apple Computer, 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  *
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  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission. 
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #include "config.h"
30 #include "JSUtils.h"
31
32 #include "JSBase.h"
33 #include "JSObject.h"
34 #include "JSRun.h"
35 #include "JSValueWrapper.h"
36 #include "UserObjectImp.h"
37 #include <JavaScriptCore/JSString.h>
38 #include <JavaScriptCore/PropertyNameArray.h>
39
40 struct ObjectImpList {
41     JSObject* imp;
42     ObjectImpList* next;
43     CFTypeRef data;
44 };
45
46 static CFTypeRef KJSValueToCFTypeInternal(JSValue *inValue, ExecState *exec, ObjectImpList* inImps);
47
48
49 //--------------------------------------------------------------------------
50 // CFStringToUString
51 //--------------------------------------------------------------------------
52
53 UString CFStringToUString(CFStringRef inCFString)
54 {
55     UString result;
56     if (inCFString) {
57         CFIndex len = CFStringGetLength(inCFString);
58         UniChar* buffer = (UniChar*)malloc(sizeof(UniChar) * len);
59         if (buffer)
60         {
61             CFStringGetCharacters(inCFString, CFRangeMake(0, len), buffer);
62             result = UString((const UChar *)buffer, len);
63             free(buffer);
64         }
65     }
66     return result;
67 }
68
69
70 //--------------------------------------------------------------------------
71 // UStringToCFString
72 //--------------------------------------------------------------------------
73 // Caller is responsible for releasing the returned CFStringRef
74 CFStringRef UStringToCFString(const UString& inUString)
75 {
76     return CFStringCreateWithCharacters(0, (const UniChar*)inUString.data(), inUString.size());
77 }
78
79
80 //--------------------------------------------------------------------------
81 // CFStringToIdentifier
82 //--------------------------------------------------------------------------
83
84 Identifier CFStringToIdentifier(CFStringRef inCFString, ExecState* exec)
85 {
86     return Identifier(exec, CFStringToUString(inCFString));
87 }
88
89
90 //--------------------------------------------------------------------------
91 // IdentifierToCFString
92 //--------------------------------------------------------------------------
93 // Caller is responsible for releasing the returned CFStringRef
94 CFStringRef IdentifierToCFString(const Identifier& inIdentifier)
95 {
96     return UStringToCFString(inIdentifier.ustring());
97 }
98
99
100 //--------------------------------------------------------------------------
101 // KJSValueToJSObject
102 //--------------------------------------------------------------------------
103 JSUserObject* KJSValueToJSObject(JSValue *inValue, ExecState *exec)
104 {
105     JSUserObject* result = 0;
106
107     if (inValue->isObject(&UserObjectImp::info)) {
108         UserObjectImp* userObjectImp = static_cast<UserObjectImp *>(inValue);
109         result = userObjectImp->GetJSUserObject();
110         if (result)
111             result->Retain();
112     } else {
113         JSValueWrapper* wrapperValue = new JSValueWrapper(inValue);
114         if (wrapperValue) {
115             JSObjectCallBacks callBacks;
116             JSValueWrapper::GetJSObectCallBacks(callBacks);
117             result = (JSUserObject*)JSObjectCreate(wrapperValue, &callBacks);
118             if (!result) {
119                 delete wrapperValue;
120             }
121         }
122     }
123     return result;
124 }
125
126 //--------------------------------------------------------------------------
127 // JSObjectKJSValue
128 //--------------------------------------------------------------------------
129 JSValue *JSObjectKJSValue(JSUserObject* ptr)
130 {
131     JSLock lock(true);
132
133     JSValue *result = jsUndefined();
134     if (ptr)
135     {
136         bool handled = false;
137
138         switch (ptr->DataType())
139         {
140             case kJSUserObjectDataTypeJSValueWrapper:
141             {
142                 JSValueWrapper* wrapper = (JSValueWrapper*)ptr->GetData();
143                 if (wrapper)
144                 {
145                     result = wrapper->GetValue();
146                     handled = true;
147                 }
148                 break;
149             }
150
151             case kJSUserObjectDataTypeCFType:
152             {
153                 CFTypeRef cfType = (CFTypeRef*)ptr->GetData();
154                 if (cfType)
155                 {
156                     CFTypeID typeID = CFGetTypeID(cfType);
157                     if (typeID == CFStringGetTypeID())
158                     {
159                         result = jsString(getThreadGlobalExecState(), CFStringToUString((CFStringRef)cfType));
160                         handled = true;
161                     }
162                     else if (typeID == CFNumberGetTypeID())
163                     {
164                         double num;
165                         CFNumberGetValue((CFNumberRef)cfType, kCFNumberDoubleType, &num);
166                         result = jsNumber(getThreadGlobalExecState(), num);
167                         handled = true;
168                     }
169                     else if (typeID == CFBooleanGetTypeID())
170                     {
171                         result = jsBoolean(CFBooleanGetValue((CFBooleanRef)cfType));
172                         handled = true;
173                     }
174                     else if (typeID == CFNullGetTypeID())
175                     {
176                         result = jsNull();
177                         handled = true;
178                     }
179                 }
180                 break;
181             }
182         }
183         if (!handled)
184         {
185             result = new (getThreadGlobalExecState()) UserObjectImp(ptr);
186         }
187     }
188     return result;
189 }
190
191
192
193
194 //--------------------------------------------------------------------------
195 // KJSValueToCFTypeInternal
196 //--------------------------------------------------------------------------
197 // Caller is responsible for releasing the returned CFTypeRef
198 CFTypeRef KJSValueToCFTypeInternal(JSValue *inValue, ExecState *exec, ObjectImpList* inImps)
199 {
200     if (!inValue)
201         return 0;
202
203     CFTypeRef result = 0;
204
205     JSLock lock(true);
206
207     switch (inValue->type())
208     {
209         case BooleanType:
210             {
211                 result = inValue->toBoolean(exec) ? kCFBooleanTrue : kCFBooleanFalse;
212                 RetainCFType(result);
213             }
214             break;
215
216         case StringType:
217             {
218                 UString uString = inValue->toString(exec);
219                 result = UStringToCFString(uString);
220             }
221             break;
222
223         case NumberType:
224             {
225                 double number1 = inValue->toNumber(exec);
226                 double number2 = (double)inValue->toInteger(exec);
227                 if (number1 ==  number2)
228                 {
229                     int intValue = (int)number2;
230                     result = CFNumberCreate(0, kCFNumberIntType, &intValue);
231                 }
232                 else
233                 {
234                     result = CFNumberCreate(0, kCFNumberDoubleType, &number1);
235                 }
236             }
237             break;
238
239         case ObjectType:
240             {
241                             if (inValue->isObject(&UserObjectImp::info)) {
242                                 UserObjectImp* userObjectImp = static_cast<UserObjectImp *>(inValue);
243                     JSUserObject* ptr = userObjectImp->GetJSUserObject();
244                     if (ptr)
245                     {
246                         result = ptr->CopyCFValue();
247                     }
248                 }
249                 else
250                 {
251                     JSObject *object = inValue->toObject(exec);
252                     UInt8 isArray = false;
253
254                     // if two objects reference each
255                     JSObject* imp = object;
256                     ObjectImpList* temp = inImps;
257                     while (temp) {
258                         if (imp == temp->imp) {
259                             return CFRetain(GetCFNull());
260                         }
261                         temp = temp->next;
262                     }
263
264                     ObjectImpList imps;
265                     imps.next = inImps;
266                     imps.imp = imp;
267
268
269 //[...] HACK since we do not have access to the class info we use class name instead
270 #if 0
271                     if (object->inherits(&ArrayInstanceImp::info))
272 #else
273                     if (object->className() == "Array")
274 #endif
275                     {
276                         isArray = true;
277                         JSGlueGlobalObject* globalObject = static_cast<JSGlueGlobalObject*>(exec->dynamicGlobalObject());
278                         if (globalObject && (globalObject->Flags() & kJSFlagConvertAssociativeArray)) {
279                             PropertyNameArray propNames(exec);
280                             object->getPropertyNames(exec, propNames);
281                             PropertyNameArray::const_iterator iter = propNames.begin();
282                             PropertyNameArray::const_iterator end = propNames.end();
283                             while(iter != end && isArray)
284                             {
285                                 Identifier propName = *iter;
286                                 UString ustr = propName.ustring();
287                                 const UniChar* uniChars = (const UniChar*)ustr.data();
288                                 int size = ustr.size();
289                                 while (size--) {
290                                     if (uniChars[size] < '0' || uniChars[size] > '9') {
291                                         isArray = false;
292                                         break;
293                                     }
294                                 }
295                                 iter++;
296                             }
297                         }
298                     }
299
300                     if (isArray)
301                     {
302                         // This is an KJS array
303                         unsigned int length = object->get(exec, Identifier(exec, "length"))->toUInt32(exec);
304                         result = CFArrayCreateMutable(0, 0, &kCFTypeArrayCallBacks);
305                         if (result)
306                         {
307                             for (unsigned i = 0; i < length; i++)
308                             {
309                                 CFTypeRef cfValue = KJSValueToCFTypeInternal(object->get(exec, i), exec, &imps);
310                                 CFArrayAppendValue((CFMutableArrayRef)result, cfValue);
311                                 ReleaseCFType(cfValue);
312                             }
313                         }
314                     }
315                     else
316                     {
317                         // Not an array, just treat it like a dictionary which contains (property name, property value) pairs
318                         PropertyNameArray propNames(exec);
319                         object->getPropertyNames(exec, propNames);
320                         {
321                             result = CFDictionaryCreateMutable(0,
322                                                                0,
323                                                                &kCFTypeDictionaryKeyCallBacks,
324                                                                &kCFTypeDictionaryValueCallBacks);
325                             if (result)
326                             {
327                                 PropertyNameArray::const_iterator iter = propNames.begin();
328                                 PropertyNameArray::const_iterator end = propNames.end();
329                                 while(iter != end)
330                                 {
331                                     Identifier propName = *iter;
332                                     if (object->hasProperty(exec, propName))
333                                     {
334                                         CFStringRef cfKey = IdentifierToCFString(propName);
335                                         CFTypeRef cfValue = KJSValueToCFTypeInternal(object->get(exec, propName), exec, &imps);
336                                         if (cfKey && cfValue)
337                                         {
338                                             CFDictionaryAddValue((CFMutableDictionaryRef)result, cfKey, cfValue);
339                                         }
340                                         ReleaseCFType(cfKey);
341                                         ReleaseCFType(cfValue);
342                                     }
343                                     iter++;
344                                 }
345                             }
346                         }
347                     }
348                 }
349             }
350             break;
351
352         case NullType:
353         case UndefinedType:
354         case UnspecifiedType:
355             result = RetainCFType(GetCFNull());
356             break;
357
358         default:
359             fprintf(stderr, "KJSValueToCFType: wrong value type %d\n", inValue->type());
360             break;
361     }
362
363     return result;
364 }
365
366 CFTypeRef KJSValueToCFType(JSValue *inValue, ExecState *exec)
367 {
368     return KJSValueToCFTypeInternal(inValue, exec, 0);
369 }
370
371 CFTypeRef GetCFNull(void)
372 {
373     static CFArrayRef sCFNull = CFArrayCreate(0, 0, 0, 0);
374     CFTypeRef result = JSGetCFNull();
375     if (!result)
376     {
377         result = sCFNull;
378     }
379     return result;
380 }
381
382 /*
383  * This is a slight hack. The JSGlue API has no concept of execution state.
384  * However, execution state is an inherent part of JS, and JSCore requires it.
385  * So, we keep a single execution state for the whole thread and supply it
386  * where necessary.
387
388  * The execution state holds two things: (1) exceptions; (2) the global object. 
389  * JSGlue has no API for accessing exceptions, so we just discard them. As for
390  * the global object, JSGlue includes no calls that depend on it. Its property
391  * getters and setters are per-object; they don't walk up the enclosing scope. 
392  * Functions called by JSObjectCallFunction may reference values in the enclosing 
393  * scope, but they do so through an internally stored scope chain, so we don't 
394  * need to supply the global scope.
395  */      
396
397 static pthread_key_t globalObjectKey;
398 static pthread_once_t globalObjectKeyOnce = PTHREAD_ONCE_INIT;
399
400 static void unprotectGlobalObject(void* data) 
401 {
402     JSLock lock(true);
403     gcUnprotect(static_cast<JSGlobalObject*>(data));
404 }
405
406 static void initializeGlobalObjectKey()
407 {
408     pthread_key_create(&globalObjectKey, unprotectGlobalObject);
409 }
410
411 ExecState* getThreadGlobalExecState()
412 {
413     pthread_once(&globalObjectKeyOnce, initializeGlobalObjectKey);
414     JSGlobalObject* globalObject = static_cast<JSGlobalObject*>(pthread_getspecific(globalObjectKey));
415     if (!globalObject) {
416         globalObject = new (JSGlobalObject::Shared) JSGlueGlobalObject;
417         gcProtect(globalObject);
418         pthread_setspecific(globalObjectKey, globalObject);
419     }
420     
421     ExecState* exec = globalObject->globalExec();
422
423     // Discard exceptions -- otherwise an exception would forestall JS 
424     // evaluation throughout the thread
425     exec->clearException();
426     return exec;
427 }
428