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
CFStringToUString(CFStringRef inCFString)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
UStringToCFString(const UString & inUString)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
CFStringToIdentifier(CFStringRef inCFString,ExecState * exec)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
IdentifierToCFString(const Identifier & inIdentifier)94 CFStringRef IdentifierToCFString(const Identifier& inIdentifier)
95 {
96 return UStringToCFString(inIdentifier.ustring());
97 }
98
99
100 //--------------------------------------------------------------------------
101 // KJSValueToJSObject
102 //--------------------------------------------------------------------------
KJSValueToJSObject(JSValue inValue,ExecState * exec)103 JSUserObject* KJSValueToJSObject(JSValue inValue, ExecState *exec)
104 {
105 JSUserObject* result = 0;
106
107 if (inValue.isObject(&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 //--------------------------------------------------------------------------
JSObjectKJSValue(JSUserObject * ptr)129 JSValue JSObjectKJSValue(JSUserObject* ptr)
130 {
131 JSLock lock(LockForReal);
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
KJSValueToCFTypeInternal(JSValue inValue,ExecState * exec,ObjectImpList * inImps)199 CFTypeRef KJSValueToCFTypeInternal(JSValue inValue, ExecState *exec, ObjectImpList* inImps)
200 {
201 if (!inValue)
202 return 0;
203
204 CFTypeRef result = 0;
205
206 JSLock lock(LockForReal);
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.isObject(&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
KJSValueToCFType(JSValue inValue,ExecState * exec)361 CFTypeRef KJSValueToCFType(JSValue inValue, ExecState *exec)
362 {
363 return KJSValueToCFTypeInternal(inValue, exec, 0);
364 }
365
GetCFNull(void)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
unprotectGlobalObject(void * data)395 static void unprotectGlobalObject(void* data)
396 {
397 JSLock lock(LockForReal);
398 gcUnprotect(static_cast<JSGlueGlobalObject*>(data));
399 }
400
initializeGlobalObjectKey()401 static void initializeGlobalObjectKey()
402 {
403 pthread_key_create(&globalObjectKey, unprotectGlobalObject);
404 }
405
getThreadGlobalObject()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
getThreadGlobalExecState()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