| /* |
| * 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 x = jsObjectsById.find(objId); |
| if (x == jsObjectsById.end()) { |
| Debug::log(Debug::Error) << "Unknown object id " << objId << Debug::flush; |
| continue; |
| } |
| |
| JSObjectRef ref = x->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 = new Value[argumentCount]; |
| for (int i = 0; i < argumentCount; i++) { |
| makeValue(args[i], arguments[i]); |
| } |
| |
| bool status = InvokeMessage::send(*channel, thisValue, dispatchId, |
| argumentCount, args); |
| delete[] args; |
| if (!status) { |
| 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; |
| } |