1/*
2 * Copyright (C) 2005, 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 *
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#include <JavaScriptCore/WTFThreadData.h>
40
41struct ObjectImpList {
42    JSObject* imp;
43    ObjectImpList* next;
44    CFTypeRef data;
45};
46
47static CFTypeRef KJSValueToCFTypeInternal(JSValue inValue, ExecState *exec, ObjectImpList* inImps);
48static JSGlueGlobalObject* getThreadGlobalObject();
49
50//--------------------------------------------------------------------------
51// CFStringToUString
52//--------------------------------------------------------------------------
53
54UString CFStringToUString(CFStringRef inCFString)
55{
56    UString result;
57    if (inCFString) {
58        CFIndex len = CFStringGetLength(inCFString);
59        UniChar* buffer = (UniChar*)malloc(sizeof(UniChar) * len);
60        if (buffer)
61        {
62            CFStringGetCharacters(inCFString, CFRangeMake(0, len), buffer);
63            result = UString((const UChar *)buffer, len);
64            free(buffer);
65        }
66    }
67    return result;
68}
69
70
71//--------------------------------------------------------------------------
72// UStringToCFString
73//--------------------------------------------------------------------------
74// Caller is responsible for releasing the returned CFStringRef
75CFStringRef UStringToCFString(const UString& inUString)
76{
77    return CFStringCreateWithCharacters(0, (const UniChar*)inUString.characters(), inUString.length());
78}
79
80
81//--------------------------------------------------------------------------
82// CFStringToIdentifier
83//--------------------------------------------------------------------------
84
85Identifier CFStringToIdentifier(CFStringRef inCFString, ExecState* exec)
86{
87    return Identifier(exec, CFStringToUString(inCFString));
88}
89
90
91//--------------------------------------------------------------------------
92// IdentifierToCFString
93//--------------------------------------------------------------------------
94// Caller is responsible for releasing the returned CFStringRef
95CFStringRef IdentifierToCFString(const Identifier& inIdentifier)
96{
97    return UStringToCFString(inIdentifier.ustring());
98}
99
100
101//--------------------------------------------------------------------------
102// KJSValueToJSObject
103//--------------------------------------------------------------------------
104JSUserObject* KJSValueToJSObject(JSValue inValue, ExecState *exec)
105{
106    JSUserObject* result = 0;
107
108    if (inValue.inherits(&UserObjectImp::s_info)) {
109        UserObjectImp* userObjectImp = static_cast<UserObjectImp *>(asObject(inValue));
110        result = userObjectImp->GetJSUserObject();
111        if (result)
112            result->Retain();
113    } else {
114        JSValueWrapper* wrapperValue = new JSValueWrapper(inValue);
115        if (wrapperValue) {
116            JSObjectCallBacks callBacks;
117            JSValueWrapper::GetJSObectCallBacks(callBacks);
118            result = (JSUserObject*)JSObjectCreate(wrapperValue, &callBacks);
119            if (!result) {
120                delete wrapperValue;
121            }
122        }
123    }
124    return result;
125}
126
127//--------------------------------------------------------------------------
128// JSObjectKJSValue
129//--------------------------------------------------------------------------
130JSValue JSObjectKJSValue(JSUserObject* ptr)
131{
132    JSGlueAPIEntry entry;
133
134    JSValue result = jsUndefined();
135    if (ptr)
136    {
137        bool handled = false;
138
139        switch (ptr->DataType())
140        {
141            case kJSUserObjectDataTypeJSValueWrapper:
142            {
143                JSValueWrapper* wrapper = (JSValueWrapper*)ptr->GetData();
144                if (wrapper)
145                {
146                    result = wrapper->GetValue();
147                    handled = true;
148                }
149                break;
150            }
151
152            case kJSUserObjectDataTypeCFType:
153            {
154                CFTypeRef cfType = (CFTypeRef*)ptr->GetData();
155                if (cfType)
156                {
157                    CFTypeID typeID = CFGetTypeID(cfType);
158                    if (typeID == CFStringGetTypeID())
159                    {
160                        result = jsString(getThreadGlobalExecState(), CFStringToUString((CFStringRef)cfType));
161                        handled = true;
162                    }
163                    else if (typeID == CFNumberGetTypeID())
164                    {
165                        double num;
166                        CFNumberGetValue((CFNumberRef)cfType, kCFNumberDoubleType, &num);
167                        result = jsNumber(num);
168                        handled = true;
169                    }
170                    else if (typeID == CFBooleanGetTypeID())
171                    {
172                        result = jsBoolean(CFBooleanGetValue((CFBooleanRef)cfType));
173                        handled = true;
174                    }
175                    else if (typeID == CFNullGetTypeID())
176                    {
177                        result = jsNull();
178                        handled = true;
179                    }
180                }
181                break;
182            }
183        }
184        if (!handled)
185        {
186            ExecState* exec = getThreadGlobalExecState();
187            result = new (exec) UserObjectImp(exec->globalData(), getThreadGlobalObject()->userObjectStructure(), ptr);
188        }
189    }
190    return result;
191}
192
193
194
195
196//--------------------------------------------------------------------------
197// KJSValueToCFTypeInternal
198//--------------------------------------------------------------------------
199// Caller is responsible for releasing the returned CFTypeRef
200CFTypeRef KJSValueToCFTypeInternal(JSValue inValue, ExecState *exec, ObjectImpList* inImps)
201{
202    if (!inValue)
203        return 0;
204
205    CFTypeRef result = 0;
206
207    JSGlueAPIEntry entry;
208
209        if (inValue.isBoolean())
210            {
211                result = inValue.toBoolean(exec) ? kCFBooleanTrue : kCFBooleanFalse;
212                RetainCFType(result);
213                return result;
214            }
215
216        if (inValue.isString())
217            {
218                UString uString = inValue.toString(exec);
219                result = UStringToCFString(uString);
220                return result;
221            }
222
223        if (inValue.isNumber())
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                return result;
237            }
238
239        if (inValue.isObject())
240            {
241                if (inValue.inherits(&UserObjectImp::s_info)) {
242                    UserObjectImp* userObjectImp = static_cast<UserObjectImp *>(asObject(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::s_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.characters();
288                                int size = ustr.length();
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                return result;
350            }
351
352    if (inValue.isUndefinedOrNull())
353        {
354            result = RetainCFType(GetCFNull());
355            return result;
356        }
357
358    ASSERT_NOT_REACHED();
359    return 0;
360}
361
362CFTypeRef KJSValueToCFType(JSValue inValue, ExecState *exec)
363{
364    return KJSValueToCFTypeInternal(inValue, exec, 0);
365}
366
367CFTypeRef GetCFNull(void)
368{
369    static CFArrayRef sCFNull = CFArrayCreate(0, 0, 0, 0);
370    CFTypeRef result = JSGetCFNull();
371    if (!result)
372    {
373        result = sCFNull;
374    }
375    return result;
376}
377
378/*
379 * This is a slight hack. The JSGlue API has no concept of execution state.
380 * However, execution state is an inherent part of JS, and JSCore requires it.
381 * So, we keep a single execution state for the whole thread and supply it
382 * where necessary.
383
384 * The execution state holds two things: (1) exceptions; (2) the global object.
385 * JSGlue has no API for accessing exceptions, so we just discard them. As for
386 * the global object, JSGlue includes no calls that depend on it. Its property
387 * getters and setters are per-object; they don't walk up the enclosing scope.
388 * Functions called by JSObjectCallFunction may reference values in the enclosing
389 * scope, but they do so through an internally stored scope chain, so we don't
390 * need to supply the global scope.
391 */
392
393static pthread_key_t globalObjectKey;
394static pthread_once_t globalObjectKeyOnce = PTHREAD_ONCE_INIT;
395
396static void unprotectGlobalObject(void* data)
397{
398    JSGlueAPIEntry entry;
399    gcUnprotect(static_cast<JSGlueGlobalObject*>(data));
400}
401
402static void initializeGlobalObjectKey()
403{
404    pthread_key_create(&globalObjectKey, unprotectGlobalObject);
405}
406
407JSGlobalData* getThreadGlobalData()
408{
409    return &JSGlobalData::sharedInstance();
410}
411
412static JSGlueGlobalObject* getThreadGlobalObject()
413{
414    pthread_once(&globalObjectKeyOnce, initializeGlobalObjectKey);
415    JSGlueGlobalObject* globalObject = static_cast<JSGlueGlobalObject*>(pthread_getspecific(globalObjectKey));
416    if (!globalObject) {
417        globalObject = new (getThreadGlobalData()) JSGlueGlobalObject(*getThreadGlobalData(), JSGlueGlobalObject::createStructure(*getThreadGlobalData(), jsNull()));
418        gcProtect(globalObject);
419        pthread_setspecific(globalObjectKey, globalObject);
420    }
421    return globalObject;
422}
423
424ExecState* getThreadGlobalExecState()
425{
426    ExecState* exec = getThreadGlobalObject()->globalExec();
427
428    // Discard exceptions -- otherwise an exception would forestall JS
429    // evaluation throughout the thread
430    exec->clearException();
431    return exec;
432}
433
434JSGlueAPIEntry::JSGlueAPIEntry()
435    : m_lock(LockForReal)
436    , m_storedIdentifierTable(wtfThreadData().currentIdentifierTable())
437{
438    wtfThreadData().setCurrentIdentifierTable(getThreadGlobalData()->identifierTable);
439}
440
441JSGlueAPIEntry::~JSGlueAPIEntry()
442{
443    wtfThreadData().setCurrentIdentifierTable(m_storedIdentifierTable);
444}
445
446JSGlueAPICallback::JSGlueAPICallback(ExecState* exec)
447    : m_dropLocks(exec)
448{
449    wtfThreadData().resetCurrentIdentifierTable();
450}
451
452JSGlueAPICallback::~JSGlueAPICallback()
453{
454    wtfThreadData().setCurrentIdentifierTable(getThreadGlobalData()->identifierTable);
455}
456