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