| /* | |
| * Copyright 2008 Google Inc. | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not | |
| * use this file except in compliance with the License. You may obtain a copy of | |
| * the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
| * License for the specific language governing permissions and limitations under | |
| * the License. | |
| */ | |
| #import "WebScriptSessionHandler.h" | |
| #import "InvokeMessage.h" | |
| #import "ObjectFunctions.h" | |
| #import "ServerMethods.h" | |
| #import "TrackingData.h" | |
| #import "scoped_ptr.h" | |
| WebScriptSessionHandler::WebScriptSessionHandler(HostChannel* channel, | |
| JSGlobalContextRef contextRef, | |
| CrashHandlerRef crashHandler) : | |
| SessionData(channel, contextRef, this), jsObjectId(1), crashHandler(crashHandler) { | |
| JSClassDefinition def = kJSClassDefinitionEmpty; | |
| def.className = "JavaObject"; | |
| def.hasProperty = JavaObjectHasProperty; | |
| def.callAsFunction = JavaObjectCallAsFunction; | |
| def.finalize = JavaObjectFinalize; | |
| def.getProperty = JavaObjectGetProperty; | |
| def.setProperty = JavaObjectSetProperty; | |
| javaObjectWrapperClass = JSClassCreate(&def); | |
| JSClassRetain(javaObjectWrapperClass); | |
| // Get the String constructor function to tell Strings from other Objects | |
| JSStringRef stringString = JSStringCreateWithUTF8CString("String"); | |
| JSValueRef stringConstructorValue = JSObjectGetProperty(contextRef, | |
| JSContextGetGlobalObject(contextRef), | |
| stringString, NULL); | |
| stringConstructor = JSValueToObject(contextRef, stringConstructorValue, NULL); | |
| JSValueProtect(contextRef, stringConstructor); | |
| JSStringRelease(stringString); | |
| // Call out to the utility __gwt_makeResult function to create the return array | |
| JSStringRef makeResultString = JSStringCreateWithUTF8CString("__gwt_makeResult"); | |
| JSValueRef makeResultValue = JSObjectGetProperty(contextRef, JSContextGetGlobalObject(contextRef), makeResultString, NULL); | |
| JSStringRelease(makeResultString); | |
| if (!JSValueIsObject(contextRef, makeResultValue)) { | |
| crashHandler->crash(__PRETTY_FUNCTION__, "Could not find __gwt_makeResult"); | |
| } else { | |
| makeResultFunction = JSValueToObject(contextRef, makeResultValue, NULL); | |
| JSValueProtect(contextRef, makeResultFunction); | |
| } | |
| pthread_mutexattr_t mutexAttrs; | |
| pthread_mutexattr_init(&mutexAttrs); | |
| // This behaves basically like the Java synchronized keyword | |
| pthread_mutexattr_settype(&mutexAttrs, PTHREAD_MUTEX_RECURSIVE); | |
| pthread_mutex_init(&javaObjectsLock, &mutexAttrs); | |
| pthread_mutexattr_destroy(&mutexAttrs); | |
| } | |
| WebScriptSessionHandler::~WebScriptSessionHandler() { | |
| std::map<int, JSObjectRef>::iterator i; | |
| pthread_mutex_lock(&javaObjectsLock); | |
| while ((i = javaObjectsById.begin()) != javaObjectsById.end()) { | |
| JavaObjectFinalize(i->second); | |
| } | |
| pthread_mutex_unlock(&javaObjectsLock); | |
| for (i = jsObjectsById.begin(); i != jsObjectsById.end(); i++) { | |
| JSObjectRef ref = i->second; | |
| delete static_cast<TrackingDataRef>(JSObjectGetPrivate(ref)); | |
| JSObjectSetPrivate(ref, NULL); | |
| JSValueUnprotect(contextRef, i->second); | |
| } | |
| JSClassRelease(javaObjectWrapperClass); | |
| JSValueUnprotect(contextRef, stringConstructor); | |
| JSValueUnprotect(contextRef, makeResultFunction); | |
| JSGarbageCollect(contextRef); | |
| pthread_mutex_destroy(&javaObjectsLock); | |
| } | |
| void WebScriptSessionHandler::disconnectDetectedImpl() { | |
| crashHandler->crash(__PRETTY_FUNCTION__, "Server disconnect detected"); | |
| } | |
| void WebScriptSessionHandler::fatalError(HostChannel& channel, | |
| const std::string& message) { | |
| // TODO: better way of reporting error? | |
| Debug::log(Debug::Error) << "Fatal error: " << message << Debug::flush; | |
| } | |
| void WebScriptSessionHandler::sendFreeValues(HostChannel& channel) { | |
| pthread_mutex_lock(&javaObjectsLock); | |
| int idCount = javaObjectsToFree.size(); | |
| if (idCount == 0) { | |
| pthread_mutex_unlock(&javaObjectsLock); | |
| return; | |
| } | |
| int ids[idCount]; | |
| std::set<int>::iterator it = javaObjectsToFree.begin(); | |
| for (int i = 0; i < idCount; it++) { | |
| ids[i++] = *it; | |
| } | |
| if (!ServerMethods::freeJava(channel, this, idCount, ids)) { | |
| Debug::log(Debug::Error) << "Unable to free Java ids" << Debug::flush; | |
| } else { | |
| Debug::log(Debug::Debugging) << "Freed " << idCount << " Java ids" << Debug::flush; | |
| } | |
| javaObjectsToFree.clear(); | |
| pthread_mutex_unlock(&javaObjectsLock); | |
| } | |
| void WebScriptSessionHandler::freeValue(HostChannel& channel, int idCount, const int* ids) { | |
| Debug::log(Debug::Spam) << "freeValue freeing " << idCount << " js objects" << Debug::flush; | |
| if (idCount == 0) { | |
| return; | |
| } | |
| for (int i = 0; i < idCount; i++) { | |
| int objId = ids[i]; | |
| std::map<int, JSObjectRef>::iterator i = jsObjectsById.find(objId); | |
| if (i == jsObjectsById.end()) { | |
| Debug::log(Debug::Error) << "Unknown object id " << objId << Debug::flush; | |
| continue; | |
| } | |
| JSObjectRef ref = i->second; | |
| jsObjectsById.erase(objId); | |
| jsIdsByObject.erase(ref); | |
| JSValueUnprotect(contextRef, ref); | |
| } | |
| Debug::log(Debug::Debugging) << "Freed " << idCount << " JS objects" << Debug::flush; | |
| } | |
| void WebScriptSessionHandler::initiateAutodestructSequence(const char* functionName, const char* message) { | |
| crashHandler->crash(functionName, message); | |
| } | |
| bool WebScriptSessionHandler::invoke(HostChannel& channel, const Value& thisObj, | |
| const std::string& methodName, | |
| int numArgs, const Value* args, Value* returnValue) { | |
| Debug::log(Debug::Spam) << "invoke " << methodName << Debug::flush; | |
| JSValueRef argsJS[numArgs]; | |
| JSValueRef localException = NULL; | |
| JSStringRef methodNameJS = JSStringCreateWithUTF8CString(methodName.c_str()); | |
| JSObjectRef thisObjJs; | |
| if (thisObj.isNull()) { | |
| thisObjJs = JSContextGetGlobalObject(contextRef); | |
| } else { | |
| thisObjJs = (JSObjectRef) makeValueRef(thisObj); | |
| } | |
| JSValueRef functionValueJS = JSObjectGetProperty(contextRef, JSContextGetGlobalObject(contextRef), | |
| methodNameJS, &localException); | |
| JSStringRelease(methodNameJS); | |
| if (!JSValueIsObject(contextRef, functionValueJS)) { | |
| char message[512]; | |
| snprintf(message, sizeof(message), "Could not find method for property name %s on %s", methodName.c_str(), thisObj.toString().c_str()); | |
| makeExceptionValue(*returnValue, message); | |
| return true; | |
| } | |
| JSObjectRef functionJS = JSValueToObject(contextRef, functionValueJS, | |
| &localException); | |
| if (localException) { | |
| makeValue(*returnValue, localException); | |
| return true; | |
| } | |
| // Convert the arguments | |
| for (int i = 0; i < numArgs; i++) { | |
| argsJS[i] = makeValueRef(args[i]); | |
| } | |
| JSValueRef retVal = JSObjectCallAsFunction(contextRef, functionJS, | |
| thisObjJs, numArgs, argsJS, | |
| &localException); | |
| if (localException) { | |
| Debug::log(Debug::Spam) << "Returning exception to server" << Debug::flush; | |
| makeValue(*returnValue, localException); | |
| return true; | |
| } else { | |
| makeValue(*returnValue, retVal); | |
| return false; | |
| } | |
| } | |
| bool WebScriptSessionHandler::invokeSpecial(HostChannel& channel, SpecialMethodId method, int numArgs, | |
| const Value* const args, Value* returnValue) { | |
| Debug::log(Debug::Spam) << "invokeSpecial " << method << Debug::flush; | |
| switch (method) { | |
| case GetProperty: | |
| { | |
| int objId = args[0].getInt(); | |
| std::map<int, JSObjectRef>::iterator i = jsObjectsById.find(objId); | |
| if (i == jsObjectsById.end()) { | |
| char message[50]; | |
| snprintf(message, sizeof(message), "Unknown object id %i", objId); | |
| makeExceptionValue(*returnValue, message); | |
| return true; | |
| } | |
| JSObjectRef jsObj = i->second; | |
| if (args[1].isString()) { | |
| JSStringRef asString = JSValueToStringCopy(contextRef, jsObj, NULL); | |
| int maxLength = JSStringGetMaximumUTF8CStringSize(asString); | |
| scoped_array<char> asChars(new char[maxLength]); | |
| JSStringGetUTF8CString(asString, asChars.get(), maxLength); | |
| JSValueRef localException = NULL; | |
| JSStringRef str = JSStringCreateWithUTF8CString(args[1].getString().c_str()); | |
| JSValueRef value = JSObjectGetProperty(contextRef, jsObj, str, &localException); | |
| JSStringRelease(str); | |
| if (localException) { | |
| makeValue(*returnValue, localException); | |
| return true; | |
| } else { | |
| makeValue(*returnValue, value); | |
| return false; | |
| } | |
| } else if (args[1].isInt()) { | |
| JSValueRef localException = NULL; | |
| JSValueRef value = JSObjectGetPropertyAtIndex(contextRef, jsObj, args[1].getInt(), &localException); | |
| if (localException) { | |
| makeValue(*returnValue, localException); | |
| return true; | |
| } else { | |
| makeValue(*returnValue, value); | |
| return false; | |
| } | |
| } else { | |
| char message[50]; | |
| snprintf(message, sizeof(message), "Unhandled argument type %s for getProperty", args[1].toString().c_str()); | |
| makeExceptionValue(*returnValue, message); | |
| return true; | |
| } | |
| } | |
| default: | |
| Debug::log(Debug::Error) << "Unhandled invokeSpecial " << method << Debug::flush; | |
| makeExceptionValue(*returnValue, "Unhandled invokeSpecial"); | |
| return true; | |
| } | |
| } | |
| JSValueRef WebScriptSessionHandler::javaFunctionCallbackImpl (int dispatchId, | |
| JSObjectRef thisObject, | |
| size_t argumentCount, | |
| const JSValueRef arguments[], | |
| JSValueRef* exception){ | |
| /* | |
| * NB: Because throwing exceptions in JavaScriptCore is trivial, we don't rely | |
| * on any special return values to indicate that an exception is thrown, we'll simply | |
| * throw the exception. | |
| */ | |
| Debug::log(Debug::Debugging) << "Java method " << dispatchId << " invoked" << Debug::flush; | |
| JSValueRef toReturn; | |
| if (crashHandler->hasCrashed()) { | |
| Debug::log(Debug::Debugging) << "Not executing method since we have crashed" << Debug::flush; | |
| toReturn = JSValueMakeUndefined(contextRef); | |
| } else { | |
| /* | |
| * If a JS function is evaluated without an meaningful this object or the global | |
| * object is implicitly used as the this object, we'll assume that the | |
| * Java-derived method is static, and send a null this object to the server | |
| */ | |
| Value thisValue; | |
| if (JSValueIsEqual(contextRef, thisObject, JSContextGetGlobalObject(contextRef), NULL)) { | |
| thisValue = Value(); | |
| thisValue.setNull(); | |
| } else { | |
| makeValue(thisValue, thisObject); | |
| } | |
| // Argument conversion is straightforward | |
| Value args[argumentCount]; | |
| for (int i = 0; i < argumentCount; i++) { | |
| makeValue(args[i], arguments[i]); | |
| } | |
| if (!InvokeMessage::send(*channel, thisValue, dispatchId, | |
| argumentCount, args)) { | |
| initiateAutodestructSequence(__PRETTY_FUNCTION__, "Unable to send invocation message"); | |
| *exception = makeException("Unable to send invocation message"); | |
| return JSValueMakeUndefined(contextRef); | |
| } | |
| scoped_ptr<ReturnMessage> ret(channel->reactToMessagesWhileWaitingForReturn(sessionHandler)); | |
| if (!ret.get()) { | |
| initiateAutodestructSequence(__PRETTY_FUNCTION__, "Unable to receive return message"); | |
| *exception = makeException("Unable to receive return message"); | |
| return JSValueMakeUndefined(contextRef); | |
| } | |
| Value v = ret->getReturnValue(); | |
| if (ret->isException()) { | |
| *exception = makeValueRef(v); | |
| toReturn = JSValueMakeUndefined(contextRef); | |
| } else { | |
| toReturn = makeValueRef(v); | |
| } | |
| } | |
| JSValueRef makeResultArguments[] = {JSValueMakeBoolean(contextRef, false), toReturn}; | |
| return JSObjectCallAsFunction(contextRef, makeResultFunction, NULL, 2, makeResultArguments, exception); | |
| } | |
| void WebScriptSessionHandler::javaObjectFinalizeImpl(int objId) { | |
| if (pthread_mutex_lock(&javaObjectsLock)) { | |
| Debug::log(Debug::Error) << "Unable to acquire javaObjectsLock in thread " << pthread_self() << " " << __PRETTY_FUNCTION__ << Debug::flush; | |
| initiateAutodestructSequence(__PRETTY_FUNCTION__, "Unable to acquire javaObjectsLock"); | |
| return; | |
| } | |
| javaObjectsById.erase(objId); | |
| javaObjectsToFree.insert(objId); | |
| if (pthread_mutex_unlock(&javaObjectsLock)) { | |
| Debug::log(Debug::Error) << "Unable to release javaObjectsLock in thread " << pthread_self() << " " << __PRETTY_FUNCTION__ << Debug::flush; | |
| initiateAutodestructSequence(__PRETTY_FUNCTION__, "Unable to release javaObjectsLock"); | |
| } | |
| } | |
| JSValueRef WebScriptSessionHandler::javaObjectGetPropertyImpl (TrackingDataRef tracker, JSObjectRef object, | |
| JSStringRef propertyName, JSValueRef* exception) { | |
| *exception = NULL; | |
| if (crashHandler->hasCrashed()) { | |
| Debug::log(Debug::Debugging) << "Not executing since we have crashed" << Debug::flush; | |
| return JSValueMakeUndefined(contextRef); | |
| } | |
| // Convert the name | |
| int maxLength = JSStringGetMaximumUTF8CStringSize(propertyName); | |
| scoped_array<char> propertyNameChars(new char[maxLength]); | |
| JSStringGetUTF8CString(propertyName, propertyNameChars.get(), maxLength); | |
| JSValueRef toReturn; | |
| if (!strcmp(propertyNameChars.get(), "toString")) { | |
| // We'll call out to the JSNI tear-off support function | |
| JSStringRef tearOffName =JSStringCreateWithUTF8CString("__gwt_makeTearOff"); | |
| JSValueRef makeTearOffValue = JSObjectGetProperty(contextRef, JSContextGetGlobalObject(contextRef), tearOffName, exception); | |
| JSStringRelease(tearOffName); | |
| if (*exception) { | |
| return JSValueMakeUndefined(contextRef); | |
| } | |
| JSObjectRef makeTearOff = JSValueToObject(contextRef, makeTearOffValue, exception); | |
| if (*exception) { | |
| return JSValueMakeUndefined(contextRef); | |
| } | |
| JSValueRef arguments[3]; | |
| arguments[0] = object; | |
| arguments[1] = JSValueMakeNumber(contextRef, 0); | |
| arguments[2] = JSValueMakeNumber(contextRef, 0); | |
| toReturn = JSObjectCallAsFunction(contextRef, makeTearOff, JSContextGetGlobalObject(contextRef), 3, arguments, exception); | |
| } else { | |
| char* endptr; | |
| int dispatchId = strtol(propertyNameChars.get(), &endptr, 10); | |
| if (*endptr != '\0') { | |
| Debug::log(Debug::Error) << "Unable to parse dispatch id " << propertyNameChars.get() << Debug::flush; | |
| *exception = makeException("Unable to parse dispatch id"); | |
| } else if (dispatchId < 0) { | |
| Debug::log(Debug::Error) << "Dispatch ids may not be negative" << Debug::flush; | |
| *exception = makeException("Dispatch ids may not be negative"); | |
| } else { | |
| Value v = ServerMethods::getProperty(*channel, this, tracker->getObjectId(), dispatchId); | |
| toReturn = makeValueRef(v); | |
| } | |
| } | |
| return toReturn; | |
| } | |
| bool WebScriptSessionHandler::javaObjectHasPropertyImpl (TrackingDataRef tracker, JSObjectRef object, JSStringRef propertyName) { | |
| // The property name must either be "toString" or a number | |
| int maxLength = JSStringGetMaximumUTF8CStringSize(propertyName); | |
| scoped_array<char> propertyNameChars(new char[maxLength]); | |
| JSStringGetUTF8CString(propertyName, propertyNameChars.get(), maxLength); | |
| if (!strcmp(propertyNameChars.get(), "toString")) { | |
| return true; | |
| } | |
| char* endptr; | |
| int dispatchId = strtol(propertyNameChars.get(), &endptr, 10); | |
| if (*endptr != '\0') { | |
| return false; | |
| } else if (dispatchId < 0) { | |
| return false; | |
| } else { | |
| return true; | |
| } | |
| } | |
| bool WebScriptSessionHandler::javaObjectSetPropertyImpl (TrackingDataRef tracker, JSObjectRef object, | |
| JSStringRef propertyName, JSValueRef jsValue, | |
| JSValueRef* exception) { | |
| if (crashHandler->hasCrashed()) { | |
| Debug::log(Debug::Debugging) << "Not executing since we have crashed" << Debug::flush; | |
| return true; | |
| } | |
| int maxLength = JSStringGetMaximumUTF8CStringSize(propertyName); | |
| scoped_array<char> propertyNameChars(new char[maxLength]); | |
| JSStringGetUTF8CString(propertyName, propertyNameChars.get(), maxLength); | |
| Value value; | |
| char* endptr; | |
| int dispatchId = strtol(propertyNameChars.get(), &endptr, 10); | |
| if (*endptr != '\0') { | |
| // TODO Figure out the right policy here; when we throw a Java object, JSCore wants to | |
| // add expandos to record stack information. It would be possible to map the limited | |
| // number of properties into a synthetic causal exception in the exception being thrown. | |
| } else if (dispatchId < 0) { | |
| // Do nothing. | |
| Debug::log(Debug::Error) << "Dispatch ids may not be negative" << Debug::flush; | |
| *exception = makeException("Dispatch ids may not be negative"); | |
| } else { | |
| makeValue(value, jsValue); | |
| if (!ServerMethods::setProperty(*channel, this, tracker->getObjectId(), dispatchId, value)) { | |
| char message[50]; | |
| snprintf(message, sizeof(message), "Unable to set value object %i dispatchId %i", tracker->getObjectId(), dispatchId); | |
| *exception = makeException(message); | |
| } | |
| } | |
| // true means to not try to follow the prototype chain; not an indication of success | |
| return true; | |
| } | |
| void WebScriptSessionHandler::loadJsni(HostChannel& channel, const std::string& js) { | |
| Debug::log(Debug::Spam) << "loadJsni " << js << Debug::flush; | |
| JSValueRef localException = NULL; | |
| JSStringRef script = JSStringCreateWithUTF8CString(js.c_str()); | |
| JSEvaluateScript(contextRef, script, NULL, NULL, NULL, &localException); | |
| JSStringRelease(script); | |
| if (localException) { | |
| // TODO Exception handling | |
| Debug::log(Debug::Error) << "Exception thrown during loadJsni" << Debug::flush; | |
| } else { | |
| Debug::log(Debug::Spam) << "Success" << Debug::flush; | |
| } | |
| } | |
| JSValueRef WebScriptSessionHandler::makeException(const char* message) { | |
| JSValueRef localException = NULL; | |
| JSObjectRef global = JSContextGetGlobalObject(contextRef); | |
| JSStringRef errorName = JSStringCreateWithUTF8CString("Error"); | |
| JSValueRef errorValue = JSObjectGetProperty(contextRef, global, errorName, &localException); | |
| JSStringRelease(errorName); | |
| if (!JSValueIsObject(contextRef, errorValue)) { | |
| initiateAutodestructSequence(__PRETTY_FUNCTION__, "Could not get reference to Error"); | |
| return JSValueMakeUndefined(contextRef); | |
| } | |
| JSObjectRef errorObject = (JSObjectRef) errorValue; | |
| if (!JSObjectIsFunction(contextRef, errorObject)) { | |
| initiateAutodestructSequence(__PRETTY_FUNCTION__, "Error was not a function"); | |
| return JSValueMakeUndefined(contextRef); | |
| } | |
| JSValueRef args[1]; | |
| JSStringRef messageJs = JSStringCreateWithUTF8CString(message); | |
| args[0] = JSValueMakeString(contextRef, messageJs); | |
| JSStringRelease(messageJs); | |
| return JSObjectCallAsConstructor(contextRef, errorObject, 1, args, &localException); | |
| } | |
| void WebScriptSessionHandler::makeExceptionValue(Value& value, const char* message) { | |
| makeValue(value, makeException(message)); | |
| } | |
| JSObjectRef WebScriptSessionHandler::makeJavaWrapper(int objId) { | |
| Debug::log(Debug::Spam) << "Creating wrapper for Java object " << objId << Debug::flush; | |
| TrackingDataRef data = new TrackingData(this, objId); | |
| return JSObjectMake(contextRef, javaObjectWrapperClass, | |
| const_cast<TrackingData*>(data)); | |
| } | |
| JSValueRef WebScriptSessionHandler::makeValueRef(const Value& v) { | |
| std::map<int, JSObjectRef>::iterator i; | |
| switch (v.getType()) { | |
| case Value::NULL_TYPE: | |
| return JSValueMakeNull(contextRef); | |
| case Value::BOOLEAN: | |
| return JSValueMakeBoolean(contextRef, v.getBoolean()); | |
| case Value::BYTE: | |
| return JSValueMakeNumber(contextRef, v.getByte()); | |
| case Value::CHAR: | |
| return JSValueMakeNumber(contextRef, v.getChar()); | |
| case Value::SHORT: | |
| return JSValueMakeNumber(contextRef, v.getShort()); | |
| case Value::INT: | |
| return JSValueMakeNumber(contextRef, v.getInt()); | |
| case Value::LONG: | |
| return JSValueMakeNumber(contextRef, v.getLong()); | |
| case Value::FLOAT: | |
| return JSValueMakeNumber(contextRef, v.getFloat()); | |
| case Value::DOUBLE: | |
| return JSValueMakeNumber(contextRef, v.getDouble()); | |
| case Value::STRING: | |
| { | |
| std::string stringValue = v.getString(); | |
| // We need to handle the conversion ourselves to be able to get both | |
| // UTF8 encoding as well as explicit control over the length of the string | |
| // due to the possibility of null characters being part of the data | |
| CFStringRef cfString = CFStringCreateWithBytesNoCopy(NULL, (UInt8*)stringValue.data(), | |
| stringValue.length(), kCFStringEncodingUTF8, | |
| false, kCFAllocatorNull); | |
| JSStringRef stringRef = JSStringCreateWithCFString(cfString); | |
| JSValueRef toReturn = JSValueMakeString(contextRef, stringRef); | |
| JSStringRelease(stringRef); | |
| CFRelease(cfString); | |
| return toReturn; | |
| } | |
| case Value::JAVA_OBJECT: | |
| unsigned javaId = v.getJavaObjectId(); | |
| JSObjectRef ref; | |
| pthread_mutex_lock(&javaObjectsLock); | |
| i = javaObjectsById.find(javaId); | |
| /* | |
| * It's possible that we've already finalized the JsObjectRef that | |
| * represented the object with the given id. If so, we must remove it | |
| * from the list of finalized object ids to avoid prematurely freeing | |
| * the object on the server. | |
| */ | |
| javaObjectsToFree.erase(javaId); | |
| if (i == javaObjectsById.end()) { | |
| /* | |
| * We don't call JSValueProtect so that the JavaObject peer can be | |
| * garbage-collected during the lifetime of the program. Object | |
| * identity is maintained as long as the object hasn't been finalized. | |
| * If it had been finalized, then there wouldn't be an object to use | |
| * as a basis for identity comparison. | |
| * | |
| * NB: The act of creating the wrapper may trigger a GC. | |
| */ | |
| ref = makeJavaWrapper(javaId); | |
| javaObjectsById[javaId] = ref; | |
| } else { | |
| ref = i->second; | |
| } | |
| pthread_mutex_unlock(&javaObjectsLock); | |
| return ref; | |
| case Value::JS_OBJECT: | |
| int jsId = v.getJsObjectId(); | |
| i = jsObjectsById.find(jsId); | |
| if (i == jsObjectsById.end()) { | |
| char errMsg[50]; | |
| snprintf(errMsg, sizeof(errMsg), "Missing JsObject with id %i", jsId); | |
| return makeException(errMsg); | |
| } else { | |
| return i->second; | |
| } | |
| case Value::UNDEFINED: | |
| return JSValueMakeUndefined(contextRef); | |
| default: | |
| char message[50]; | |
| snprintf(message, sizeof(message), "Could not convert %s", v.toString().c_str()); | |
| initiateAutodestructSequence(__PRETTY_FUNCTION__, message); | |
| return makeException(message); | |
| } | |
| } | |
| bool WebScriptSessionHandler::makeValue(Value& ret, JSValueRef v) { | |
| JSValueRef localException = NULL; | |
| if (JSValueIsNull(contextRef, v)) { | |
| ret.setNull(); | |
| } else if (JSValueIsUndefined(contextRef, v)) { | |
| ret.setUndefined(); | |
| } else if (JSValueIsBoolean(contextRef, v)) { | |
| ret.setBoolean(JSValueToBoolean(contextRef, v)); | |
| } else if (JSValueIsNumber(contextRef, v)) { | |
| double d = JSValueToNumber(contextRef, v, &localException); | |
| int i = round(d); | |
| if (i == d) { | |
| ret.setInt(i); | |
| } else { | |
| ret.setDouble(d); | |
| } | |
| } else if (JSValueIsString(contextRef, v) || | |
| JSValueIsInstanceOfConstructor(contextRef, v, stringConstructor, &localException)) { | |
| return makeValueFromString(ret, v); | |
| } else if (JSValueIsObjectOfClass(contextRef, v, javaObjectWrapperClass)) { | |
| // It's one of our Java object proxies | |
| JSObjectRef objectRef = JSValueToObject(contextRef, v, &localException); | |
| if (!localException) { | |
| TrackingDataRef tracker = (TrackingDataRef) JSObjectGetPrivate(objectRef); | |
| int objId = tracker->getObjectId(); | |
| ret.setJavaObject(objId); | |
| Debug::log(Debug::Spam) << "Made a Java object Value " << objId << Debug::flush; | |
| } | |
| } else if (JSValueIsObject(contextRef, v)) { | |
| JSObjectRef objectRef = JSValueToObject(contextRef, v, &localException); | |
| if (!localException) { | |
| /* | |
| * Then this is just a plain-old JavaScript Object. Because JSCore | |
| * doesn't retain private data for objects derived from the built-in | |
| * Object type, we'll simply revert to using a pair of maps to provide | |
| * a 1:1 mapping of JSObjectRefs and ints. | |
| */ | |
| std::map<JSObjectRef, int>::iterator i = jsIdsByObject.find(objectRef); | |
| if (i != jsIdsByObject.end()) { | |
| // We've seen the object before | |
| ret.setJsObjectId(i->second); | |
| } else { | |
| // Allocate a new id | |
| int objId = ++jsObjectId; | |
| JSValueProtect(contextRef, objectRef); | |
| jsObjectsById[objId] = objectRef; | |
| jsIdsByObject[objectRef] = objId; | |
| ret.setJsObjectId(objId); | |
| Debug::log(Debug::Spam) << "Made JS Value " << objId << Debug::flush; | |
| } | |
| } | |
| } else { | |
| Debug::log(Debug::Error) << "Unhandled JSValueRef -> Value conversion in plugin" << Debug::flush; | |
| ret.setString("Unhandled JSValueRef -> Value conversion in plugin"); | |
| } | |
| if (localException) { | |
| makeValue(ret, localException); | |
| return true; | |
| } else { | |
| return false; | |
| } | |
| } | |
| bool WebScriptSessionHandler::makeValueFromString(Value& ret, JSValueRef value) { | |
| JSValueRef localException = NULL; | |
| JSStringRef jsString = JSValueToStringCopy(contextRef, value, &localException); | |
| if (localException) { | |
| makeValue(ret, localException); | |
| return true; | |
| } | |
| CFStringRef cfString = JSStringCopyCFString(NULL, jsString); | |
| JSStringRelease(jsString); | |
| CFIndex cfLength = CFStringGetLength(cfString); | |
| CFIndex maxLength = CFStringGetMaximumSizeForEncoding(cfLength, kCFStringEncodingUTF8); | |
| scoped_array<char> utf8(new char[maxLength]); | |
| CFIndex numBytes; | |
| CFStringGetBytes(cfString, CFRangeMake(0, cfLength), kCFStringEncodingUTF8, | |
| 0, false, (UInt8*) utf8.get(), maxLength, &numBytes); | |
| CFRelease(cfString); | |
| ret.setString(utf8.get(), numBytes); | |
| Debug::log(Debug::Spam) << "Made a string Value " << ret.getString() << Debug::flush; | |
| return false; | |
| } |