|  | /* | 
|  | * 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. | 
|  | */ | 
|  |  | 
|  | #include "FFSessionHandler.h" | 
|  | #include "HostChannel.h" | 
|  | #include "JavaObject.h" | 
|  | #include "JSRunner.h" | 
|  | #include "Debug.h" | 
|  | #include "XpcomDebug.h" | 
|  | #include "scoped_ptr/scoped_ptr.h" | 
|  | #include "RootedObject.h" | 
|  | #include "InvokeMessage.h" | 
|  | #include "ServerMethods.h" | 
|  |  | 
|  | #include "jsapi.h" | 
|  | #include "nsCOMPtr.h" | 
|  | #include "nsIJSContextStack.h" | 
|  | #include "nsIPrincipal.h" | 
|  | #include "nsServiceManagerUtils.h" | 
|  |  | 
|  | static JSContext* getJSContext() { | 
|  | // Get JSContext from stack. | 
|  | nsCOMPtr<nsIJSContextStack> stack = | 
|  | do_GetService("@mozilla.org/js/xpc/ContextStack;1"); | 
|  | if (!stack) { | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | JSContext *cx; | 
|  | if (NS_FAILED(stack->Peek(&cx))) { | 
|  | return NULL; | 
|  | } | 
|  | return cx; | 
|  | } | 
|  |  | 
|  | FFSessionHandler::FFSessionHandler(HostChannel* channel) | 
|  | : SessionData(channel, this, getJSContext()), jsObjectId(0), | 
|  | jsObjectsById(NULL), stringObjectClass(NULL) { | 
|  | // TODO(jat): is there a way to avoid calling this twice, without keeping | 
|  | // JSContext in an instance field? | 
|  | JSContext* ctx = getJSContext(); | 
|  | if (!JS_AddNamedRoot(ctx, &jsObjectsById, "jsObjectsById")) { | 
|  | Debug::log(Debug::Error) << "Error rooting jsObjectsById" << Debug::flush; | 
|  | } | 
|  | jsObjectsById = JS_NewArrayObject(ctx, 0, NULL); | 
|  | if (!jsObjectsById) { | 
|  | Debug::log(Debug::Error) << "Error rooting jsObjectsById" << Debug::flush; | 
|  | } | 
|  | if (!JS_AddNamedRoot(ctx, &toStringTearOff, "toStringTearOff")) { | 
|  | Debug::log(Debug::Error) << "Error rooting toStringTearOff" << Debug::flush; | 
|  | } | 
|  | getStringObjectClass(ctx); | 
|  | getToStringTearOff(ctx); | 
|  | } | 
|  |  | 
|  | void FFSessionHandler::getStringObjectClass(JSContext* ctx) { | 
|  | jsval str = JS_GetEmptyStringValue(ctx); | 
|  | JSObject* obj = 0; | 
|  | if (!JS_ValueToObject(ctx, str, &obj)) { | 
|  | return; | 
|  | } | 
|  | if (!obj) { | 
|  | return; | 
|  | } | 
|  | stringObjectClass = JS_GET_CLASS(ctx, obj); | 
|  | } | 
|  |  | 
|  | void FFSessionHandler::getToStringTearOff(JSContext* ctx) { | 
|  | jsval funcVal; | 
|  | if (!JS_GetProperty(ctx, global, "__gwt_makeTearOff", &funcVal)) { | 
|  | Debug::log(Debug::Error) << "Could not get function \"__gwt_makeTearOff\"" | 
|  | << Debug::flush; | 
|  | return; | 
|  | } | 
|  | jsval jsargs[3] = { | 
|  | JSVAL_NULL,                                     // no proxy | 
|  | INT_TO_JSVAL(InvokeMessage::TOSTRING_DISP_ID),  // dispId | 
|  | JSVAL_ZERO                                      // arg count is zero | 
|  | }; | 
|  | if (!JS_CallFunctionValue(ctx, global, funcVal, 3, jsargs, &toStringTearOff)) { | 
|  | jsval exc; | 
|  | if (JS_GetPendingException(ctx, &exc)) { | 
|  | Debug::log(Debug::Error) | 
|  | << "__gwt_makeTearOff(null,0,0) threw exception " | 
|  | << dumpJsVal(ctx, exc) << Debug::flush; | 
|  | } else { | 
|  | Debug::log(Debug::Error) << "Error creating toString tear-off" | 
|  | << Debug::flush; | 
|  | } | 
|  | // TODO(jat): show some crash page and die | 
|  | } | 
|  | } | 
|  |  | 
|  | FFSessionHandler::~FFSessionHandler(void) { | 
|  | Debug::log(Debug::Debugging) << "FFSessionHandler::~FFSessionHandler" << Debug::flush; | 
|  | disconnect(); | 
|  | if (runtime) { | 
|  | JS_RemoveRootRT(runtime, &jsObjectsById); | 
|  | jsObjectsById = NULL; | 
|  | JS_RemoveRootRT(runtime, &toStringTearOff); | 
|  | runtime = NULL; | 
|  | } | 
|  | } | 
|  |  | 
|  | void FFSessionHandler::freeValue(HostChannel& channel, int idCount, const int* ids) { | 
|  | Debug::DebugStream& dbg = Debug::log(Debug::Spam) | 
|  | << "FFSessionHandler::freeValue [ "; | 
|  | JSContext* ctx = getJSContext(); | 
|  |  | 
|  | for (int i = 0; i < idCount; ++i) { | 
|  | int objId = ids[i]; | 
|  | dbg << objId << " "; | 
|  | jsval toRemove; | 
|  | if (JS_GetElement(ctx, jsObjectsById, objId, &toRemove) && JSVAL_IS_OBJECT(toRemove)) { | 
|  | jsIdsByObject.erase(JSVAL_TO_OBJECT(toRemove)); | 
|  | JS_DeleteElement(ctx, jsObjectsById, objId); | 
|  | } else { | 
|  | Debug::log(Debug::Error) << "Error deleting js objId=" << objId << Debug::flush; | 
|  | } | 
|  | } | 
|  |  | 
|  | dbg << "]" << Debug::flush; | 
|  | } | 
|  |  | 
|  | void FFSessionHandler::loadJsni(HostChannel& channel, const std::string& js) { | 
|  | Debug::log(Debug::Spam) << "FFSessionHandler::loadJsni " << js << "(EOM)" << Debug::flush; | 
|  | JSContext* ctx = getJSContext(); | 
|  | if (!JSRunner::eval(ctx, global, js)) { | 
|  | Debug::log(Debug::Error) << "Error executing script" << Debug::flush; | 
|  | } | 
|  | } | 
|  |  | 
|  | void FFSessionHandler::sendFreeValues(HostChannel& channel) { | 
|  | unsigned n = javaObjectsToFree.size(); | 
|  | if (n) { | 
|  | scoped_array<int> ids(new int[n]); | 
|  | int i = 0; | 
|  | for (std::set<int>::iterator it = javaObjectsToFree.begin(); | 
|  | it != javaObjectsToFree.end(); ++it) { | 
|  | ids[i++] = *it; | 
|  | } | 
|  | if (ServerMethods::freeJava(channel, this, n, ids.get())) { | 
|  | javaObjectsToFree.clear(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | bool FFSessionHandler::invoke(HostChannel& channel, const Value& thisObj, const std::string& methodName, | 
|  | int numArgs, const Value* const args, Value* returnValue) { | 
|  | Debug::log(Debug::Spam) << "FFSessionHandler::invoke " << thisObj.toString() | 
|  | << "::" << methodName << Debug::flush; | 
|  | JSContext* ctx = getJSContext(); | 
|  |  | 
|  | // Used to root JSthis and args while making the JS call | 
|  | // TODO(jat): keep one object and just keep a "stack pointer" into that | 
|  | // object on the native stack so we don't keep allocating/rooting/freeing | 
|  | // an object | 
|  | RootedObject argsRoot(ctx, "FFSessionhandler::invoke"); | 
|  | argsRoot = JS_NewArrayObject(ctx, 0, NULL); | 
|  | if (!JS_SetArrayLength(ctx, argsRoot.get(), numArgs + 1)) { | 
|  | Debug::log(Debug::Error) | 
|  | << "FFSessionhandler::invoke - could not set argsRoot length" | 
|  | << Debug::flush; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | jsval jsThis; | 
|  | if (thisObj.isNull()) { | 
|  | jsThis = OBJECT_TO_JSVAL(global); | 
|  | Debug::log(Debug::Spam) << "  using global object for this" << Debug::flush; | 
|  | } else { | 
|  | makeJsvalFromValue(jsThis, ctx, thisObj); | 
|  | if (Debug::level(Debug::Spam)) { | 
|  | Debug::log(Debug::Spam) << "  obj=" << dumpJsVal(ctx, jsThis) | 
|  | << Debug::flush; | 
|  | } | 
|  | } | 
|  | if (!JS_SetElement(ctx, argsRoot.get(), 0, &jsThis)) { | 
|  | Debug::log(Debug::Error) | 
|  | << "FFSessionhandler::invoke - could not set argsRoot[0] to this" | 
|  | << Debug::flush; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | jsval funcVal; | 
|  | // TODO: handle non-ASCII method names | 
|  | if (!JS_GetProperty(ctx, global, methodName.c_str(), &funcVal)) { | 
|  | Debug::log(Debug::Error) << "Could not get function " << methodName << Debug::flush; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | scoped_array<jsval> jsargs(new jsval[numArgs]); | 
|  | for (int i = 0; i < numArgs; ++i) { | 
|  | makeJsvalFromValue(jsargs[i], ctx, args[i]); | 
|  | if (Debug::level(Debug::Spam)) { | 
|  | Debug::log(Debug::Spam) << "  arg[" << i << "] = " << dumpJsVal(ctx, | 
|  | jsargs[i]) << Debug::flush; | 
|  | } | 
|  | if (!JS_SetElement(ctx, argsRoot.get(), i + 1, &jsargs[i])) { | 
|  | Debug::log(Debug::Error) | 
|  | << "FFSessionhandler::invoke - could not set args[" << (i + 1) << "]" | 
|  | << Debug::flush; | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (JS_IsExceptionPending(ctx)) { | 
|  | JS_ClearPendingException(ctx); | 
|  | } | 
|  |  | 
|  | jsval rval; | 
|  | JSBool ok = JS_CallFunctionValue(ctx, JSVAL_TO_OBJECT(jsThis), funcVal, | 
|  | numArgs, jsargs.get(), &rval); | 
|  |  | 
|  | if (!ok) { | 
|  | if (JS_GetPendingException(ctx, &rval)) { | 
|  | makeValueFromJsval(*returnValue, ctx, rval); | 
|  | Debug::log(Debug::Debugging) << "FFSessionHandler::invoke " | 
|  | << thisObj.toString() << "::" << methodName << " threw exception " | 
|  | << dumpJsVal(ctx, rval) << Debug::flush; | 
|  | } else { | 
|  | Debug::log(Debug::Error) << "Non-exception failure invoking " | 
|  | << methodName << Debug::flush; | 
|  | returnValue->setUndefined(); | 
|  | } | 
|  | } else { | 
|  | makeValueFromJsval(*returnValue, ctx, rval); | 
|  | } | 
|  | Debug::log(Debug::Spam) << "  return= " << *returnValue << Debug::flush; | 
|  | return !ok; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Invoke a plugin-provided method with the given args.  As above, this method does not own | 
|  | * any of its args. | 
|  | * | 
|  | * Returns true if an exception occurred. | 
|  | */ | 
|  | bool FFSessionHandler::invokeSpecial(HostChannel& channel, SpecialMethodId method, int numArgs, | 
|  | const Value* const args, Value* returnValue)  { | 
|  | Debug::log(Debug::Spam) << "FFSessionHandler::invokeSpecial" << Debug::flush; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Convert UTF16 string to UTF8-encoded std::string. | 
|  | * | 
|  | * @return UTF8-encoded string. | 
|  | */ | 
|  | static std::string utf8String(const jschar* str, unsigned len) { | 
|  | std::string utf8str; | 
|  | while (len-- > 0) { | 
|  | unsigned ch = *str++; | 
|  | // check for paired surrogates first, leave unpaired surrogates as-is | 
|  | if (ch >= 0xD800 && ch < 0xDC00 && len > 0 && *str >= 0xDC00 && *str < 0xE000) { | 
|  | ch = ((ch & 1023) << 10) + (*str++ & 1023) + 0x10000; | 
|  | len--; | 
|  | } | 
|  | if (ch < 0x80) {          // U+0000 - U+007F as 0xxxxxxx | 
|  | utf8str.append(1, ch); | 
|  | } else if (ch < 0x800) {  // U+0080 - U+07FF as 110xxxxx 10xxxxxx | 
|  | utf8str.append(1, 0xC0 + ((ch >> 6) & 31)); | 
|  | utf8str.append(1, 0x80 + (ch & 63)); | 
|  | } else if (ch < 0x10000) { // U+0800 - U+FFFF as 1110xxxx 10xxxxxx 10xxxxxx | 
|  | utf8str.append(1, 0xE0 + ((ch >> 12) & 15)); | 
|  | utf8str.append(1, 0x80 + ((ch >> 6) & 63)); | 
|  | utf8str.append(1, 0x80 + (ch & 63)); | 
|  | } else {  // rest as 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | 
|  | utf8str.append(1, 0xF0 + ((ch >> 18) & 7)); | 
|  | utf8str.append(1, 0x80 + ((ch >> 12) & 63)); | 
|  | utf8str.append(1, 0x80 + ((ch >> 6) & 63)); | 
|  | utf8str.append(1, 0x80 + (ch & 63)); | 
|  | } | 
|  | } | 
|  | return utf8str; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Creates a JSString from a UTF8-encoded std::string. | 
|  | * | 
|  | * @return the JSString object, which owns its memory buffer. | 
|  | */ | 
|  | static JSString* stringUtf8(JSContext* ctx, const std::string& utf8str) { | 
|  | unsigned len = 0; | 
|  | for (unsigned i = 0; i < utf8str.length(); ++i) { | 
|  | char ch = utf8str[i]; | 
|  | switch (ch & 0xF8) { | 
|  | // continuation & invalid chars | 
|  | default: | 
|  | // ASCII characters | 
|  | case 0x00: case 0x08: case 0x10: case 0x18: | 
|  | case 0x20: case 0x28: case 0x30: case 0x38: | 
|  | case 0x40: case 0x48: case 0x50: case 0x58: | 
|  | case 0x60: case 0x68: case 0x70: case 0x78: | 
|  | // 2-byte UTF8 characters | 
|  | case 0xC0: case 0xC8: case 0xD0: case 0xD8: | 
|  | // 3-byte UTF8 characters | 
|  | case 0xE0: case 0xE8: | 
|  | ++len; | 
|  | break; | 
|  | case 0xF0: | 
|  | len += 2; | 
|  | break; | 
|  | } | 
|  | } | 
|  | // Account for null terminator even if it isn't included in the string length | 
|  | // Note that buf becomes owned by the JSString and must not be freed here. | 
|  | jschar* buf = static_cast<jschar*>(JS_malloc(ctx, (len + 1) * sizeof(jschar))); | 
|  | if (!buf) { | 
|  | return NULL; | 
|  | } | 
|  | jschar* p = buf; | 
|  | unsigned codePoint; | 
|  | int charsLeft = -1; | 
|  | for (unsigned i = 0; i < utf8str.length(); ++i) { | 
|  | char ch = utf8str[i]; | 
|  | if (charsLeft >= 0) { | 
|  | if ((ch & 0xC0) != 0x80) { | 
|  | // invalid, missing continuation character | 
|  | *p++ = static_cast<jschar>(0xFFFD); | 
|  | charsLeft = -1; | 
|  | } else { | 
|  | codePoint = (codePoint << 6) | (ch & 63); | 
|  | if (!--charsLeft) { | 
|  | if (codePoint >= 0x10000) { | 
|  | codePoint -= 0x10000; | 
|  | *p++ = static_cast<jschar>(0xD800 + ((codePoint >> 10) & 1023)); | 
|  | *p++ = static_cast<jschar>(0xDC00 + (codePoint & 1023)); | 
|  | } else { | 
|  | *p++ = static_cast<jschar>(codePoint); | 
|  | } | 
|  | charsLeft = -1; | 
|  | } | 
|  | } | 
|  | continue; | 
|  | } | 
|  | // Look at the top 5 bits to determine how many bytes are in this character. | 
|  | switch (ch & 0xF8) { | 
|  | default: // skip invalid and continuation chars | 
|  | break; | 
|  | case 0x00: case 0x08: case 0x10: case 0x18: | 
|  | case 0x20: case 0x28: case 0x30: case 0x38: | 
|  | case 0x40: case 0x48: case 0x50: case 0x58: | 
|  | case 0x60: case 0x68: case 0x70: case 0x78: | 
|  | *p++ = static_cast<jschar>(ch); | 
|  | break; | 
|  | case 0xC0: case 0xC8: case 0xD0: case 0xD8: | 
|  | charsLeft = 1; | 
|  | codePoint = ch & 31; | 
|  | break; | 
|  | case 0xE0: case 0xE8: | 
|  | charsLeft = 2; | 
|  | codePoint = ch & 15; | 
|  | break; | 
|  | case 0xF0: | 
|  | charsLeft = 3; | 
|  | codePoint = ch & 7; | 
|  | break; | 
|  | } | 
|  | } | 
|  | // null terminator, apparently some code expects a terminator even though | 
|  | // the strings are counted.  Note that this null word should not be included | 
|  | // in the length, and that the buffer becomes owned by the JSString object. | 
|  | *p = 0; | 
|  | return JS_NewUCString(ctx, buf, p - buf); | 
|  | } | 
|  |  | 
|  | void FFSessionHandler::makeValueFromJsval(Value& retVal, JSContext* ctx, | 
|  | const jsval& value) { | 
|  | if (JSVAL_IS_VOID(value)) { | 
|  | retVal.setUndefined(); | 
|  | } else if (JSVAL_IS_NULL(value)) { | 
|  | retVal.setNull(); | 
|  | } else if (JSVAL_IS_INT(value)) { | 
|  | retVal.setInt(JSVAL_TO_INT(value)); | 
|  | } else if (JSVAL_IS_BOOLEAN(value)) { | 
|  | retVal.setBoolean(JSVAL_TO_BOOLEAN(value)); | 
|  | } else if (JSVAL_IS_STRING(value)) { | 
|  | JSString* str = JSVAL_TO_STRING(value); | 
|  | retVal.setString(utf8String(JS_GetStringChars(str), | 
|  | JS_GetStringLength(str))); | 
|  | } else if (JSVAL_IS_DOUBLE(value)) { | 
|  | retVal.setDouble(*JSVAL_TO_DOUBLE(value)); | 
|  | } else if (JSVAL_IS_OBJECT(value)) { | 
|  | JSObject* obj = JSVAL_TO_OBJECT(value); | 
|  | if (JavaObject::isJavaObject(ctx, obj)) { | 
|  | retVal.setJavaObject(JavaObject::getObjectId(ctx, obj)); | 
|  | } else if (JS_GET_CLASS(ctx, obj) == stringObjectClass) { | 
|  | // JS String wrapper object, treat as a string primitive | 
|  | JSString* str = JS_ValueToString(ctx, value); | 
|  | retVal.setString(utf8String(JS_GetStringChars(str), | 
|  | JS_GetStringLength(str))); | 
|  | // str will be garbage-collected, does not need to be freed | 
|  | } else { | 
|  | // It's a plain-old JavaScript Object | 
|  | std::map<JSObject*, int>::iterator it = jsIdsByObject.find(obj); | 
|  | if (it != jsIdsByObject.end()) { | 
|  | retVal.setJsObjectId(it->second); | 
|  | } else { | 
|  | // Allocate a new id | 
|  | int objId = ++jsObjectId; | 
|  | JS_SetElement(ctx, jsObjectsById, objId, const_cast<jsval*>(&value)); | 
|  | jsIdsByObject[obj] = objId; | 
|  | retVal.setJsObjectId(objId); | 
|  | } | 
|  | } | 
|  | } else { | 
|  | Debug::log(Debug::Error) << "Unhandled jsval type " << Debug::flush; | 
|  | retVal.setString("Unhandled jsval type"); | 
|  | } | 
|  | } | 
|  |  | 
|  | void FFSessionHandler::makeJsvalFromValue(jsval& retVal, JSContext* ctx, | 
|  | const Value& value) { | 
|  | switch (value.getType()) { | 
|  | case Value::NULL_TYPE: | 
|  | retVal = JSVAL_NULL; | 
|  | break; | 
|  | case Value::BOOLEAN: | 
|  | retVal = BOOLEAN_TO_JSVAL(value.getBoolean()); | 
|  | break; | 
|  | case Value::BYTE: | 
|  | retVal = INT_TO_JSVAL((int) value.getByte()); | 
|  | break; | 
|  | case Value::CHAR: | 
|  | retVal = INT_TO_JSVAL((int) value.getChar()); | 
|  | break; | 
|  | case Value::SHORT: | 
|  | retVal = INT_TO_JSVAL((int) value.getShort()); | 
|  | break; | 
|  | case Value::INT: { | 
|  | int intValue = value.getInt(); | 
|  | if (INT_FITS_IN_JSVAL(intValue)) { | 
|  | retVal = INT_TO_JSVAL(intValue); | 
|  | } else { | 
|  | JS_NewNumberValue(ctx, (jsdouble) intValue, &retVal); | 
|  | } | 
|  | break; | 
|  | } | 
|  | // TODO(jat): do we still need long support in the wire format and Value? | 
|  | //    case Value::LONG: | 
|  | //      retVal = value.getLong(); | 
|  | //      break; | 
|  | case Value::FLOAT: | 
|  | JS_NewNumberValue(ctx, (jsdouble) value.getFloat(), &retVal); | 
|  | break; | 
|  | case Value::DOUBLE: | 
|  | JS_NewNumberValue(ctx, (jsdouble) value.getDouble(), &retVal); | 
|  | break; | 
|  | case Value::STRING: | 
|  | { | 
|  | JSString* str = stringUtf8(ctx, value.getString()); | 
|  | retVal = STRING_TO_JSVAL(str); | 
|  | } | 
|  | break; | 
|  | case Value::JAVA_OBJECT: | 
|  | { | 
|  | int javaId = value.getJavaObjectId(); | 
|  | std::map<int, JSObject*>::iterator i = javaObjectsById.find(javaId); | 
|  | if (i == javaObjectsById.end()) { | 
|  | JSObject* obj = JavaObject::construct(ctx, this, javaId); | 
|  | javaObjectsById[javaId] = obj; | 
|  | // We may have previously released the proxy for the same object id, | 
|  | // but have not yet sent a free message back to the server. | 
|  | javaObjectsToFree.erase(javaId); | 
|  | retVal = OBJECT_TO_JSVAL(obj); | 
|  | } else { | 
|  | retVal = OBJECT_TO_JSVAL(i->second); | 
|  | } | 
|  | } | 
|  | break; | 
|  | case Value::JS_OBJECT: | 
|  | { | 
|  | int jsId = value.getJsObjectId(); | 
|  | if (!JS_GetElement(ctx, jsObjectsById, jsId, &retVal)) { | 
|  | Debug::log(Debug::Error) << "Error getting jsObject with id " << jsId << Debug::flush; | 
|  | } | 
|  | if (!JSVAL_IS_OBJECT(retVal)) { | 
|  | Debug::log(Debug::Error) << "Missing jsObject with id " << jsId << Debug::flush; | 
|  | } | 
|  | } | 
|  | break; | 
|  | case Value::UNDEFINED: | 
|  | retVal = JSVAL_VOID; | 
|  | break; | 
|  | default: | 
|  | Debug::log(Debug::Error) << "Unknown Value type " << value.toString() << Debug::flush; | 
|  | } | 
|  | } | 
|  |  | 
|  | void FFSessionHandler::freeJavaObject(int objectId) { | 
|  | if (!javaObjectsById.erase(objectId)) { | 
|  | Debug::log(Debug::Error) << "Trying to free unknown JavaObject: " << objectId << Debug::flush; | 
|  | return; | 
|  | } | 
|  | javaObjectsToFree.insert(objectId); | 
|  | } | 
|  |  | 
|  | void FFSessionHandler::disconnect() { | 
|  | Debug::log(Debug::Debugging) << "FFSessionHandler::disconnect" << Debug::flush; | 
|  | JSContext* ctx = getJSContext(); | 
|  | bool freeCtx = false; | 
|  | if (!ctx) { | 
|  | Debug::log(Debug::Debugging) << "  creating temporary context" | 
|  | << Debug::flush; | 
|  | freeCtx = true; | 
|  | ctx = JS_NewContext(runtime, 8192); | 
|  | if (ctx) { | 
|  | JS_SetOptions(ctx, JSOPTION_VAROBJFIX); | 
|  | #ifdef JSVERSION_LATEST | 
|  | JS_SetVersion(ctx, JSVERSION_LATEST); | 
|  | #endif | 
|  | } | 
|  | } | 
|  | if (ctx) { | 
|  | JS_BeginRequest(ctx); | 
|  | for (std::map<int, JSObject*>::iterator it = javaObjectsById.begin(); | 
|  | it != javaObjectsById.end(); ++it) { | 
|  | int javaId = it->first; | 
|  | JSObject* obj = it->second; | 
|  | if (JavaObject::isJavaObject(ctx, obj)) { | 
|  | // clear the SessionData pointer -- JavaObject knows it is | 
|  | // disconnected if this is null | 
|  | JS_SetPrivate(ctx, obj, NULL); | 
|  | javaObjectsToFree.erase(javaId); | 
|  | } | 
|  | } | 
|  | JS_EndRequest(ctx); | 
|  | if (freeCtx) { | 
|  | JS_DestroyContext(ctx); | 
|  | } | 
|  | } else { | 
|  | Debug::log(Debug::Warning) | 
|  | << "FFSessionHandler::disconnect - no context available" | 
|  | << Debug::flush; | 
|  | } | 
|  | HostChannel* channel = getHostChannel(); | 
|  | if (channel->isConnected()) { | 
|  | channel->disconnectFromHost(); | 
|  | } | 
|  | } |