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
CFStringToUString(CFStringRef inCFString)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
UStringToCFString(const UString & inUString)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
CFStringToIdentifier(CFStringRef inCFString,ExecState * exec)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
IdentifierToCFString(const Identifier & inIdentifier)95 CFStringRef IdentifierToCFString(const Identifier& inIdentifier)
96 {
97 return UStringToCFString(inIdentifier.ustring());
98 }
99
100
101 //--------------------------------------------------------------------------
102 // KJSValueToJSObject
103 //--------------------------------------------------------------------------
KJSValueToJSObject(JSValue inValue,ExecState * exec)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 //--------------------------------------------------------------------------
JSObjectKJSValue(JSUserObject * ptr)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
KJSValueToCFTypeInternal(JSValue inValue,ExecState * exec,ObjectImpList * inImps)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
KJSValueToCFType(JSValue inValue,ExecState * exec)362 CFTypeRef KJSValueToCFType(JSValue inValue, ExecState *exec)
363 {
364 return KJSValueToCFTypeInternal(inValue, exec, 0);
365 }
366
GetCFNull(void)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
unprotectGlobalObject(void * data)396 static void unprotectGlobalObject(void* data)
397 {
398 JSGlueAPIEntry entry;
399 gcUnprotect(static_cast<JSGlueGlobalObject*>(data));
400 }
401
initializeGlobalObjectKey()402 static void initializeGlobalObjectKey()
403 {
404 pthread_key_create(&globalObjectKey, unprotectGlobalObject);
405 }
406
getThreadGlobalData()407 JSGlobalData* getThreadGlobalData()
408 {
409 return &JSGlobalData::sharedInstance();
410 }
411
getThreadGlobalObject()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
getThreadGlobalExecState()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
JSGlueAPIEntry()434 JSGlueAPIEntry::JSGlueAPIEntry()
435 : m_lock(LockForReal)
436 , m_storedIdentifierTable(wtfThreadData().currentIdentifierTable())
437 {
438 wtfThreadData().setCurrentIdentifierTable(getThreadGlobalData()->identifierTable);
439 }
440
~JSGlueAPIEntry()441 JSGlueAPIEntry::~JSGlueAPIEntry()
442 {
443 wtfThreadData().setCurrentIdentifierTable(m_storedIdentifierTable);
444 }
445
JSGlueAPICallback(ExecState * exec)446 JSGlueAPICallback::JSGlueAPICallback(ExecState* exec)
447 : m_dropLocks(exec)
448 {
449 wtfThreadData().resetCurrentIdentifierTable();
450 }
451
~JSGlueAPICallback()452 JSGlueAPICallback::~JSGlueAPICallback()
453 {
454 wtfThreadData().setCurrentIdentifierTable(getThreadGlobalData()->identifierTable);
455 }
456