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