Home | History | Annotate | Download | only in JavaScriptGlue
      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 
     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 static JSGlueGlobalObject* getThreadGlobalObject();
     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.inherits(&UserObjectImp::info)) {
    108         UserObjectImp* userObjectImp = static_cast<UserObjectImp *>(asObject(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     JSGlueAPIEntry entry;
    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             ExecState* exec = getThreadGlobalExecState();
    186             result = new (exec) UserObjectImp(getThreadGlobalObject()->userObjectStructure(), ptr);
    187         }
    188     }
    189     return result;
    190 }
    191 
    192 
    193 
    194 
    195 //--------------------------------------------------------------------------
    196 // KJSValueToCFTypeInternal
    197 //--------------------------------------------------------------------------
    198 // Caller is responsible for releasing the returned CFTypeRef
    199 CFTypeRef KJSValueToCFTypeInternal(JSValue inValue, ExecState *exec, ObjectImpList* inImps)
    200 {
    201     if (!inValue)
    202         return 0;
    203 
    204     CFTypeRef result = 0;
    205 
    206     JSGlueAPIEntry entry;
    207 
    208         if (inValue.isBoolean())
    209             {
    210                 result = inValue.toBoolean(exec) ? kCFBooleanTrue : kCFBooleanFalse;
    211                 RetainCFType(result);
    212                 return result;
    213             }
    214 
    215         if (inValue.isString())
    216             {
    217                 UString uString = inValue.toString(exec);
    218                 result = UStringToCFString(uString);
    219                 return result;
    220             }
    221 
    222         if (inValue.isNumber())
    223             {
    224                 double number1 = inValue.toNumber(exec);
    225                 double number2 = (double)inValue.toInteger(exec);
    226                 if (number1 ==  number2)
    227                 {
    228                     int intValue = (int)number2;
    229                     result = CFNumberCreate(0, kCFNumberIntType, &intValue);
    230                 }
    231                 else
    232                 {
    233                     result = CFNumberCreate(0, kCFNumberDoubleType, &number1);
    234                 }
    235                 return result;
    236             }
    237 
    238         if (inValue.isObject())
    239             {
    240                 if (inValue.inherits(&UserObjectImp::info)) {
    241                     UserObjectImp* userObjectImp = static_cast<UserObjectImp *>(asObject(inValue));
    242                     JSUserObject* ptr = userObjectImp->GetJSUserObject();
    243                     if (ptr)
    244                     {
    245                         result = ptr->CopyCFValue();
    246                     }
    247                 }
    248                 else
    249                 {
    250                     JSObject *object = inValue.toObject(exec);
    251                     UInt8 isArray = false;
    252 
    253                     // if two objects reference each
    254                     JSObject* imp = object;
    255                     ObjectImpList* temp = inImps;
    256                     while (temp) {
    257                         if (imp == temp->imp) {
    258                             return CFRetain(GetCFNull());
    259                         }
    260                         temp = temp->next;
    261                     }
    262 
    263                     ObjectImpList imps;
    264                     imps.next = inImps;
    265                     imps.imp = imp;
    266 
    267 
    268 //[...] HACK since we do not have access to the class info we use class name instead
    269 #if 0
    270                     if (object->inherits(&ArrayInstanceImp::info))
    271 #else
    272                     if (object->className() == "Array")
    273 #endif
    274                     {
    275                         isArray = true;
    276                         JSGlueGlobalObject* globalObject = static_cast<JSGlueGlobalObject*>(exec->dynamicGlobalObject());
    277                         if (globalObject && (globalObject->Flags() & kJSFlagConvertAssociativeArray)) {
    278                             PropertyNameArray propNames(exec);
    279                             object->getPropertyNames(exec, propNames);
    280                             PropertyNameArray::const_iterator iter = propNames.begin();
    281                             PropertyNameArray::const_iterator end = propNames.end();
    282                             while(iter != end && isArray)
    283                             {
    284                                 Identifier propName = *iter;
    285                                 UString ustr = propName.ustring();
    286                                 const UniChar* uniChars = (const UniChar*)ustr.data();
    287                                 int size = ustr.size();
    288                                 while (size--) {
    289                                     if (uniChars[size] < '0' || uniChars[size] > '9') {
    290                                         isArray = false;
    291                                         break;
    292                                     }
    293                                 }
    294                                 iter++;
    295                             }
    296                         }
    297                     }
    298 
    299                     if (isArray)
    300                     {
    301                         // This is an KJS array
    302                         unsigned int length = object->get(exec, Identifier(exec, "length")).toUInt32(exec);
    303                         result = CFArrayCreateMutable(0, 0, &kCFTypeArrayCallBacks);
    304                         if (result)
    305                         {
    306                             for (unsigned i = 0; i < length; i++)
    307                             {
    308                                 CFTypeRef cfValue = KJSValueToCFTypeInternal(object->get(exec, i), exec, &imps);
    309                                 CFArrayAppendValue((CFMutableArrayRef)result, cfValue);
    310                                 ReleaseCFType(cfValue);
    311                             }
    312                         }
    313                     }
    314                     else
    315                     {
    316                         // Not an array, just treat it like a dictionary which contains (property name, property value) pairs
    317                         PropertyNameArray propNames(exec);
    318                         object->getPropertyNames(exec, propNames);
    319                         {
    320                             result = CFDictionaryCreateMutable(0,
    321                                                                0,
    322                                                                &kCFTypeDictionaryKeyCallBacks,
    323                                                                &kCFTypeDictionaryValueCallBacks);
    324                             if (result)
    325                             {
    326                                 PropertyNameArray::const_iterator iter = propNames.begin();
    327                                 PropertyNameArray::const_iterator end = propNames.end();
    328                                 while(iter != end)
    329                                 {
    330                                     Identifier propName = *iter;
    331                                     if (object->hasProperty(exec, propName))
    332                                     {
    333                                         CFStringRef cfKey = IdentifierToCFString(propName);
    334                                         CFTypeRef cfValue = KJSValueToCFTypeInternal(object->get(exec, propName), exec, &imps);
    335                                         if (cfKey && cfValue)
    336                                         {
    337                                             CFDictionaryAddValue((CFMutableDictionaryRef)result, cfKey, cfValue);
    338                                         }
    339                                         ReleaseCFType(cfKey);
    340                                         ReleaseCFType(cfValue);
    341                                     }
    342                                     iter++;
    343                                 }
    344                             }
    345                         }
    346                     }
    347                 }
    348                 return result;
    349             }
    350 
    351     if (inValue.isUndefinedOrNull())
    352         {
    353             result = RetainCFType(GetCFNull());
    354             return result;
    355         }
    356 
    357     ASSERT_NOT_REACHED();
    358     return 0;
    359 }
    360 
    361 CFTypeRef KJSValueToCFType(JSValue inValue, ExecState *exec)
    362 {
    363     return KJSValueToCFTypeInternal(inValue, exec, 0);
    364 }
    365 
    366 CFTypeRef GetCFNull(void)
    367 {
    368     static CFArrayRef sCFNull = CFArrayCreate(0, 0, 0, 0);
    369     CFTypeRef result = JSGetCFNull();
    370     if (!result)
    371     {
    372         result = sCFNull;
    373     }
    374     return result;
    375 }
    376 
    377 /*
    378  * This is a slight hack. The JSGlue API has no concept of execution state.
    379  * However, execution state is an inherent part of JS, and JSCore requires it.
    380  * So, we keep a single execution state for the whole thread and supply it
    381  * where necessary.
    382 
    383  * The execution state holds two things: (1) exceptions; (2) the global object.
    384  * JSGlue has no API for accessing exceptions, so we just discard them. As for
    385  * the global object, JSGlue includes no calls that depend on it. Its property
    386  * getters and setters are per-object; they don't walk up the enclosing scope.
    387  * Functions called by JSObjectCallFunction may reference values in the enclosing
    388  * scope, but they do so through an internally stored scope chain, so we don't
    389  * need to supply the global scope.
    390  */
    391 
    392 static pthread_key_t globalObjectKey;
    393 static pthread_once_t globalObjectKeyOnce = PTHREAD_ONCE_INIT;
    394 
    395 static void unprotectGlobalObject(void* data)
    396 {
    397     JSGlueAPIEntry entry;
    398     gcUnprotect(static_cast<JSGlueGlobalObject*>(data));
    399 }
    400 
    401 static void initializeGlobalObjectKey()
    402 {
    403     pthread_key_create(&globalObjectKey, unprotectGlobalObject);
    404 }
    405 
    406 static JSGlueGlobalObject* getThreadGlobalObject()
    407 {
    408     pthread_once(&globalObjectKeyOnce, initializeGlobalObjectKey);
    409     JSGlueGlobalObject* globalObject = static_cast<JSGlueGlobalObject*>(pthread_getspecific(globalObjectKey));
    410     if (!globalObject) {
    411         globalObject = new (&JSGlobalData::sharedInstance()) JSGlueGlobalObject(JSGlueGlobalObject::createStructure(jsNull()));
    412         gcProtect(globalObject);
    413         pthread_setspecific(globalObjectKey, globalObject);
    414     }
    415     return globalObject;
    416 }
    417 
    418 ExecState* getThreadGlobalExecState()
    419 {
    420     ExecState* exec = getThreadGlobalObject()->globalExec();
    421 
    422     // Discard exceptions -- otherwise an exception would forestall JS
    423     // evaluation throughout the thread
    424     exec->clearException();
    425     return exec;
    426 }
    427 
    428 JSGlueAPIEntry::JSGlueAPIEntry()
    429     : m_lock(LockForReal)
    430     , m_storedIdentifierTable(currentIdentifierTable())
    431 {
    432     setCurrentIdentifierTable(getThreadGlobalObject()->globalExec()->globalData().identifierTable);
    433 }
    434 
    435 JSGlueAPIEntry::~JSGlueAPIEntry()
    436 {
    437     setCurrentIdentifierTable(m_storedIdentifierTable);
    438 }
    439 
    440 JSGlueAPICallback::JSGlueAPICallback(ExecState* exec)
    441     : m_dropLocks(exec)
    442 {
    443     resetCurrentIdentifierTable();
    444 }
    445 
    446 JSGlueAPICallback::~JSGlueAPICallback()
    447 {
    448     setCurrentIdentifierTable(getThreadGlobalObject()->globalExec()->globalData().identifierTable);
    449 }
    450