/*
* Copyright (C) 2005, 2008 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "JSUtils.h"
#include "JSBase.h"
#include "JSObject.h"
#include "JSRun.h"
#include "JSValueWrapper.h"
#include "UserObjectImp.h"
#include <JavaScriptCore/JSString.h>
#include <JavaScriptCore/PropertyNameArray.h>
#include <JavaScriptCore/WTFThreadData.h>
struct ObjectImpList {
JSObject* imp;
ObjectImpList* next;
CFTypeRef data;
};
static CFTypeRef KJSValueToCFTypeInternal(JSValue inValue, ExecState *exec, ObjectImpList* inImps);
static JSGlueGlobalObject* getThreadGlobalObject();
//--------------------------------------------------------------------------
// CFStringToUString
//--------------------------------------------------------------------------
UString CFStringToUString(CFStringRef inCFString)
{
UString result;
if (inCFString) {
CFIndex len = CFStringGetLength(inCFString);
UniChar* buffer = (UniChar*)malloc(sizeof(UniChar) * len);
if (buffer)
{
CFStringGetCharacters(inCFString, CFRangeMake(0, len), buffer);
result = UString((const UChar *)buffer, len);
free(buffer);
}
}
return result;
}
//--------------------------------------------------------------------------
// UStringToCFString
//--------------------------------------------------------------------------
// Caller is responsible for releasing the returned CFStringRef
CFStringRef UStringToCFString(const UString& inUString)
{
return CFStringCreateWithCharacters(0, (const UniChar*)inUString.characters(), inUString.length());
}
//--------------------------------------------------------------------------
// CFStringToIdentifier
//--------------------------------------------------------------------------
Identifier CFStringToIdentifier(CFStringRef inCFString, ExecState* exec)
{
return Identifier(exec, CFStringToUString(inCFString));
}
//--------------------------------------------------------------------------
// IdentifierToCFString
//--------------------------------------------------------------------------
// Caller is responsible for releasing the returned CFStringRef
CFStringRef IdentifierToCFString(const Identifier& inIdentifier)
{
return UStringToCFString(inIdentifier.ustring());
}
//--------------------------------------------------------------------------
// KJSValueToJSObject
//--------------------------------------------------------------------------
JSUserObject* KJSValueToJSObject(JSValue inValue, ExecState *exec)
{
JSUserObject* result = 0;
if (inValue.inherits(&UserObjectImp::s_info)) {
UserObjectImp* userObjectImp = static_cast<UserObjectImp *>(asObject(inValue));
result = userObjectImp->GetJSUserObject();
if (result)
result->Retain();
} else {
JSValueWrapper* wrapperValue = new JSValueWrapper(inValue);
if (wrapperValue) {
JSObjectCallBacks callBacks;
JSValueWrapper::GetJSObectCallBacks(callBacks);
result = (JSUserObject*)JSObjectCreate(wrapperValue, &callBacks);
if (!result) {
delete wrapperValue;
}
}
}
return result;
}
//--------------------------------------------------------------------------
// JSObjectKJSValue
//--------------------------------------------------------------------------
JSValue JSObjectKJSValue(JSUserObject* ptr)
{
JSGlueAPIEntry entry;
JSValue result = jsUndefined();
if (ptr)
{
bool handled = false;
switch (ptr->DataType())
{
case kJSUserObjectDataTypeJSValueWrapper:
{
JSValueWrapper* wrapper = (JSValueWrapper*)ptr->GetData();
if (wrapper)
{
result = wrapper->GetValue();
handled = true;
}
break;
}
case kJSUserObjectDataTypeCFType:
{
CFTypeRef cfType = (CFTypeRef*)ptr->GetData();
if (cfType)
{
CFTypeID typeID = CFGetTypeID(cfType);
if (typeID == CFStringGetTypeID())
{
result = jsString(getThreadGlobalExecState(), CFStringToUString((CFStringRef)cfType));
handled = true;
}
else if (typeID == CFNumberGetTypeID())
{
double num;
CFNumberGetValue((CFNumberRef)cfType, kCFNumberDoubleType, &num);
result = jsNumber(num);
handled = true;
}
else if (typeID == CFBooleanGetTypeID())
{
result = jsBoolean(CFBooleanGetValue((CFBooleanRef)cfType));
handled = true;
}
else if (typeID == CFNullGetTypeID())
{
result = jsNull();
handled = true;
}
}
break;
}
}
if (!handled)
{
ExecState* exec = getThreadGlobalExecState();
result = new (exec) UserObjectImp(exec->globalData(), getThreadGlobalObject()->userObjectStructure(), ptr);
}
}
return result;
}
//--------------------------------------------------------------------------
// KJSValueToCFTypeInternal
//--------------------------------------------------------------------------
// Caller is responsible for releasing the returned CFTypeRef
CFTypeRef KJSValueToCFTypeInternal(JSValue inValue, ExecState *exec, ObjectImpList* inImps)
{
if (!inValue)
return 0;
CFTypeRef result = 0;
JSGlueAPIEntry entry;
if (inValue.isBoolean())
{
result = inValue.toBoolean(exec) ? kCFBooleanTrue : kCFBooleanFalse;
RetainCFType(result);
return result;
}
if (inValue.isString())
{
UString uString = inValue.toString(exec);
result = UStringToCFString(uString);
return result;
}
if (inValue.isNumber())
{
double number1 = inValue.toNumber(exec);
double number2 = (double)inValue.toInteger(exec);
if (number1 == number2)
{
int intValue = (int)number2;
result = CFNumberCreate(0, kCFNumberIntType, &intValue);
}
else
{
result = CFNumberCreate(0, kCFNumberDoubleType, &number1);
}
return result;
}
if (inValue.isObject())
{
if (inValue.inherits(&UserObjectImp::s_info)) {
UserObjectImp* userObjectImp = static_cast<UserObjectImp *>(asObject(inValue));
JSUserObject* ptr = userObjectImp->GetJSUserObject();
if (ptr)
{
result = ptr->CopyCFValue();
}
}
else
{
JSObject *object = inValue.toObject(exec);
UInt8 isArray = false;
// if two objects reference each
JSObject* imp = object;
ObjectImpList* temp = inImps;
while (temp) {
if (imp == temp->imp) {
return CFRetain(GetCFNull());
}
temp = temp->next;
}
ObjectImpList imps;
imps.next = inImps;
imps.imp = imp;
//[...] HACK since we do not have access to the class info we use class name instead
#if 0
if (object->inherits(&ArrayInstanceImp::s_info))
#else
if (object->className() == "Array")
#endif
{
isArray = true;
JSGlueGlobalObject* globalObject = static_cast<JSGlueGlobalObject*>(exec->dynamicGlobalObject());
if (globalObject && (globalObject->Flags() & kJSFlagConvertAssociativeArray)) {
PropertyNameArray propNames(exec);
object->getPropertyNames(exec, propNames);
PropertyNameArray::const_iterator iter = propNames.begin();
PropertyNameArray::const_iterator end = propNames.end();
while(iter != end && isArray)
{
Identifier propName = *iter;
UString ustr = propName.ustring();
const UniChar* uniChars = (const UniChar*)ustr.characters();
int size = ustr.length();
while (size--) {
if (uniChars[size] < '0' || uniChars[size] > '9') {
isArray = false;
break;
}
}
iter++;
}
}
}
if (isArray)
{
// This is an KJS array
unsigned int length = object->get(exec, Identifier(exec, "length")).toUInt32(exec);
result = CFArrayCreateMutable(0, 0, &kCFTypeArrayCallBacks);
if (result)
{
for (unsigned i = 0; i < length; i++)
{
CFTypeRef cfValue = KJSValueToCFTypeInternal(object->get(exec, i), exec, &imps);
CFArrayAppendValue((CFMutableArrayRef)result, cfValue);
ReleaseCFType(cfValue);
}
}
}
else
{
// Not an array, just treat it like a dictionary which contains (property name, property value) pairs
PropertyNameArray propNames(exec);
object->getPropertyNames(exec, propNames);
{
result = CFDictionaryCreateMutable(0,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (result)
{
PropertyNameArray::const_iterator iter = propNames.begin();
PropertyNameArray::const_iterator end = propNames.end();
while(iter != end)
{
Identifier propName = *iter;
if (object->hasProperty(exec, propName))
{
CFStringRef cfKey = IdentifierToCFString(propName);
CFTypeRef cfValue = KJSValueToCFTypeInternal(object->get(exec, propName), exec, &imps);
if (cfKey && cfValue)
{
CFDictionaryAddValue((CFMutableDictionaryRef)result, cfKey, cfValue);
}
ReleaseCFType(cfKey);
ReleaseCFType(cfValue);
}
iter++;
}
}
}
}
}
return result;
}
if (inValue.isUndefinedOrNull())
{
result = RetainCFType(GetCFNull());
return result;
}
ASSERT_NOT_REACHED();
return 0;
}
CFTypeRef KJSValueToCFType(JSValue inValue, ExecState *exec)
{
return KJSValueToCFTypeInternal(inValue, exec, 0);
}
CFTypeRef GetCFNull(void)
{
static CFArrayRef sCFNull = CFArrayCreate(0, 0, 0, 0);
CFTypeRef result = JSGetCFNull();
if (!result)
{
result = sCFNull;
}
return result;
}
/*
* This is a slight hack. The JSGlue API has no concept of execution state.
* However, execution state is an inherent part of JS, and JSCore requires it.
* So, we keep a single execution state for the whole thread and supply it
* where necessary.
* The execution state holds two things: (1) exceptions; (2) the global object.
* JSGlue has no API for accessing exceptions, so we just discard them. As for
* the global object, JSGlue includes no calls that depend on it. Its property
* getters and setters are per-object; they don't walk up the enclosing scope.
* Functions called by JSObjectCallFunction may reference values in the enclosing
* scope, but they do so through an internally stored scope chain, so we don't
* need to supply the global scope.
*/
static pthread_key_t globalObjectKey;
static pthread_once_t globalObjectKeyOnce = PTHREAD_ONCE_INIT;
static void unprotectGlobalObject(void* data)
{
JSGlueAPIEntry entry;
gcUnprotect(static_cast<JSGlueGlobalObject*>(data));
}
static void initializeGlobalObjectKey()
{
pthread_key_create(&globalObjectKey, unprotectGlobalObject);
}
JSGlobalData* getThreadGlobalData()
{
return &JSGlobalData::sharedInstance();
}
static JSGlueGlobalObject* getThreadGlobalObject()
{
pthread_once(&globalObjectKeyOnce, initializeGlobalObjectKey);
JSGlueGlobalObject* globalObject = static_cast<JSGlueGlobalObject*>(pthread_getspecific(globalObjectKey));
if (!globalObject) {
globalObject = new (getThreadGlobalData()) JSGlueGlobalObject(*getThreadGlobalData(), JSGlueGlobalObject::createStructure(*getThreadGlobalData(), jsNull()));
gcProtect(globalObject);
pthread_setspecific(globalObjectKey, globalObject);
}
return globalObject;
}
ExecState* getThreadGlobalExecState()
{
ExecState* exec = getThreadGlobalObject()->globalExec();
// Discard exceptions -- otherwise an exception would forestall JS
// evaluation throughout the thread
exec->clearException();
return exec;
}
JSGlueAPIEntry::JSGlueAPIEntry()
: m_lock(LockForReal)
, m_storedIdentifierTable(wtfThreadData().currentIdentifierTable())
{
wtfThreadData().setCurrentIdentifierTable(getThreadGlobalData()->identifierTable);
}
JSGlueAPIEntry::~JSGlueAPIEntry()
{
wtfThreadData().setCurrentIdentifierTable(m_storedIdentifierTable);
}
JSGlueAPICallback::JSGlueAPICallback(ExecState* exec)
: m_dropLocks(exec)
{
wtfThreadData().resetCurrentIdentifierTable();
}
JSGlueAPICallback::~JSGlueAPICallback()
{
wtfThreadData().setCurrentIdentifierTable(getThreadGlobalData()->identifierTable);
}