blob: 85b895dc2bb7970232fab5bd626ecbcaf70da1c3 [file] [log] [blame]
jat@google.com134be542009-08-03 15:30:11 +00001/*
2 * Copyright 2008 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17#include "FFSessionHandler.h"
18#include "HostChannel.h"
19#include "JavaObject.h"
20#include "JSRunner.h"
21#include "Debug.h"
22#include "XpcomDebug.h"
23#include "scoped_ptr/scoped_ptr.h"
24#include "RootedObject.h"
25#include "InvokeMessage.h"
26#include "ServerMethods.h"
jat@google.com23181962009-09-03 22:22:56 +000027#include "AllowedConnections.h"
jat@google.com134be542009-08-03 15:30:11 +000028
29#include "jsapi.h"
30#include "nsCOMPtr.h"
jat@google.com23181962009-09-03 22:22:56 +000031#include "nsStringAPI.h"
jat@google.com134be542009-08-03 15:30:11 +000032#include "nsIJSContextStack.h"
33#include "nsIPrincipal.h"
34#include "nsServiceManagerUtils.h"
35
36static JSContext* getJSContext() {
37 // Get JSContext from stack.
38 nsCOMPtr<nsIJSContextStack> stack =
39 do_GetService("@mozilla.org/js/xpc/ContextStack;1");
40 if (!stack) {
41 return NULL;
42 }
43
44 JSContext *cx;
45 if (NS_FAILED(stack->Peek(&cx))) {
46 return NULL;
47 }
jat@google.com9b0b38c2009-08-24 23:45:48 +000048
49 if (cx == nsnull) {
jat@google.com23181962009-09-03 22:22:56 +000050 // TODO(jat): figure out why this can be null at plugin unload time
jat@google.com268f9982009-11-06 21:34:25 +000051 Debug::log(Debug::Error) << "GWT Dev Plugin: Null JS context" << Debug::flush;
jat@google.com23181962009-09-03 22:22:56 +000052 }
jat@google.com9b0b38c2009-08-24 23:45:48 +000053
jat@google.com134be542009-08-03 15:30:11 +000054 return cx;
55}
56
57FFSessionHandler::FFSessionHandler(HostChannel* channel)
58 : SessionData(channel, this, getJSContext()), jsObjectId(0),
59 jsObjectsById(NULL), stringObjectClass(NULL) {
jat@google.com8b918c12009-11-23 23:38:51 +000060 Debug::log(Debug::Debugging) << "FFSessionHandler::FFSessionHandler(this="
61 << this << ")" << Debug::flush;
jat@google.com134be542009-08-03 15:30:11 +000062 // TODO(jat): is there a way to avoid calling this twice, without keeping
63 // JSContext in an instance field?
64 JSContext* ctx = getJSContext();
65 if (!JS_AddNamedRoot(ctx, &jsObjectsById, "jsObjectsById")) {
66 Debug::log(Debug::Error) << "Error rooting jsObjectsById" << Debug::flush;
67 }
68 jsObjectsById = JS_NewArrayObject(ctx, 0, NULL);
69 if (!jsObjectsById) {
70 Debug::log(Debug::Error) << "Error rooting jsObjectsById" << Debug::flush;
71 }
72 if (!JS_AddNamedRoot(ctx, &toStringTearOff, "toStringTearOff")) {
73 Debug::log(Debug::Error) << "Error rooting toStringTearOff" << Debug::flush;
74 }
75 getStringObjectClass(ctx);
76 getToStringTearOff(ctx);
77}
78
79void FFSessionHandler::getStringObjectClass(JSContext* ctx) {
80 jsval str = JS_GetEmptyStringValue(ctx);
81 JSObject* obj = 0;
82 if (!JS_ValueToObject(ctx, str, &obj)) {
83 return;
84 }
85 if (!obj) {
86 return;
87 }
88 stringObjectClass = JS_GET_CLASS(ctx, obj);
89}
90
91void FFSessionHandler::getToStringTearOff(JSContext* ctx) {
92 jsval funcVal;
jat@google.com9b0b38c2009-08-24 23:45:48 +000093
94 Debug::log(Debug::Debugging) << "Getting function \"__gwt_makeTearOff\""
95 << Debug::flush;
96
97 if (!JS_GetProperty(ctx, global, "__gwt_makeTearOff", &funcVal)
98 || funcVal == JSVAL_VOID) {
jat@google.com134be542009-08-03 15:30:11 +000099 Debug::log(Debug::Error) << "Could not get function \"__gwt_makeTearOff\""
100 << Debug::flush;
101 return;
102 }
103 jsval jsargs[3] = {
104 JSVAL_NULL, // no proxy
105 INT_TO_JSVAL(InvokeMessage::TOSTRING_DISP_ID), // dispId
106 JSVAL_ZERO // arg count is zero
107 };
108 if (!JS_CallFunctionValue(ctx, global, funcVal, 3, jsargs, &toStringTearOff)) {
109 jsval exc;
110 if (JS_GetPendingException(ctx, &exc)) {
111 Debug::log(Debug::Error)
112 << "__gwt_makeTearOff(null,0,0) threw exception "
113 << dumpJsVal(ctx, exc) << Debug::flush;
114 } else {
115 Debug::log(Debug::Error) << "Error creating toString tear-off"
116 << Debug::flush;
117 }
118 // TODO(jat): show some crash page and die
119 }
120}
121
122FFSessionHandler::~FFSessionHandler(void) {
jat@google.com8b918c12009-11-23 23:38:51 +0000123 Debug::log(Debug::Debugging) << "FFSessionHandler::~FFSessionHandler(this="
124 << this << ")" << Debug::flush;
jat@google.com134be542009-08-03 15:30:11 +0000125 disconnect();
126 if (runtime) {
127 JS_RemoveRootRT(runtime, &jsObjectsById);
128 jsObjectsById = NULL;
129 JS_RemoveRootRT(runtime, &toStringTearOff);
130 runtime = NULL;
131 }
132}
133
scottb@google.comb0dbdff2009-11-23 21:18:40 +0000134void FFSessionHandler::disconnectDetectedImpl() {
135 JSContext* ctx = getJSContext();
136 if (!ctx) {
137 return;
138 }
139
140 Debug::log(Debug::Debugging) << "Getting function \"__gwt_disconnected\""
141 << Debug::flush;
142
143 jsval funcVal;
144 if (!JS_GetProperty(ctx, global, "__gwt_disconnected", &funcVal)
145 || funcVal == JSVAL_VOID) {
146 Debug::log(Debug::Error) << "Could not get function \"__gwt_disconnected\""
147 << Debug::flush;
148 return;
149 }
150 jsval rval;
151 JS_CallFunctionValue(ctx, global, funcVal, 0, 0, &rval);
152}
153
jat@google.com134be542009-08-03 15:30:11 +0000154void FFSessionHandler::freeValue(HostChannel& channel, int idCount, const int* ids) {
155 Debug::DebugStream& dbg = Debug::log(Debug::Spam)
156 << "FFSessionHandler::freeValue [ ";
157 JSContext* ctx = getJSContext();
158
159 for (int i = 0; i < idCount; ++i) {
160 int objId = ids[i];
161 dbg << objId << " ";
162 jsval toRemove;
163 if (JS_GetElement(ctx, jsObjectsById, objId, &toRemove) && JSVAL_IS_OBJECT(toRemove)) {
jat@google.com5e86cbd2009-08-22 23:59:24 +0000164 jsIdsByObject.erase(identityFromObject(JSVAL_TO_OBJECT(toRemove)));
jat@google.com134be542009-08-03 15:30:11 +0000165 JS_DeleteElement(ctx, jsObjectsById, objId);
166 } else {
167 Debug::log(Debug::Error) << "Error deleting js objId=" << objId << Debug::flush;
168 }
169 }
170
171 dbg << "]" << Debug::flush;
172}
173
174void FFSessionHandler::loadJsni(HostChannel& channel, const std::string& js) {
175 Debug::log(Debug::Spam) << "FFSessionHandler::loadJsni " << js << "(EOM)" << Debug::flush;
176 JSContext* ctx = getJSContext();
177 if (!JSRunner::eval(ctx, global, js)) {
178 Debug::log(Debug::Error) << "Error executing script" << Debug::flush;
179 }
180}
181
182void FFSessionHandler::sendFreeValues(HostChannel& channel) {
183 unsigned n = javaObjectsToFree.size();
184 if (n) {
185 scoped_array<int> ids(new int[n]);
186 int i = 0;
187 for (std::set<int>::iterator it = javaObjectsToFree.begin();
188 it != javaObjectsToFree.end(); ++it) {
189 ids[i++] = *it;
190 }
191 if (ServerMethods::freeJava(channel, this, n, ids.get())) {
192 javaObjectsToFree.clear();
193 }
194 }
195}
196
jat@google.com5e86cbd2009-08-22 23:59:24 +0000197void FFSessionHandler::fatalError(HostChannel& channel,
198 const std::string& message) {
199 // TODO(jat): implement
200}
201
jat@google.com134be542009-08-03 15:30:11 +0000202bool FFSessionHandler::invoke(HostChannel& channel, const Value& thisObj, const std::string& methodName,
203 int numArgs, const Value* const args, Value* returnValue) {
204 Debug::log(Debug::Spam) << "FFSessionHandler::invoke " << thisObj.toString()
205 << "::" << methodName << Debug::flush;
206 JSContext* ctx = getJSContext();
207
208 // Used to root JSthis and args while making the JS call
209 // TODO(jat): keep one object and just keep a "stack pointer" into that
210 // object on the native stack so we don't keep allocating/rooting/freeing
211 // an object
212 RootedObject argsRoot(ctx, "FFSessionhandler::invoke");
213 argsRoot = JS_NewArrayObject(ctx, 0, NULL);
214 if (!JS_SetArrayLength(ctx, argsRoot.get(), numArgs + 1)) {
215 Debug::log(Debug::Error)
216 << "FFSessionhandler::invoke - could not set argsRoot length"
217 << Debug::flush;
218 return true;
219 }
jat@google.com5e86cbd2009-08-22 23:59:24 +0000220
jat@google.com134be542009-08-03 15:30:11 +0000221 jsval jsThis;
222 if (thisObj.isNull()) {
223 jsThis = OBJECT_TO_JSVAL(global);
224 Debug::log(Debug::Spam) << " using global object for this" << Debug::flush;
225 } else {
226 makeJsvalFromValue(jsThis, ctx, thisObj);
227 if (Debug::level(Debug::Spam)) {
228 Debug::log(Debug::Spam) << " obj=" << dumpJsVal(ctx, jsThis)
229 << Debug::flush;
230 }
231 }
232 if (!JS_SetElement(ctx, argsRoot.get(), 0, &jsThis)) {
233 Debug::log(Debug::Error)
234 << "FFSessionhandler::invoke - could not set argsRoot[0] to this"
235 << Debug::flush;
236 return true;
237 }
238
239 jsval funcVal;
240 // TODO: handle non-ASCII method names
jat@google.com9b0b38c2009-08-24 23:45:48 +0000241 if (!JS_GetProperty(ctx, global, methodName.c_str(), &funcVal)
242 || funcVal == JSVAL_VOID) {
243 Debug::log(Debug::Error) << "Could not get function " << methodName
244 << Debug::flush;
jat@google.com134be542009-08-03 15:30:11 +0000245 return true;
246 }
247
248 scoped_array<jsval> jsargs(new jsval[numArgs]);
249 for (int i = 0; i < numArgs; ++i) {
250 makeJsvalFromValue(jsargs[i], ctx, args[i]);
251 if (Debug::level(Debug::Spam)) {
252 Debug::log(Debug::Spam) << " arg[" << i << "] = " << dumpJsVal(ctx,
253 jsargs[i]) << Debug::flush;
254 }
255 if (!JS_SetElement(ctx, argsRoot.get(), i + 1, &jsargs[i])) {
256 Debug::log(Debug::Error)
257 << "FFSessionhandler::invoke - could not set args[" << (i + 1) << "]"
258 << Debug::flush;
259 return true;
260 }
261 }
262
263 if (JS_IsExceptionPending(ctx)) {
264 JS_ClearPendingException(ctx);
265 }
266
267 jsval rval;
268 JSBool ok = JS_CallFunctionValue(ctx, JSVAL_TO_OBJECT(jsThis), funcVal,
269 numArgs, jsargs.get(), &rval);
270
271 if (!ok) {
272 if (JS_GetPendingException(ctx, &rval)) {
273 makeValueFromJsval(*returnValue, ctx, rval);
274 Debug::log(Debug::Debugging) << "FFSessionHandler::invoke "
275 << thisObj.toString() << "::" << methodName << " threw exception "
276 << dumpJsVal(ctx, rval) << Debug::flush;
277 } else {
278 Debug::log(Debug::Error) << "Non-exception failure invoking "
279 << methodName << Debug::flush;
280 returnValue->setUndefined();
281 }
282 } else {
283 makeValueFromJsval(*returnValue, ctx, rval);
284 }
285 Debug::log(Debug::Spam) << " return= " << *returnValue << Debug::flush;
286 return !ok;
287}
288
289/**
290 * Invoke a plugin-provided method with the given args. As above, this method does not own
291 * any of its args.
292 *
293 * Returns true if an exception occurred.
294 */
295bool FFSessionHandler::invokeSpecial(HostChannel& channel, SpecialMethodId method, int numArgs,
296 const Value* const args, Value* returnValue) {
297 Debug::log(Debug::Spam) << "FFSessionHandler::invokeSpecial" << Debug::flush;
298 return false;
299}
300
301/**
302 * Convert UTF16 string to UTF8-encoded std::string.
jat@google.com5e86cbd2009-08-22 23:59:24 +0000303 *
304 * This is implemented here because the Mozilla libraries mangle certain UTF8
305 * strings.
306 *
jat@google.com134be542009-08-03 15:30:11 +0000307 * @return UTF8-encoded string.
308 */
309static std::string utf8String(const jschar* str, unsigned len) {
310 std::string utf8str;
311 while (len-- > 0) {
312 unsigned ch = *str++;
313 // check for paired surrogates first, leave unpaired surrogates as-is
314 if (ch >= 0xD800 && ch < 0xDC00 && len > 0 && *str >= 0xDC00 && *str < 0xE000) {
315 ch = ((ch & 1023) << 10) + (*str++ & 1023) + 0x10000;
316 len--;
317 }
318 if (ch < 0x80) { // U+0000 - U+007F as 0xxxxxxx
319 utf8str.append(1, ch);
320 } else if (ch < 0x800) { // U+0080 - U+07FF as 110xxxxx 10xxxxxx
321 utf8str.append(1, 0xC0 + ((ch >> 6) & 31));
322 utf8str.append(1, 0x80 + (ch & 63));
323 } else if (ch < 0x10000) { // U+0800 - U+FFFF as 1110xxxx 10xxxxxx 10xxxxxx
324 utf8str.append(1, 0xE0 + ((ch >> 12) & 15));
325 utf8str.append(1, 0x80 + ((ch >> 6) & 63));
326 utf8str.append(1, 0x80 + (ch & 63));
327 } else { // rest as 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
328 utf8str.append(1, 0xF0 + ((ch >> 18) & 7));
329 utf8str.append(1, 0x80 + ((ch >> 12) & 63));
330 utf8str.append(1, 0x80 + ((ch >> 6) & 63));
331 utf8str.append(1, 0x80 + (ch & 63));
332 }
333 }
334 return utf8str;
335}
336
337/**
338 * Creates a JSString from a UTF8-encoded std::string.
jat@google.com5e86cbd2009-08-22 23:59:24 +0000339 *
340 * This is implemented here because the Mozilla libraries mangle certain UTF8
341 * strings.
342 *
jat@google.com134be542009-08-03 15:30:11 +0000343 * @return the JSString object, which owns its memory buffer.
344 */
345static JSString* stringUtf8(JSContext* ctx, const std::string& utf8str) {
346 unsigned len = 0;
347 for (unsigned i = 0; i < utf8str.length(); ++i) {
348 char ch = utf8str[i];
349 switch (ch & 0xF8) {
350 // continuation & invalid chars
351 default:
352 // ASCII characters
353 case 0x00: case 0x08: case 0x10: case 0x18:
354 case 0x20: case 0x28: case 0x30: case 0x38:
355 case 0x40: case 0x48: case 0x50: case 0x58:
356 case 0x60: case 0x68: case 0x70: case 0x78:
357 // 2-byte UTF8 characters
358 case 0xC0: case 0xC8: case 0xD0: case 0xD8:
359 // 3-byte UTF8 characters
360 case 0xE0: case 0xE8:
361 ++len;
362 break;
363 case 0xF0:
364 len += 2;
365 break;
366 }
367 }
368 // Account for null terminator even if it isn't included in the string length
369 // Note that buf becomes owned by the JSString and must not be freed here.
370 jschar* buf = static_cast<jschar*>(JS_malloc(ctx, (len + 1) * sizeof(jschar)));
371 if (!buf) {
372 return NULL;
373 }
374 jschar* p = buf;
375 unsigned codePoint;
376 int charsLeft = -1;
377 for (unsigned i = 0; i < utf8str.length(); ++i) {
378 char ch = utf8str[i];
379 if (charsLeft >= 0) {
380 if ((ch & 0xC0) != 0x80) {
381 // invalid, missing continuation character
382 *p++ = static_cast<jschar>(0xFFFD);
383 charsLeft = -1;
384 } else {
385 codePoint = (codePoint << 6) | (ch & 63);
386 if (!--charsLeft) {
387 if (codePoint >= 0x10000) {
388 codePoint -= 0x10000;
389 *p++ = static_cast<jschar>(0xD800 + ((codePoint >> 10) & 1023));
390 *p++ = static_cast<jschar>(0xDC00 + (codePoint & 1023));
391 } else {
392 *p++ = static_cast<jschar>(codePoint);
393 }
394 charsLeft = -1;
395 }
396 }
397 continue;
398 }
399 // Look at the top 5 bits to determine how many bytes are in this character.
400 switch (ch & 0xF8) {
401 default: // skip invalid and continuation chars
402 break;
403 case 0x00: case 0x08: case 0x10: case 0x18:
404 case 0x20: case 0x28: case 0x30: case 0x38:
405 case 0x40: case 0x48: case 0x50: case 0x58:
406 case 0x60: case 0x68: case 0x70: case 0x78:
407 *p++ = static_cast<jschar>(ch);
408 break;
409 case 0xC0: case 0xC8: case 0xD0: case 0xD8:
410 charsLeft = 1;
411 codePoint = ch & 31;
412 break;
413 case 0xE0: case 0xE8:
414 charsLeft = 2;
415 codePoint = ch & 15;
416 break;
417 case 0xF0:
418 charsLeft = 3;
419 codePoint = ch & 7;
420 break;
421 }
422 }
423 // null terminator, apparently some code expects a terminator even though
424 // the strings are counted. Note that this null word should not be included
425 // in the length, and that the buffer becomes owned by the JSString object.
426 *p = 0;
427 return JS_NewUCString(ctx, buf, p - buf);
428}
429
430void FFSessionHandler::makeValueFromJsval(Value& retVal, JSContext* ctx,
431 const jsval& value) {
432 if (JSVAL_IS_VOID(value)) {
433 retVal.setUndefined();
434 } else if (JSVAL_IS_NULL(value)) {
435 retVal.setNull();
436 } else if (JSVAL_IS_INT(value)) {
437 retVal.setInt(JSVAL_TO_INT(value));
438 } else if (JSVAL_IS_BOOLEAN(value)) {
439 retVal.setBoolean(JSVAL_TO_BOOLEAN(value));
440 } else if (JSVAL_IS_STRING(value)) {
441 JSString* str = JSVAL_TO_STRING(value);
442 retVal.setString(utf8String(JS_GetStringChars(str),
443 JS_GetStringLength(str)));
444 } else if (JSVAL_IS_DOUBLE(value)) {
445 retVal.setDouble(*JSVAL_TO_DOUBLE(value));
446 } else if (JSVAL_IS_OBJECT(value)) {
447 JSObject* obj = JSVAL_TO_OBJECT(value);
448 if (JavaObject::isJavaObject(ctx, obj)) {
449 retVal.setJavaObject(JavaObject::getObjectId(ctx, obj));
450 } else if (JS_GET_CLASS(ctx, obj) == stringObjectClass) {
451 // JS String wrapper object, treat as a string primitive
452 JSString* str = JS_ValueToString(ctx, value);
453 retVal.setString(utf8String(JS_GetStringChars(str),
454 JS_GetStringLength(str)));
455 // str will be garbage-collected, does not need to be freed
456 } else {
457 // It's a plain-old JavaScript Object
jat@google.com5e86cbd2009-08-22 23:59:24 +0000458 void* objKey = identityFromObject(obj);
459 std::map<void*, int>::iterator it = jsIdsByObject.find(objKey);
jat@google.com134be542009-08-03 15:30:11 +0000460 if (it != jsIdsByObject.end()) {
461 retVal.setJsObjectId(it->second);
462 } else {
463 // Allocate a new id
464 int objId = ++jsObjectId;
465 JS_SetElement(ctx, jsObjectsById, objId, const_cast<jsval*>(&value));
jat@google.com5e86cbd2009-08-22 23:59:24 +0000466 jsIdsByObject[objKey] = objId;
jat@google.com134be542009-08-03 15:30:11 +0000467 retVal.setJsObjectId(objId);
468 }
469 }
470 } else {
471 Debug::log(Debug::Error) << "Unhandled jsval type " << Debug::flush;
472 retVal.setString("Unhandled jsval type");
473 }
474}
475
476void FFSessionHandler::makeJsvalFromValue(jsval& retVal, JSContext* ctx,
477 const Value& value) {
478 switch (value.getType()) {
479 case Value::NULL_TYPE:
480 retVal = JSVAL_NULL;
481 break;
482 case Value::BOOLEAN:
483 retVal = BOOLEAN_TO_JSVAL(value.getBoolean());
484 break;
485 case Value::BYTE:
486 retVal = INT_TO_JSVAL((int) value.getByte());
487 break;
488 case Value::CHAR:
489 retVal = INT_TO_JSVAL((int) value.getChar());
490 break;
491 case Value::SHORT:
492 retVal = INT_TO_JSVAL((int) value.getShort());
493 break;
494 case Value::INT: {
495 int intValue = value.getInt();
496 if (INT_FITS_IN_JSVAL(intValue)) {
497 retVal = INT_TO_JSVAL(intValue);
498 } else {
499 JS_NewNumberValue(ctx, (jsdouble) intValue, &retVal);
500 }
501 break;
502 }
503 // TODO(jat): do we still need long support in the wire format and Value?
504// case Value::LONG:
505// retVal = value.getLong();
506// break;
507 case Value::FLOAT:
508 JS_NewNumberValue(ctx, (jsdouble) value.getFloat(), &retVal);
509 break;
510 case Value::DOUBLE:
511 JS_NewNumberValue(ctx, (jsdouble) value.getDouble(), &retVal);
512 break;
513 case Value::STRING:
514 {
515 JSString* str = stringUtf8(ctx, value.getString());
516 retVal = STRING_TO_JSVAL(str);
517 }
518 break;
519 case Value::JAVA_OBJECT:
520 {
521 int javaId = value.getJavaObjectId();
522 std::map<int, JSObject*>::iterator i = javaObjectsById.find(javaId);
523 if (i == javaObjectsById.end()) {
524 JSObject* obj = JavaObject::construct(ctx, this, javaId);
525 javaObjectsById[javaId] = obj;
526 // We may have previously released the proxy for the same object id,
527 // but have not yet sent a free message back to the server.
528 javaObjectsToFree.erase(javaId);
529 retVal = OBJECT_TO_JSVAL(obj);
530 } else {
531 retVal = OBJECT_TO_JSVAL(i->second);
532 }
533 }
534 break;
535 case Value::JS_OBJECT:
536 {
537 int jsId = value.getJsObjectId();
538 if (!JS_GetElement(ctx, jsObjectsById, jsId, &retVal)) {
539 Debug::log(Debug::Error) << "Error getting jsObject with id " << jsId << Debug::flush;
540 }
541 if (!JSVAL_IS_OBJECT(retVal)) {
542 Debug::log(Debug::Error) << "Missing jsObject with id " << jsId << Debug::flush;
543 }
544 }
545 break;
546 case Value::UNDEFINED:
547 retVal = JSVAL_VOID;
548 break;
549 default:
550 Debug::log(Debug::Error) << "Unknown Value type " << value.toString() << Debug::flush;
551 }
552}
553
554void FFSessionHandler::freeJavaObject(int objectId) {
555 if (!javaObjectsById.erase(objectId)) {
556 Debug::log(Debug::Error) << "Trying to free unknown JavaObject: " << objectId << Debug::flush;
557 return;
558 }
559 javaObjectsToFree.insert(objectId);
560}
561
562void FFSessionHandler::disconnect() {
563 Debug::log(Debug::Debugging) << "FFSessionHandler::disconnect" << Debug::flush;
564 JSContext* ctx = getJSContext();
565 bool freeCtx = false;
566 if (!ctx) {
567 Debug::log(Debug::Debugging) << " creating temporary context"
568 << Debug::flush;
569 freeCtx = true;
570 ctx = JS_NewContext(runtime, 8192);
571 if (ctx) {
572 JS_SetOptions(ctx, JSOPTION_VAROBJFIX);
573#ifdef JSVERSION_LATEST
574 JS_SetVersion(ctx, JSVERSION_LATEST);
575#endif
576 }
577 }
578 if (ctx) {
579 JS_BeginRequest(ctx);
580 for (std::map<int, JSObject*>::iterator it = javaObjectsById.begin();
581 it != javaObjectsById.end(); ++it) {
582 int javaId = it->first;
583 JSObject* obj = it->second;
584 if (JavaObject::isJavaObject(ctx, obj)) {
585 // clear the SessionData pointer -- JavaObject knows it is
586 // disconnected if this is null
587 JS_SetPrivate(ctx, obj, NULL);
588 javaObjectsToFree.erase(javaId);
589 }
590 }
591 JS_EndRequest(ctx);
592 if (freeCtx) {
593 JS_DestroyContext(ctx);
594 }
595 } else {
596 Debug::log(Debug::Warning)
597 << "FFSessionHandler::disconnect - no context available"
598 << Debug::flush;
599 }
600 HostChannel* channel = getHostChannel();
601 if (channel->isConnected()) {
602 channel->disconnectFromHost();
603 }
604}
jat@google.com5e86cbd2009-08-22 23:59:24 +0000605
606void* FFSessionHandler::identityFromObject(JSObject* obj) {
607 JSContext* ctx = getJSContext();
608 jsval rval;
609 void* returnValue = obj;
610 if (JS_GetProperty(ctx, obj, "wrappedJSObject", &rval)
611 && JSVAL_IS_OBJECT(rval)) {
612 returnValue = JSVAL_TO_OBJECT(rval);
613 Debug::log(Debug::Info) << "identityFromObject mapped " << obj << " to "
614 << returnValue << Debug::flush;
615 }
616 return returnValue;
617}