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 41 struct ObjectImpList { 42 JSObject* imp; 43 ObjectImpList* next; 44 CFTypeRef data; 45 }; 46 47 static CFTypeRef KJSValueToCFTypeInternal(JSValue inValue, ExecState *exec, ObjectImpList* inImps); 48 static JSGlueGlobalObject* getThreadGlobalObject(); 49 50 //-------------------------------------------------------------------------- 51 // CFStringToUString 52 //-------------------------------------------------------------------------- 53 54 UString 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 75 CFStringRef UStringToCFString(const UString& inUString) 76 { 77 return CFStringCreateWithCharacters(0, (const UniChar*)inUString.characters(), inUString.length()); 78 } 79 80 81 //-------------------------------------------------------------------------- 82 // CFStringToIdentifier 83 //-------------------------------------------------------------------------- 84 85 Identifier 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 95 CFStringRef IdentifierToCFString(const Identifier& inIdentifier) 96 { 97 return UStringToCFString(inIdentifier.ustring()); 98 } 99 100 101 //-------------------------------------------------------------------------- 102 // KJSValueToJSObject 103 //-------------------------------------------------------------------------- 104 JSUserObject* 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 //-------------------------------------------------------------------------- 130 JSValue 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 200 CFTypeRef 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 362 CFTypeRef KJSValueToCFType(JSValue inValue, ExecState *exec) 363 { 364 return KJSValueToCFTypeInternal(inValue, exec, 0); 365 } 366 367 CFTypeRef 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 393 static pthread_key_t globalObjectKey; 394 static pthread_once_t globalObjectKeyOnce = PTHREAD_ONCE_INIT; 395 396 static void unprotectGlobalObject(void* data) 397 { 398 JSGlueAPIEntry entry; 399 gcUnprotect(static_cast<JSGlueGlobalObject*>(data)); 400 } 401 402 static void initializeGlobalObjectKey() 403 { 404 pthread_key_create(&globalObjectKey, unprotectGlobalObject); 405 } 406 407 JSGlobalData* getThreadGlobalData() 408 { 409 return &JSGlobalData::sharedInstance(); 410 } 411 412 static 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 424 ExecState* 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 434 JSGlueAPIEntry::JSGlueAPIEntry() 435 : m_lock(LockForReal) 436 , m_storedIdentifierTable(wtfThreadData().currentIdentifierTable()) 437 { 438 wtfThreadData().setCurrentIdentifierTable(getThreadGlobalData()->identifierTable); 439 } 440 441 JSGlueAPIEntry::~JSGlueAPIEntry() 442 { 443 wtfThreadData().setCurrentIdentifierTable(m_storedIdentifierTable); 444 } 445 446 JSGlueAPICallback::JSGlueAPICallback(ExecState* exec) 447 : m_dropLocks(exec) 448 { 449 wtfThreadData().resetCurrentIdentifierTable(); 450 } 451 452 JSGlueAPICallback::~JSGlueAPICallback() 453 { 454 wtfThreadData().setCurrentIdentifierTable(getThreadGlobalData()->identifierTable); 455 } 456