gwt.mirrorbot@gmail.com | d54a4bd | 2010-06-07 19:20:31 +0000 | [diff] [blame] | 1 | /* |
| 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.com | d54a4bd | 2010-06-07 19:20:31 +0000 | [diff] [blame] | 24 | |
| 25 | #include "mozincludes.h" |
| 26 | #include "scoped_ptr/scoped_ptr.h" |
| 27 | #include "NPVariantWrapper.h" |
| 28 | |
| 29 | using std::string; |
| 30 | using std::endl; |
conroy@google.com | 8806978 | 2010-11-23 13:51:12 +0000 | [diff] [blame] | 31 | const static string BACKGROUND_PAGE_STR = "chrome-extension://jpjpnpmbddbjkfaccnmhnkdgjideieim/background.html"; |
| 32 | const static string UNKNOWN_STR = "unknown"; |
| 33 | const static string INCLUDE_STR = "include"; |
| 34 | const static string EXCLUDE_STR = "exclude"; |
gwt.mirrorbot@gmail.com | d54a4bd | 2010-06-07 19:20:31 +0000 | [diff] [blame] | 35 | |
| 36 | static inline string convertToString(const NPString& str) { |
| 37 | return string(GetNPStringUTF8Characters(str), GetNPStringUTF8Length(str)); |
| 38 | } |
| 39 | |
| 40 | string ScriptableInstance::computeTabIdentity() { |
| 41 | return ""; |
| 42 | } |
| 43 | |
| 44 | void 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 | |
| 58 | ScriptableInstance::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.com | 8806978 | 2010-11-23 13:51:12 +0000 | [diff] [blame] | 65 | 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.com | d54a4bd | 2010-06-07 19:20:31 +0000 | [diff] [blame] | 71 | 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 | |
| 87 | ScriptableInstance::~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 | |
| 104 | void 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 | |
| 115 | bool 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 | |
| 126 | bool 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 | |
| 147 | bool 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 | |
| 157 | bool 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 | |
| 177 | bool 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 | |
| 184 | bool ScriptableInstance::hasMethod(NPIdentifier name) { |
| 185 | Debug::log(Debug::Debugging) << "ScriptableInstance::hasMethod(name=" << NPN_UTF8FromIdentifier(name) << ")" |
| 186 | << Debug::flush; |
conroy@google.com | 8806978 | 2010-11-23 13:51:12 +0000 | [diff] [blame] | 187 | if (name == _connectId || |
| 188 | name == initID || |
| 189 | name == toStringID || |
| 190 | name == loadHostEntriesID || |
| 191 | name == getHostPermissionID) { |
gwt.mirrorbot@gmail.com | d54a4bd | 2010-06-07 19:20:31 +0000 | [diff] [blame] | 192 | return true; |
| 193 | } |
| 194 | return false; |
| 195 | } |
| 196 | |
| 197 | bool 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.com | 8806978 | 2010-11-23 13:51:12 +0000 | [diff] [blame] | 213 | } else if (name == loadHostEntriesID) { |
| 214 | loadHostEntries(args, argCount, result); |
| 215 | } else if (name == getHostPermissionID) { |
| 216 | getHostPermission(args, argCount, result); |
gwt.mirrorbot@gmail.com | d54a4bd | 2010-06-07 19:20:31 +0000 | [diff] [blame] | 217 | } |
| 218 | return true; |
| 219 | } |
| 220 | |
| 221 | bool 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 | |
| 229 | bool 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 | |
| 244 | void 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.com | 8806978 | 2010-11-23 13:51:12 +0000 | [diff] [blame] | 265 | string 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 | |
| 279 | void 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.com | 977045b | 2011-05-31 17:15:12 +0000 | [diff] [blame^] | 310 | |
| 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.com | 8806978 | 2010-11-23 13:51:12 +0000 | [diff] [blame] | 317 | } |
| 318 | } else { |
| 319 | Debug::log(Debug::Error) << "ScriptableInstance::loadHostEntries called from outside the background page: " << |
| 320 | locationHref << "\n"; |
| 321 | } |
| 322 | } |
| 323 | |
| 324 | void 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.com | 8806978 | 2010-11-23 13:51:12 +0000 | [diff] [blame] | 332 | |
codefu@google.com | 977045b | 2011-05-31 17:15:12 +0000 | [diff] [blame^] | 333 | 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.com | 8806978 | 2010-11-23 13:51:12 +0000 | [diff] [blame] | 339 | 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.com | d54a4bd | 2010-06-07 19:20:31 +0000 | [diff] [blame] | 350 | void 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.com | 20638f6 | 2011-01-25 20:47:44 +0000 | [diff] [blame] | 365 | |
| 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.com | 8806978 | 2010-11-23 13:51:12 +0000 | [diff] [blame] | 372 | const string urlStr = getLocationHref(); |
conroy@google.com | 20638f6 | 2011-01-25 20:47:44 +0000 | [diff] [blame] | 373 | |
gwt.mirrorbot@gmail.com | d54a4bd | 2010-06-07 19:20:31 +0000 | [diff] [blame] | 374 | 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.com | d54a4bd | 2010-06-07 19:20:31 +0000 | [diff] [blame] | 382 | |
gwt.mirrorbot@gmail.com | d54a4bd | 2010-06-07 19:20:31 +0000 | [diff] [blame] | 383 | bool allowed = false; |
codefu@google.com | 977045b | 2011-05-31 17:15:12 +0000 | [diff] [blame^] | 384 | AllowedConnections::matchesRule( |
| 385 | AllowedConnections::getHostFromUrl(urlStr), |
| 386 | AllowedConnections::getCodeServerFromUrl(appUrlStr), |
| 387 | &allowed); |
gwt.mirrorbot@gmail.com | d54a4bd | 2010-06-07 19:20:31 +0000 | [diff] [blame] | 388 | 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.com | 20638f6 | 2011-01-25 20:47:44 +0000 | [diff] [blame] | 428 | connected = LoadModuleMessage::send(*_channel, appUrlStr, tabKeyStr, sessionKeyStr, |
gwt.mirrorbot@gmail.com | d54a4bd | 2010-06-07 19:20:31 +0000 | [diff] [blame] | 429 | moduleNameStr, userAgent, this); |
| 430 | BOOLEAN_TO_NPVARIANT(connected, *result); |
| 431 | result->type = NPVariantType_Bool; |
| 432 | } |
| 433 | |
| 434 | int 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.com | 8806978 | 2010-11-23 13:51:12 +0000 | [diff] [blame] | 451 | void ScriptableInstance::fatalError(HostChannel& channel, const string& message) { |
gwt.mirrorbot@gmail.com | d54a4bd | 2010-06-07 19:20:31 +0000 | [diff] [blame] | 452 | // TODO(jat): better error handling |
| 453 | Debug::log(Debug::Error) << "Fatal error: " << message << Debug::flush; |
| 454 | } |
| 455 | |
| 456 | void 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 |
| 464 | void 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.com | 8806978 | 2010-11-23 13:51:12 +0000 | [diff] [blame] | 477 | void ScriptableInstance::loadJsni(HostChannel& channel, const string& js) { |
gwt.mirrorbot@gmail.com | d54a4bd | 2010-06-07 19:20:31 +0000 | [diff] [blame] | 478 | 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 | |
| 487 | Value 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.com | 8806978 | 2010-11-23 13:51:12 +0000 | [diff] [blame] | 500 | string propName = args[1].getString(); |
gwt.mirrorbot@gmail.com | d54a4bd | 2010-06-07 19:20:31 +0000 | [diff] [blame] | 501 | 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 | |
| 515 | Value 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.com | 8806978 | 2010-11-23 13:51:12 +0000 | [diff] [blame] | 529 | string propName = args[1].getString(); |
gwt.mirrorbot@gmail.com | d54a4bd | 2010-06-07 19:20:31 +0000 | [diff] [blame] | 530 | 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 | */ |
| 550 | bool 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.com | 8806978 | 2010-11-23 13:51:12 +0000 | [diff] [blame] | 572 | string buf("unexpected invokeSpecial("); |
gwt.mirrorbot@gmail.com | d54a4bd | 2010-06-07 19:20:31 +0000 | [diff] [blame] | 573 | buf += static_cast<int>(dispatchId); |
| 574 | buf += ")"; |
| 575 | returnValue->setString(buf); |
| 576 | return true; |
| 577 | } |
| 578 | |
| 579 | bool ScriptableInstance::invoke(HostChannel& channel, const Value& thisRef, |
conroy@google.com | 8806978 | 2010-11-23 13:51:12 +0000 | [diff] [blame] | 580 | const string& methodName, int numArgs, const Value* const args, |
gwt.mirrorbot@gmail.com | d54a4bd | 2010-06-07 19:20:31 +0000 | [diff] [blame] | 581 | 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 | |
| 628 | bool 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 | |
| 672 | bool 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 | |
| 698 | bool 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 | |
| 719 | bool 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 | |
| 737 | JavaObject* 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 | |
| 751 | void 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 | |
| 761 | void ScriptableInstance::disconnectDetectedImpl() { |
| 762 | NPVariantWrapper result(*this); |
| 763 | NPN_Invoke(getNPP(), window, jsDisconnectedID, 0, 0, result.addressForReturn()); |
| 764 | } |
| 765 | |
| 766 | void 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 | } |