blob: 4344a5ba3fadc24e5c6e5830e216045e113c7dbd [file] [log] [blame]
gwt.mirrorbot@gmail.comd54a4bd2010-06-07 19:20:31 +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 <cstring>
18
19#include "ScriptableInstance.h"
20#include "InvokeMessage.h"
21#include "ReturnMessage.h"
22#include "ServerMethods.h"
23#include "AllowedConnections.h"
gwt.mirrorbot@gmail.comd54a4bd2010-06-07 19:20:31 +000024
25#include "mozincludes.h"
26#include "scoped_ptr/scoped_ptr.h"
27#include "NPVariantWrapper.h"
28
29using std::string;
30using std::endl;
conroy@google.com88069782010-11-23 13:51:12 +000031const static string BACKGROUND_PAGE_STR = "chrome-extension://jpjpnpmbddbjkfaccnmhnkdgjideieim/background.html";
32const static string UNKNOWN_STR = "unknown";
33const static string INCLUDE_STR = "include";
34const static string EXCLUDE_STR = "exclude";
gwt.mirrorbot@gmail.comd54a4bd2010-06-07 19:20:31 +000035
36static inline string convertToString(const NPString& str) {
37 return string(GetNPStringUTF8Characters(str), GetNPStringUTF8Length(str));
38}
39
40string ScriptableInstance::computeTabIdentity() {
41 return "";
42}
43
44void ScriptableInstance::dumpObjectBytes(NPObject* obj) {
45 char buf[20];
46 Debug::log(Debug::Debugging) << " object bytes:\n";
47 const unsigned char* ptr = reinterpret_cast<const unsigned char*>(obj);
48 for (int i = 0; i < 24; ++i) {
49 snprintf(buf, sizeof(buf), " %02x", ptr[i]);
50 Debug::log(Debug::Debugging) << buf;
51 }
52 NPVariant objVar;
53 OBJECT_TO_NPVARIANT(obj, objVar);
54 Debug::log(Debug::Debugging) << " obj.toString()="
55 << NPVariantProxy::toString(objVar) << Debug::flush;
56}
57
58ScriptableInstance::ScriptableInstance(NPP npp) : NPObjectWrapper<ScriptableInstance>(npp),
59 plugin(*reinterpret_cast<Plugin*>(npp->pdata)),
60 _channel(new HostChannel()),
61 localObjects(),
62 _connectId(NPN_GetStringIdentifier("connect")),
63 initID(NPN_GetStringIdentifier("init")),
64 toStringID(NPN_GetStringIdentifier("toString")),
conroy@google.com88069782010-11-23 13:51:12 +000065 loadHostEntriesID(NPN_GetStringIdentifier("loadHostEntries")),
66 locationID(NPN_GetStringIdentifier("location")),
67 hrefID(NPN_GetStringIdentifier("href")),
68 urlID(NPN_GetStringIdentifier("url")),
69 includeID(NPN_GetStringIdentifier("include")),
70 getHostPermissionID(NPN_GetStringIdentifier("getHostPermission")),
gwt.mirrorbot@gmail.comd54a4bd2010-06-07 19:20:31 +000071 connectedID(NPN_GetStringIdentifier("connected")),
72 statsID(NPN_GetStringIdentifier("stats")),
73 gwtId(NPN_GetStringIdentifier("__gwt_ObjectId")),
74 jsDisconnectedID(NPN_GetStringIdentifier("__gwt_disconnected")),
75 jsInvokeID(NPN_GetStringIdentifier("__gwt_jsInvoke")),
76 jsResultID(NPN_GetStringIdentifier("__gwt_makeResult")),
77 jsTearOffID(NPN_GetStringIdentifier("__gwt_makeTearOff")),
78 jsValueOfID(NPN_GetStringIdentifier("valueOf")),
79 idx0(NPN_GetIntIdentifier(0)),
80 idx1(NPN_GetIntIdentifier(1)) {
81 savedValueIdx = -1;
82 if (NPN_GetValue(npp, NPNVWindowNPObject, &window) != NPERR_NO_ERROR) {
83 Debug::log(Debug::Error) << "Error getting window object" << Debug::flush;
84 }
85}
86
87ScriptableInstance::~ScriptableInstance() {
88 // TODO(jat): free any remaining Java objects held by JS, then make
89 // the JS wrapper handle that situation gracefully
90 if (window) {
91 NPN_ReleaseObject(window);
92 }
93 for (hash_map<int, JavaObject*>::iterator it = javaObjects.begin(); it != javaObjects.end();
94 ++it) {
95 Debug::log(Debug::Spam) << " disconnecting Java wrapper " << it->first << Debug::flush;
96 it->second->disconnectPlugin();
97 }
98 if (_channel) {
99 _channel->disconnectFromHost();
100 delete _channel;
101 }
102}
103
104void ScriptableInstance::dumpJSresult(const char* js) {
105 NPString npScript;
106 dupString(js, npScript);
107 NPVariantWrapper wrappedRetVal(*this);
108 if (!NPN_Evaluate(getNPP(), window, &npScript, wrappedRetVal.addressForReturn())) {
109 Debug::log(Debug::Error) << " *** dumpJSresult failed" << Debug::flush;
110 return;
111 }
112 Debug::log(Debug::Info) << "dumpWindow=" << wrappedRetVal.toString() << Debug::flush;
113}
114
115bool ScriptableInstance::tryGetStringPrimitive(NPObject* obj, NPVariant& result) {
116 if (NPN_HasMethod(getNPP(), obj, jsValueOfID)) {
117 if (NPN_Invoke(getNPP(), obj, jsValueOfID, 0, 0, &result)
118 && NPVariantProxy::isString(result)) {
119 return true;
120 }
121 NPVariantProxy::release(result);
122 }
123 return false;
124}
125
126bool ScriptableInstance::makeResult(bool isException, const Value& value, NPVariant* result) {
127 Debug::log(Debug::Debugging) << "makeResult(" << isException << ", " << value << ")"
128 << Debug::flush;
129 Value temp;
130 if (value.getType() == Value::JAVA_OBJECT) {
131 int javaId = value.getJavaObjectId();
132 // We may have previously released the proxy for the same object id,
133 // but have not yet sent a free message back to the server.
134 javaObjectsToFree.erase(javaId);
135 }
136 NPVariantArray varArgs(*this, 3);
137 varArgs[0] = isException ? 1 : 0;
138 varArgs[1] = value;
139 NPVariantWrapper retVal(*this);
140 return NPN_Invoke(getNPP(), window, jsResultID, varArgs.getArray(), varArgs.getSize(), result);
141}
142
143//=============================================================================
144// NPObject methods
145//=============================================================================
146
147bool ScriptableInstance::hasProperty(NPIdentifier name) {
148 if (!NPN_IdentifierIsString(name)) {
149 // all numeric properties are ok, as we assume only JSNI code is making
150 // the field access via dispatchID
151 return true;
152 }
153 // TODO: special-case toString tear-offs
154 return name == statsID || name == connectedID;
155}
156
157bool ScriptableInstance::getProperty(NPIdentifier name, NPVariant* variant) {
158 Debug::log(Debug::Debugging) << "ScriptableInstance::getProperty(name="
159 << NPN_UTF8FromIdentifier(name) << ")" << Debug::flush;
160 bool retVal = false;
161 VOID_TO_NPVARIANT(*variant);
162 if (name == connectedID) {
163 BOOLEAN_TO_NPVARIANT(_channel->isConnected(), *variant);
164 retVal = true;
165 } else if (name == statsID) {
166 NPVariantProxy::assignFrom(*variant, "<stats data>");
167 retVal = true;
168 }
169 if (retVal) {
170 // TODO: testing
171 Debug::log(Debug::Debugging) << " return value " << *variant
172 << Debug::flush;
173 }
174 return retVal;
175}
176
177bool ScriptableInstance::setProperty(NPIdentifier name, const NPVariant* variant) {
178 Debug::log(Debug::Debugging) << "ScriptableInstance::setProperty(name="
179 << NPN_UTF8FromIdentifier(name) << ", value=" << *variant << ")"
180 << Debug::flush;
181 return false;
182}
183
184bool ScriptableInstance::hasMethod(NPIdentifier name) {
185 Debug::log(Debug::Debugging) << "ScriptableInstance::hasMethod(name=" << NPN_UTF8FromIdentifier(name) << ")"
186 << Debug::flush;
conroy@google.com88069782010-11-23 13:51:12 +0000187 if (name == _connectId ||
188 name == initID ||
189 name == toStringID ||
190 name == loadHostEntriesID ||
191 name == getHostPermissionID) {
gwt.mirrorbot@gmail.comd54a4bd2010-06-07 19:20:31 +0000192 return true;
193 }
194 return false;
195}
196
197bool ScriptableInstance::invoke(NPIdentifier name, const NPVariant* args, unsigned argCount,
198 NPVariant* result) {
199 NPUTF8* uname = NPN_UTF8FromIdentifier(name);
200 Debug::log(Debug::Debugging) << "ScriptableInstance::invoke(name=" << uname << ",#args=" << argCount << ")"
201 << Debug::flush;
202 VOID_TO_NPVARIANT(*result);
203 if (name == _connectId) {
204 connect(args, argCount, result);
205 } else if (name == initID) {
206 init(args, argCount, result);
207 } else if (name == toStringID) {
208 // TODO(jat): figure out why this doesn't show in Firebug
209 string val("[GWT OOPHM Plugin: connected=");
210 val += _channel->isConnected() ? 'Y' : 'N';
211 val += ']';
212 NPVariantProxy::assignFrom(*result, val);
conroy@google.com88069782010-11-23 13:51:12 +0000213 } else if (name == loadHostEntriesID) {
214 loadHostEntries(args, argCount, result);
215 } else if (name == getHostPermissionID) {
216 getHostPermission(args, argCount, result);
gwt.mirrorbot@gmail.comd54a4bd2010-06-07 19:20:31 +0000217 }
218 return true;
219}
220
221bool ScriptableInstance::invokeDefault(const NPVariant* args, unsigned argCount,
222 NPVariant* result) {
223 Debug::log(Debug::Debugging) << "ScriptableInstance::invokeDefault(#args=" << argCount << ")"
224 << Debug::flush;
225 VOID_TO_NPVARIANT(*result);
226 return true;
227}
228
229bool ScriptableInstance::enumeration(NPIdentifier** propReturn, uint32_t* count) {
230 Debug::log(Debug::Debugging) << "ScriptableInstance::enumeration()" << Debug::flush;
231 int n = 2;
232 NPIdentifier* props = static_cast<NPIdentifier*>(NPN_MemAlloc(sizeof(NPIdentifier) * n));
233 props[0] = connectedID;
234 props[1] = statsID;
235 *propReturn = props;
236 *count = n;
237 return true;
238}
239
240//=============================================================================
241// internal methods
242//=============================================================================
243
244void ScriptableInstance::init(const NPVariant* args, unsigned argCount, NPVariant* result) {
245 if (argCount != 1 || !NPVariantProxy::isObject(args[0])) {
246 // TODO: better failure?
247 Debug::log(Debug::Error) << "ScriptableInstance::init called with incorrect arguments:\n";
248 for (unsigned i = 0; i < argCount; ++i) {
249 Debug::log(Debug::Error) << " " << i << " " << NPVariantProxy::toString(args[i]) << "\n";
250 }
251 Debug::log(Debug::Error) << Debug::flush;
252 result->type = NPVariantType_Void;
253 return;
254 }
255 if (window) {
256 NPN_ReleaseObject(window);
257 }
258 // replace our window object with that passed by the caller
259 window = NPVariantProxy::getAsObject(args[0]);
260 NPN_RetainObject(window);
261 BOOLEAN_TO_NPVARIANT(true, *result);
262 result->type = NPVariantType_Bool;
263}
264
conroy@google.com88069782010-11-23 13:51:12 +0000265string ScriptableInstance::getLocationHref() {
266 NPVariantWrapper locationVariant(*this);
267 NPVariantWrapper hrefVariant(*this);
268
269 // window.location
270 NPN_GetProperty(getNPP(), window, locationID, locationVariant.addressForReturn());
271 //window.location.href
272 NPN_GetProperty(getNPP(), locationVariant.getAsObject(), hrefID, hrefVariant.addressForReturn());
273
274 const NPString* locationHref = NPVariantProxy::getAsNPString(hrefVariant);
275 return convertToString(*locationHref);
276}
277
278
279void ScriptableInstance::loadHostEntries(const NPVariant* args, unsigned argCount, NPVariant* result) {
280 string locationHref = getLocationHref();
281 if (locationHref.compare(BACKGROUND_PAGE_STR) == 0) {
282 AllowedConnections::clearRules();
283 for (unsigned i = 0; i < argCount; ++i) {
284 //Get the host entry object {url: "somehost.net", include: true/false}
285 NPObject* hostEntry = NPVariantProxy::getAsObject(args[i]);
286 if (!hostEntry) {
287 Debug::log(Debug::Error) << "Got a host entry that is not an object.\n";
288 break;
289 }
290
291 //Get the url
292 NPVariantWrapper urlVariant(*this);
293 if (!NPN_GetProperty(getNPP(), hostEntry, urlID, urlVariant.addressForReturn()) ||
294 !urlVariant.isString()) {
295 Debug::log(Debug::Error) << "Got a host.url entry that is not a string.\n";
296 break;
297 }
298 const NPString* urlNPString = urlVariant.getAsNPString();
299 string urlString = convertToString(*urlNPString);
300
301 //Include/Exclude?
302 NPVariantWrapper includeVariant(*this);
303 if (!NPN_GetProperty(getNPP(), hostEntry, includeID, includeVariant.addressForReturn()) ||
304 !includeVariant.isBoolean()) {
305 Debug::log(Debug::Error) << "Got a host.include entry that is not a boolean.\n";
306 break;
307 }
308 bool include = includeVariant.getAsBoolean();
309 Debug::log(Debug::Info) << "Adding " << urlString << "(" << (include ? "include" : "exclude") << ")\n";
codefu@google.com977045b2011-05-31 17:15:12 +0000310
311 int slash = urlString.find( '/' );
312 if( slash == std::string::npos ) {
313 AllowedConnections::addRule(urlString, "localhost", !include);
314 } else {
315 AllowedConnections::addRule(urlString.substr( 0, slash), urlString.substr(slash+1), !include);
316 }
conroy@google.com88069782010-11-23 13:51:12 +0000317 }
318 } else {
319 Debug::log(Debug::Error) << "ScriptableInstance::loadHostEntries called from outside the background page: " <<
320 locationHref << "\n";
321 }
322}
323
324void ScriptableInstance::getHostPermission(const NPVariant* args, unsigned argCount, NPVariant* result) {
325 if (argCount != 1 || !NPVariantProxy::isString(args[0])) {
326 Debug::log(Debug::Error) << "ScriptableInstance::getHostPermission called with incorrect arguments.\n";
327 }
328
329 const NPString url = args[0].value.stringValue;
330 const string urlStr = convertToString(url);
331 bool allowed = false;
conroy@google.com88069782010-11-23 13:51:12 +0000332
codefu@google.com977045b2011-05-31 17:15:12 +0000333 Debug::log(Debug::Info) << "getHostPermission() url " << urlStr << Debug::flush;
334 bool matches = AllowedConnections::matchesRule(
335 AllowedConnections::getHostFromUrl(urlStr),
336 AllowedConnections::getCodeServerFromUrl(urlStr),
337 &allowed);
338 string retStr;
conroy@google.com88069782010-11-23 13:51:12 +0000339 if (!matches) {
340 retStr = UNKNOWN_STR;
341 } else if (allowed) {
342 retStr = INCLUDE_STR;
343 } else {
344 retStr = EXCLUDE_STR;
345 }
346
347 NPVariantProxy(*this, *result) = retStr;
348}
349
gwt.mirrorbot@gmail.comd54a4bd2010-06-07 19:20:31 +0000350void ScriptableInstance::connect(const NPVariant* args, unsigned argCount, NPVariant* result) {
351 if (argCount != 5 || !NPVariantProxy::isString(args[0])
352 || !NPVariantProxy::isString(args[1])
353 || !NPVariantProxy::isString(args[2])
354 || !NPVariantProxy::isString(args[3])
355 || !NPVariantProxy::isString(args[4])) {
356 // TODO: better failure?
357 Debug::log(Debug::Error) << "ScriptableInstance::connect called with incorrect arguments:\n";
358 for (unsigned i = 0; i < argCount; ++i) {
359 Debug::log(Debug::Error) << " " << i << " " << NPVariantProxy::toString(args[i]) << "\n";
360 }
361 Debug::log(Debug::Error) << Debug::flush;
362 result->type = NPVariantType_Void;
363 return;
364 }
conroy@google.com20638f62011-01-25 20:47:44 +0000365
366 // application provided URL string used for user facing things like the
367 // devmode tab title
368 const NPString appUrl = args[0].value.stringValue;
369 const string appUrlStr = convertToString(appUrl);
370
371 // window.location.href provided URL. (used for security)
conroy@google.com88069782010-11-23 13:51:12 +0000372 const string urlStr = getLocationHref();
conroy@google.com20638f62011-01-25 20:47:44 +0000373
gwt.mirrorbot@gmail.comd54a4bd2010-06-07 19:20:31 +0000374 const NPString sessionKey = args[1].value.stringValue;
375 const NPString hostAddr = args[2].value.stringValue;
376 const NPString moduleName = args[3].value.stringValue;
377 const NPString hostedHtmlVersion = args[4].value.stringValue;
378 Debug::log(Debug::Info) << "ScriptableInstance::connect(url=" << NPVariantProxy::toString(args[0])
379 << ",sessionKey=" << NPVariantProxy::toString(args[1]) << ",host=" << NPVariantProxy::toString(args[2])
380 << ",module=" << NPVariantProxy::toString(args[3]) << ",hostedHtmlVers=" << NPVariantProxy::toString(args[4])
381 << ")" << Debug::flush;
gwt.mirrorbot@gmail.comd54a4bd2010-06-07 19:20:31 +0000382
gwt.mirrorbot@gmail.comd54a4bd2010-06-07 19:20:31 +0000383 bool allowed = false;
codefu@google.com977045b2011-05-31 17:15:12 +0000384 AllowedConnections::matchesRule(
385 AllowedConnections::getHostFromUrl(urlStr),
386 AllowedConnections::getCodeServerFromUrl(appUrlStr),
387 &allowed);
gwt.mirrorbot@gmail.comd54a4bd2010-06-07 19:20:31 +0000388 if (!allowed) {
389 BOOLEAN_TO_NPVARIANT(false, *result);
390 result->type = NPVariantType_Bool;
391 return;
392 }
393
394 bool connected = false;
395 unsigned port = 9997; // TODO(jat): should there be a default?
396 int n = GetNPStringUTF8Length(hostAddr);
397 scoped_ptr<char> host(new char[n + 1]);
398 const char* s = GetNPStringUTF8Characters(hostAddr);
399 char* d = host.get();
400 while (n > 0 && *s != ':') {
401 n--;
402 *d++ = *s++;
403 }
404 *d = 0;
405 if (n > 0) {
406 port = atoi(s + 1);
407 }
408 Debug::log(Debug::Info) << " host=" << host.get() << ",port=" << port << Debug::flush;
409
410
411 if (!_channel->connectToHost(host.get(), port)) {
412 BOOLEAN_TO_NPVARIANT(false, *result);
413 result->type = NPVariantType_Bool;
414 }
415
416 string hostedHtmlVersionStr = convertToString(hostedHtmlVersion);
417 if (!_channel->init(this, BROWSERCHANNEL_PROTOCOL_VERSION,
418 BROWSERCHANNEL_PROTOCOL_VERSION, hostedHtmlVersionStr)) {
419 BOOLEAN_TO_NPVARIANT(false, *result);
420 result->type = NPVariantType_Bool;
421 }
422
423 string moduleNameStr = convertToString(moduleName);
424 string userAgent(NPN_UserAgent(getNPP()));
425 string tabKeyStr = computeTabIdentity();
426 string sessionKeyStr = convertToString(sessionKey);
427 Debug::log(Debug::Debugging) << " connected, sending loadModule" << Debug::flush;
conroy@google.com20638f62011-01-25 20:47:44 +0000428 connected = LoadModuleMessage::send(*_channel, appUrlStr, tabKeyStr, sessionKeyStr,
gwt.mirrorbot@gmail.comd54a4bd2010-06-07 19:20:31 +0000429 moduleNameStr, userAgent, this);
430 BOOLEAN_TO_NPVARIANT(connected, *result);
431 result->type = NPVariantType_Bool;
432}
433
434int ScriptableInstance::getLocalObjectRef(NPObject* obj) {
435 NPVariantWrapper wrappedRetVal(*this);
436 int id;
437 if (NPN_GetProperty(getNPP(), obj, gwtId, wrappedRetVal.addressForReturn())
438 && wrappedRetVal.isInt()) {
439 id = wrappedRetVal.getAsInt();
440 localObjects.set(id, obj);
441 } else {
442 id = localObjects.add(obj);
443 wrappedRetVal = id;
444 if (!NPN_SetProperty(getNPP(), obj, gwtId, wrappedRetVal.address())) {
445 Debug::log(Debug::Error) << "Setting GWT id on object failed" << Debug::flush;
446 }
447 }
448 return id;
449}
450
conroy@google.com88069782010-11-23 13:51:12 +0000451void ScriptableInstance::fatalError(HostChannel& channel, const string& message) {
gwt.mirrorbot@gmail.comd54a4bd2010-06-07 19:20:31 +0000452 // TODO(jat): better error handling
453 Debug::log(Debug::Error) << "Fatal error: " << message << Debug::flush;
454}
455
456void ScriptableInstance::dupString(const char* str, NPString& npString) {
457 npString.UTF8Length = static_cast<uint32_t>(strlen(str));
458 NPUTF8* chars = static_cast<NPUTF8*>(NPN_MemAlloc(npString.UTF8Length));
459 memcpy(chars, str, npString.UTF8Length);
460 npString.UTF8Characters = chars;
461}
462
463// SessionHandler methods
464void ScriptableInstance::freeValue(HostChannel& channel, int idCount, const int* const ids) {
465 Debug::log(Debug::Debugging) << "freeValue(#ids=" << idCount << ")" << Debug::flush;
466 for (int i = 0; i < idCount; ++i) {
467 Debug::log(Debug::Spam) << " id=" << ids[i] << Debug::flush;
468 NPObject* obj = localObjects.get(ids[i]);
469 if (!NPN_RemoveProperty(getNPP(), obj, gwtId)) {
470 Debug::log(Debug::Error) << "Unable to remove GWT ID from object " << ids[i] << Debug::flush;
471 } else {
472 localObjects.free(ids[i]);
473 }
474 }
475}
476
conroy@google.com88069782010-11-23 13:51:12 +0000477void ScriptableInstance::loadJsni(HostChannel& channel, const string& js) {
gwt.mirrorbot@gmail.comd54a4bd2010-06-07 19:20:31 +0000478 NPString npScript;
479 dupString(js.c_str(), npScript);
480 NPVariantWrapper npResult(*this);
481 Debug::log(Debug::Spam) << "loadJsni - \n" << js << Debug::flush;
482 if (!NPN_Evaluate(getNPP(), window, &npScript, npResult.addressForReturn())) {
483 Debug::log(Debug::Error) << "loadJsni failed\n" << js << Debug::flush;
484 }
485}
486
487Value ScriptableInstance::clientMethod_getProperty(HostChannel& channel, int numArgs, const Value* const args) {
488 if (numArgs != 2 || !args[0].isInt() || (!args[1].isString() && !args[1].isInt())) {
489 Debug::log(Debug::Error) << "Incorrect invocation of getProperty: #args=" << numArgs << ":";
490 for (int i = 0; i < numArgs; ++i) {
491 Debug::log(Debug::Error) << " " << i << "=" << args[i].toString();
492 }
493 Debug::log(Debug::Error) << Debug::flush;
494 return Value();
495 }
496 int id = args[0].getInt();
497 NPObject* obj = localObjects.get(id);
498 NPIdentifier propID;
499 if (args[1].isString()) {
conroy@google.com88069782010-11-23 13:51:12 +0000500 string propName = args[1].getString();
gwt.mirrorbot@gmail.comd54a4bd2010-06-07 19:20:31 +0000501 propID = NPN_GetStringIdentifier(propName.c_str());
502 } else {
503 int propNum = args[1].getInt();
504 propID = NPN_GetIntIdentifier(propNum);
505 }
506 NPVariantWrapper npResult(*this);
507 if (!NPN_GetProperty(getNPP(), obj, propID, npResult.addressForReturn())) {
508 Debug::log(Debug::Warning) << "getProperty(id=" << id << ", prop="
509 << NPN_UTF8FromIdentifier(propID) << ") failed" << Debug::flush;
510 return Value();
511 }
512 return npResult.getAsValue(*this);
513}
514
515Value ScriptableInstance::clientMethod_setProperty(HostChannel& channel, int numArgs, const Value* const args) {
516 if (numArgs != 2 || !args[0].isInt() || (!args[1].isString() && !args[1].isInt())) {
517 Debug::log(Debug::Error) << "Incorrect invocation of setProperty: #args="
518 << numArgs << ":";
519 for (int i = 0; i < numArgs; ++i) {
520 Debug::log(Debug::Error) << " " << i << "=" << args[i].toString();
521 }
522 Debug::log(Debug::Error) << Debug::flush;
523 return Value();
524 }
525 int id = args[0].getInt();
526 NPObject* obj = localObjects.get(id);
527 NPIdentifier propID;
528 if (args[1].isString()) {
conroy@google.com88069782010-11-23 13:51:12 +0000529 string propName = args[1].getString();
gwt.mirrorbot@gmail.comd54a4bd2010-06-07 19:20:31 +0000530 propID = NPN_GetStringIdentifier(propName.c_str());
531 } else {
532 int propNum = args[1].getInt();
533 propID = NPN_GetIntIdentifier(propNum);
534 }
535 NPVariantWrapper npValue(*this);
536 npValue.operator=(args[2]);
537 if (!NPN_SetProperty(getNPP(), obj, propID, npValue.address())) {
538 Debug::log(Debug::Warning) << "setProperty(id=" << id << ", prop="
539 << NPN_UTF8FromIdentifier(propID) << ", val=" << args[2].toString()
540 << ") failed" << Debug::flush;
541 return Value();
542 }
543 return Value();
544}
545
546/**
547 * SessionHandler.invoke - used by LoadModule and reactToMessages* to process server-side
548 * requests to invoke methods in Javascript or the plugin.
549 */
550bool ScriptableInstance::invokeSpecial(HostChannel& channel, SpecialMethodId dispatchId,
551 int numArgs, const Value* const args, Value* returnValue) {
552 switch (dispatchId) {
553 case SessionHandler::HasMethod:
554 case SessionHandler::HasProperty:
555 break;
556 case SessionHandler::SetProperty:
557 *returnValue = clientMethod_setProperty(channel, numArgs, args);
558 return false;
559 case SessionHandler::GetProperty:
560 *returnValue = clientMethod_getProperty(channel, numArgs, args);
561 return false;
562 default:
563 break;
564 }
565 Debug::log(Debug::Error) << "Unexpected special method " << dispatchId
566 << " called on plugin; #args=" << numArgs << ":";
567 for (int i = 0; i < numArgs; ++i) {
568 Debug::log(Debug::Error) << " " << i << "=" << args[i].toString();
569 }
570 Debug::log(Debug::Error) << Debug::flush;
571 // TODO(jat): should we create a real exception object?
conroy@google.com88069782010-11-23 13:51:12 +0000572 string buf("unexpected invokeSpecial(");
gwt.mirrorbot@gmail.comd54a4bd2010-06-07 19:20:31 +0000573 buf += static_cast<int>(dispatchId);
574 buf += ")";
575 returnValue->setString(buf);
576 return true;
577}
578
579bool ScriptableInstance::invoke(HostChannel& channel, const Value& thisRef,
conroy@google.com88069782010-11-23 13:51:12 +0000580 const string& methodName, int numArgs, const Value* const args,
gwt.mirrorbot@gmail.comd54a4bd2010-06-07 19:20:31 +0000581 Value* returnValue) {
582 Debug::log(Debug::Debugging) << "invokeJS(" << methodName << ", this="
583 << thisRef.toString() << ", numArgs=" << numArgs << ")" << Debug::flush;
584 NPVariantArray varArgs(*this, numArgs + 2);
585 varArgs[0] = thisRef;
586 varArgs[1] = methodName;
587 for (int i = 0; i < numArgs; ++i) {
588 varArgs[i + 2] = args[i];
589 }
590 const NPVariant* argArray = varArgs.getArray();
591 if (Debug::level(Debug::Spam)) {
592 for (int i = 0; i < varArgs.getSize(); ++i) {
593 Debug::log(Debug::Spam) << " arg " << i << " is "
594 << NPVariantProxy::toString(argArray[i]) << Debug::flush;
595 }
596 }
597 NPVariantWrapper wrappedRetVal(*this);
598 if (!NPN_Invoke(getNPP(), window, jsInvokeID, argArray, varArgs.getSize(),
599 wrappedRetVal.addressForReturn())) {
600 Debug::log(Debug::Error) << "*** invokeJS(" << methodName << ", this="
601 << thisRef.toString() << ", numArgs=" << numArgs << ") failed"
602 << Debug::flush;
603 // TODO(jat): should we create a real exception object?
604 returnValue->setString("invoke of " + methodName + " failed");
605 return true;
606 }
607 Debug::log(Debug::Spam) << " wrapped return is " << wrappedRetVal.toString() << Debug::flush;
608 NPVariantWrapper exceptFlag(*this);
609 NPVariantWrapper retval(*this);
610 NPObject* wrappedArray = wrappedRetVal.getAsObject();
611 if (!NPN_GetProperty(getNPP(), wrappedArray, idx0, exceptFlag.addressForReturn())) {
612 Debug::log(Debug::Error) << " Error getting element 0 of wrapped return value ("
613 << wrappedRetVal << ") on call to " << methodName << Debug::flush;
614 }
615 if (!NPN_GetProperty(getNPP(), wrappedArray, idx1, retval.addressForReturn())) {
616 Debug::log(Debug::Error) << " Error getting element 1 of wrapped return value ("
617 << wrappedRetVal << ") on call to " << methodName << Debug::flush;
618 }
619 Debug::log(Debug::Debugging) << " return value " << retval.toString() << Debug::flush;
620 *returnValue = retval.getAsValue(*this);
621 if (exceptFlag.isInt() && exceptFlag.getAsInt() != 0) {
622 Debug::log(Debug::Debugging) << " exception: " << retval << Debug::flush;
623 return true;
624 }
625 return false;
626}
627
628bool ScriptableInstance::JavaObject_invoke(int objectId, int dispId,
629 const NPVariant* args, uint32_t numArgs, NPVariant* result) {
630 Debug::log(Debug::Debugging) << "JavaObject_invoke(dispId= " << dispId << ", numArgs=" << numArgs << ")" << Debug::flush;
631 if (Debug::level(Debug::Spam)) {
632 for (uint32_t i = 0; i < numArgs; ++i) {
633 Debug::log(Debug::Spam) << " " << i << " = " << args[i] << Debug::flush;
634 }
635 }
636
637 bool isRawToString = false;
638 if (dispId == -1) {
639 dispId = 0;
640 isRawToString = true;
641 }
642
643 Value javaThis;
644 javaThis.setJavaObject(objectId);
645 scoped_array<Value> vargs(new Value[numArgs]);
646 for (unsigned i = 0; i < numArgs; ++i) {
647 vargs[i] = NPVariantProxy::getAsValue(args[i], *this);
648 }
649 bool isException = false;
650 Value returnValue;
651 if (!InvokeMessage::send(*_channel, javaThis, dispId, numArgs, vargs.get())) {
652 Debug::log(Debug::Error) << "JavaObject_invoke: failed to send invoke message" << Debug::flush;
653 } else {
654 Debug::log(Debug::Debugging) << " return from invoke" << Debug::flush;
655 scoped_ptr<ReturnMessage> retMsg(_channel->reactToMessagesWhileWaitingForReturn(this));
656 if (!retMsg.get()) {
657 Debug::log(Debug::Error) << "JavaObject_invoke: failed to get return value" << Debug::flush;
658 } else {
659 if (isRawToString) {
660 // toString() needs the raw value
661 NPVariantProxy::assignFrom(*this, *result, retMsg->getReturnValue());
662 return !retMsg->isException();
663 }
664 isException = retMsg->isException();
665 returnValue = retMsg->getReturnValue();
666 }
667 }
668 // Wrap the result
669 return makeResult(isException, returnValue, result);
670}
671
672bool ScriptableInstance::JavaObject_getProperty(int objectId, int dispId,
673 NPVariant* result) {
674 Debug::log(Debug::Debugging) << "JavaObject_getProperty(objectid="
675 << objectId << ", dispId=" << dispId << ")" << Debug::flush;
676 VOID_TO_NPVARIANT(*result);
677 Value propertyValue = ServerMethods::getProperty(*_channel, this, objectId, dispId);
678 if (propertyValue.isJsObject()) {
679 // TODO(jat): special-case for testing
680 NPObject* npObj = localObjects.get(propertyValue.getJsObjectId());
681 OBJECT_TO_NPVARIANT(npObj, *result);
682 NPN_RetainObject(npObj);
683 } else {
684 NPVariantProxy::assignFrom(*this, *result, propertyValue);
685 }
686 Debug::log(Debug::Debugging) << " return val=" << propertyValue
687 << ", NPVariant=" << *result << Debug::flush;
688 if (NPVariantProxy::isObject(*result)) {
689 dumpObjectBytes(NPVariantProxy::getAsObject(*result));
690 }
691 if (NPVariantProxy::isObject(*result)) {
692 Debug::log(Debug::Debugging) << " final return refcount = "
693 << NPVariantProxy::getAsObject(*result)->referenceCount << Debug::flush;
694 }
695 return true;
696}
697
698bool ScriptableInstance::JavaObject_setProperty(int objectId, int dispId,
699 const NPVariant* npValue) {
700 Debug::log(Debug::Debugging) << "JavaObject_setProperty(objectid="
701 << objectId << ", dispId=" << dispId << ", value=" << *npValue << ")" << Debug::flush;
702 if (NPVariantProxy::isObject(*npValue)) {
703 Debug::log(Debug::Debugging) << " before localObj: refcount = "
704 << NPVariantProxy::getAsObject(*npValue)->referenceCount << Debug::flush;
705 }
706 Value value = NPVariantProxy::getAsValue(*npValue, *this, true);
707 if (NPVariantProxy::isObject(*npValue)) {
708 Debug::log(Debug::Debugging) << " after localObj: refcount = "
709 << NPVariantProxy::getAsObject(*npValue)->referenceCount << Debug::flush;
710 }
711 if (NPVariantProxy::isObject(*npValue)) {
712 dumpObjectBytes(NPVariantProxy::getAsObject(*npValue));
713 }
714 Debug::log(Debug::Debugging) << " as value: " << value << Debug::flush;
715 // TODO: no way to set an actual exception object! (Could ClassCastException on server.)
716 return ServerMethods::setProperty(*_channel, this, objectId, dispId, value);
717}
718
719bool ScriptableInstance::JavaObject_getToStringTearOff(NPVariant* result) {
720 Debug::log(Debug::Debugging) << "JavaObject_getToStringTearOff()" << Debug::flush;
721 VOID_TO_NPVARIANT(*result);
722
723 Value temp;
724 NPVariantArray varArgs(*this, 3);
725 temp.setNull(); varArgs[0] = temp; // proxy: no proxy needed
726 temp.setInt(0); varArgs[1] = temp; // dispId: always 0 for toString()
727 temp.setInt(0); varArgs[2] = temp; // argCount: always 0 for toString()
728
729 if (!NPN_Invoke(getNPP(), window, jsTearOffID, varArgs.getArray(), 3, result)) {
730 Debug::log(Debug::Error) << "*** JavaObject_getToStringTearOff() failed"
731 << Debug::flush;
732 return true;
733 }
734 return true;
735}
736
737JavaObject* ScriptableInstance::createJavaWrapper(int objectId) {
738 Debug::log(Debug::Debugging) << "createJavaWrapper(objectId=" << objectId << ")" << Debug::flush;
739 JavaObject* jObj;
740 hash_map<int, JavaObject*>::iterator it = javaObjects.find(objectId);
741 if (it != javaObjects.end()) {
742 jObj = it->second;
743 NPN_RetainObject(jObj);
744 return jObj;
745 }
746 jObj = JavaObject::create(this, objectId);
747 javaObjects[objectId] = jObj;
748 return jObj;
749}
750
751void ScriptableInstance::destroyJavaWrapper(JavaObject* jObj) {
752 int objectId = jObj->getObjectId();
753 if (!javaObjects.erase(objectId)) {
754 Debug::log(Debug::Error) << "destroyJavaWrapper(id=" << objectId
755 << "): trying to free unknown JavaObject" << Debug::flush;
756 }
757 Debug::log(Debug::Debugging) << "destroyJavaWrapper(id=" << objectId << ")" << Debug::flush;
758 javaObjectsToFree.insert(objectId);
759}
760
761void ScriptableInstance::disconnectDetectedImpl() {
762 NPVariantWrapper result(*this);
763 NPN_Invoke(getNPP(), window, jsDisconnectedID, 0, 0, result.addressForReturn());
764}
765
766void ScriptableInstance::sendFreeValues(HostChannel& channel) {
767 unsigned n = javaObjectsToFree.size();
768 if (n) {
769 scoped_array<int> ids(new int[n]);
770 int i = 0;
771 for (std::set<int>::iterator it = javaObjectsToFree.begin();
772 it != javaObjectsToFree.end(); ++it) {
773 ids[i++] = *it;
774 }
775 if (ServerMethods::freeJava(channel, this, n, ids.get())) {
776 javaObjectsToFree.clear();
777 }
778 }
779}