| /* | 
 |  * Copyright 2008 Google Inc. | 
 |  *  | 
 |  * Licensed under the Apache License, Version 2.0 (the "License"); you may not | 
 |  * use this file except in compliance with the License. You may obtain a copy of | 
 |  * the License at | 
 |  *  | 
 |  * http://www.apache.org/licenses/LICENSE-2.0 | 
 |  *  | 
 |  * Unless required by applicable law or agreed to in writing, software | 
 |  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | 
 |  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | 
 |  * License for the specific language governing permissions and limitations under | 
 |  * the License. | 
 |  */ | 
 |  | 
 | #include <cstring> | 
 |  | 
 | #include "ScriptableInstance.h" | 
 | #include "InvokeMessage.h" | 
 | #include "ReturnMessage.h" | 
 | #include "ServerMethods.h" | 
 | #include "AllowedConnections.h" | 
 | #include "mozincludes.h" | 
 | #include "scoped_ptr/scoped_ptr.h" | 
 | #include "NPVariantWrapper.h" | 
 |  | 
 | using std::string; | 
 | using std::endl; | 
 |  | 
 | static inline string convertToString(const NPString& str) { | 
 |   return string(GetNPStringUTF8Characters(str), GetNPStringUTF8Length(str)); | 
 | } | 
 |  | 
 | static bool askUserToAllow(const std::string& url, bool* remember) { | 
 |   // TODO(jat): implement, for now allow anything but don't remember | 
 |   *remember = false; | 
 |   return true; | 
 | } | 
 |  | 
 | string ScriptableInstance::computeTabIdentity() { | 
 |   return ""; | 
 | } | 
 |  | 
 | void ScriptableInstance::dumpObjectBytes(NPObject* obj) { | 
 |   char buf[20]; | 
 |   Debug::log(Debug::Debugging) << "   object bytes:\n"; | 
 |   const unsigned char* ptr = reinterpret_cast<const unsigned char*>(obj); | 
 |   for (int i = 0; i < 24; ++i) { | 
 |     snprintf(buf, sizeof(buf), " %02x", ptr[i]); | 
 |     Debug::log(Debug::Debugging) << buf; | 
 |   } | 
 |   NPVariant objVar; | 
 |   OBJECT_TO_NPVARIANT(obj, objVar); | 
 |   Debug::log(Debug::Debugging) << " obj.toString()=" | 
 |       << NPVariantProxy::toString(objVar) << Debug::flush; | 
 | } | 
 |  | 
 | ScriptableInstance::ScriptableInstance(NPP npp) : NPObjectWrapper<ScriptableInstance>(npp), | 
 |     plugin(*reinterpret_cast<Plugin*>(npp->pdata)), | 
 |     _channel(new HostChannel()), | 
 |     localObjects(), | 
 |     _connectId(NPN_GetStringIdentifier("connect")), | 
 |     initID(NPN_GetStringIdentifier("init")), | 
 |     toStringID(NPN_GetStringIdentifier("toString")), | 
 |     connectedID(NPN_GetStringIdentifier("connected")), | 
 |     statsID(NPN_GetStringIdentifier("stats")), | 
 |     savedID(NPN_GetStringIdentifier("saved")), | 
 |     jsInvokeID(NPN_GetStringIdentifier("__gwt_jsInvoke")), | 
 |     jsResultID(NPN_GetStringIdentifier("__gwt_makeResult")), | 
 |     jsTearOffID(NPN_GetStringIdentifier("__gwt_makeTearOff")), | 
 |     jsValueOfID(NPN_GetStringIdentifier("valueOf")), | 
 |     idx0(NPN_GetIntIdentifier(0)), | 
 |     idx1(NPN_GetIntIdentifier(1)) { | 
 |   savedValueIdx = -1; | 
 |   if (NPN_GetValue(npp, NPNVWindowNPObject, &window) != NPERR_NO_ERROR) { | 
 |     Debug::log(Debug::Error) << "Error getting window object" << Debug::flush; | 
 |   } | 
 | } | 
 |  | 
 | ScriptableInstance::~ScriptableInstance() { | 
 |   // TODO(jat): free any remaining Java objects held by JS, then make | 
 |   // the JS wrapper handle that situation gracefully | 
 |   if (window) { | 
 |     NPN_ReleaseObject(window); | 
 |   } | 
 |   for (hash_map<int, JavaObject*>::iterator it = javaObjects.begin(); it != javaObjects.end(); | 
 |       ++it) { | 
 |     Debug::log(Debug::Spam) << "  disconnecting Java wrapper " << it->first << Debug::flush; | 
 |     it->second->disconnectPlugin(); | 
 |   } | 
 |   if (_channel) { | 
 |     _channel->disconnectFromHost(); | 
 |     delete _channel; | 
 |   } | 
 | } | 
 |  | 
 | void ScriptableInstance::dumpJSresult(const char* js) { | 
 |   NPString npScript; | 
 |   dupString(js, npScript); | 
 |   NPVariantWrapper wrappedRetVal(*this); | 
 |   if (!NPN_Evaluate(getNPP(), window, &npScript, wrappedRetVal.addressForReturn())) { | 
 |     Debug::log(Debug::Error) << "   *** dumpJSresult failed" << Debug::flush; | 
 |     return; | 
 |   } | 
 |   Debug::log(Debug::Info) << "dumpWindow=" << wrappedRetVal.toString() << Debug::flush; | 
 | } | 
 |  | 
 | bool ScriptableInstance::tryGetStringPrimitive(NPObject* obj, NPVariant& result) { | 
 |   if (NPN_HasMethod(getNPP(), obj, jsValueOfID)) { | 
 |     if (NPN_Invoke(getNPP(), obj, jsValueOfID, 0, 0, &result) | 
 |         && NPVariantProxy::isString(result)) { | 
 |       return true; | 
 |     } | 
 |     NPVariantProxy::release(result); | 
 |   } | 
 |   return false; | 
 | } | 
 |  | 
 | bool ScriptableInstance::makeResult(bool isException, const Value& value, NPVariant* result) { | 
 |   Debug::log(Debug::Debugging) << "makeResult(" << isException << ", " << value << ")" | 
 |       << Debug::flush; | 
 |   Value temp; | 
 |   if (value.getType() == Value::JAVA_OBJECT) { | 
 |     int javaId = value.getJavaObjectId(); | 
 |     // We may have previously released the proxy for the same object id, | 
 |     // but have not yet sent a free message back to the server. | 
 |     javaObjectsToFree.erase(javaId); | 
 |   } | 
 |   NPVariantArray varArgs(*this, 3); | 
 |   varArgs[0] = isException ? 1 : 0; | 
 |   varArgs[1] = value; | 
 |   NPVariantWrapper retVal(*this); | 
 |   return NPN_Invoke(getNPP(), window, jsResultID, varArgs.getArray(), varArgs.getSize(), result); | 
 | } | 
 |  | 
 | //===================================================================================== | 
 | // NPObject methods | 
 | //===================================================================================== | 
 |  | 
 | bool ScriptableInstance::hasProperty(NPIdentifier name) { | 
 |   if (!NPN_IdentifierIsString(name)) { | 
 |     // all numeric properties are ok, as we assume only JSNI code is making | 
 |     // the field access via dispatchID | 
 |     return true; | 
 |   } | 
 |   // TODO: special-case toString tear-offs | 
 |   return name == statsID || name == connectedID || name == savedID; | 
 | } | 
 |  | 
 | bool ScriptableInstance::getProperty(NPIdentifier name, NPVariant* variant) { | 
 |   Debug::log(Debug::Debugging) << "ScriptableInstance::getProperty(name=" | 
 |       << NPN_UTF8FromIdentifier(name) << ")" << Debug::flush; | 
 |   bool retVal = false; | 
 |   VOID_TO_NPVARIANT(*variant); | 
 |   if (name == connectedID) { | 
 |     BOOLEAN_TO_NPVARIANT(_channel->isConnected(), *variant); | 
 |     retVal = true; | 
 |   } else if (name == statsID) { | 
 |     NPVariantProxy::assignFrom(*variant, "<stats data>"); | 
 |     retVal = true; | 
 |   } else if (name == savedID) { | 
 |     if (savedValueIdx >= 0) { | 
 |       NPObject* npObj = localObjects.get(savedValueIdx); | 
 |       OBJECT_TO_NPVARIANT(npObj, *variant); | 
 |       NPN_RetainObject(npObj); | 
 |     } | 
 |     retVal = true; | 
 |   } | 
 |   if (retVal) { | 
 |     // TODO: testing | 
 |     Debug::log(Debug::Debugging) << " return value " << *variant | 
 |         << Debug::flush; | 
 |   } | 
 |   return retVal; | 
 | } | 
 |  | 
 | bool ScriptableInstance::setProperty(NPIdentifier name, const NPVariant* variant) { | 
 |   Debug::log(Debug::Debugging) << "ScriptableInstance::setProperty(name=" | 
 |       << NPN_UTF8FromIdentifier(name) << ", value=" << *variant << ")" | 
 |       << Debug::flush;  | 
 |   if (NPN_IdentifierIsString(name)) { | 
 |     if (name == savedID && NPVariantProxy::isObject(*variant)) { | 
 |       Debug::log(Debug::Debugging) << " set saved value" << Debug::flush; | 
 |       savedValueIdx = localObjects.add(NPVariantProxy::getAsObject(*variant)); | 
 |       return true; | 
 |     } | 
 |     return false; | 
 |   } | 
 |   return false; | 
 | } | 
 |  | 
 | bool ScriptableInstance::hasMethod(NPIdentifier name) { | 
 |   Debug::log(Debug::Debugging) << "ScriptableInstance::hasMethod(name=" << NPN_UTF8FromIdentifier(name) << ")" | 
 |       << Debug::flush;  | 
 |   if (name == _connectId || name == initID || name == toStringID) { | 
 |     return true; | 
 |   } | 
 |   return false; | 
 | } | 
 |  | 
 | bool ScriptableInstance::invoke(NPIdentifier name, const NPVariant* args, unsigned argCount, | 
 |     NPVariant* result) { | 
 |   NPUTF8* uname = NPN_UTF8FromIdentifier(name); | 
 |   Debug::log(Debug::Debugging) << "ScriptableInstance::invoke(name=" << uname << ",#args=" << argCount << ")" | 
 |       << Debug::flush; | 
 |   VOID_TO_NPVARIANT(*result); | 
 |   if (name == _connectId) { | 
 |     connect(args, argCount, result); | 
 |   } else if (name == initID) { | 
 |     init(args, argCount, result); | 
 |   } else if (name == toStringID) { | 
 |     // TODO(jat): figure out why this doesn't show in Firebug | 
 |     string val("[GWT OOPHM Plugin: connected="); | 
 |     val += _channel->isConnected() ? 'Y' : 'N'; | 
 |     val += ']'; | 
 |     NPVariantProxy::assignFrom(*result, val); | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | bool ScriptableInstance::invokeDefault(const NPVariant* args, unsigned argCount, | 
 |       NPVariant* result) { | 
 |   Debug::log(Debug::Debugging) << "ScriptableInstance::invokeDefault(#args=" << argCount << ")" | 
 |       << Debug::flush; | 
 |   VOID_TO_NPVARIANT(*result); | 
 |   return true; | 
 | } | 
 |  | 
 | bool ScriptableInstance::enumeration(NPIdentifier** propReturn, uint32_t* count) { | 
 |   Debug::log(Debug::Debugging) << "ScriptableInstance::enumeration()" << Debug::flush; | 
 |   int n = 2; | 
 |   NPIdentifier* props = static_cast<NPIdentifier*>(NPN_MemAlloc(sizeof(NPIdentifier) * n)); | 
 |   props[0] = connectedID; | 
 |   props[1] = statsID; | 
 |   *propReturn = props; | 
 |   *count = n; | 
 |   return true; | 
 | } | 
 |  | 
 | //===================================================================================== | 
 | // internal methods | 
 | //===================================================================================== | 
 |  | 
 | void ScriptableInstance::init(const NPVariant* args, unsigned argCount, NPVariant* result) { | 
 |   if (argCount != 1 || !NPVariantProxy::isObject(args[0])) { | 
 |     // TODO: better failure? | 
 |     Debug::log(Debug::Error) << "ScriptableInstance::init called with incorrect arguments:\n"; | 
 |     for (unsigned i = 0; i < argCount; ++i) { | 
 |       Debug::log(Debug::Error) << " " << i << " " << NPVariantProxy::toString(args[i]) << "\n"; | 
 |     } | 
 |     Debug::log(Debug::Error) << Debug::flush; | 
 |     result->type = NPVariantType_Void; | 
 |     return; | 
 |   } | 
 |   if (window) { | 
 |     NPN_ReleaseObject(window); | 
 |   } | 
 |   // replace our window object with that passed by the caller | 
 |   window = NPVariantProxy::getAsObject(args[0]); | 
 |   NPN_RetainObject(window); | 
 |   BOOLEAN_TO_NPVARIANT(true, *result); | 
 |   result->type = NPVariantType_Bool; | 
 | } | 
 |  | 
 | void ScriptableInstance::connect(const NPVariant* args, unsigned argCount, NPVariant* result) { | 
 |   if (argCount != 5 || !NPVariantProxy::isString(args[0]) | 
 |       || !NPVariantProxy::isString(args[1]) | 
 |       || !NPVariantProxy::isString(args[2]) | 
 |       || !NPVariantProxy::isString(args[3]) | 
 |       || !NPVariantProxy::isString(args[4])) { | 
 |     // TODO: better failure? | 
 |     Debug::log(Debug::Error) << "ScriptableInstance::connect called with incorrect arguments:\n"; | 
 |     for (unsigned i = 0; i < argCount; ++i) { | 
 |       Debug::log(Debug::Error) << " " << i << " " << NPVariantProxy::toString(args[i]) << "\n"; | 
 |     } | 
 |     Debug::log(Debug::Error) << Debug::flush; | 
 |     result->type = NPVariantType_Void; | 
 |     return; | 
 |   } | 
 |   const NPString url = args[0].value.stringValue; | 
 |   const NPString sessionKey = args[1].value.stringValue; | 
 |   const NPString hostAddr = args[2].value.stringValue; | 
 |   const NPString moduleName = args[3].value.stringValue; | 
 |   const NPString hostedHtmlVersion = args[4].value.stringValue; | 
 |   Debug::log(Debug::Info) << "ScriptableInstance::connect(url=" << NPVariantProxy::toString(args[0]) | 
 |       << ",sessionKey=" << NPVariantProxy::toString(args[1]) << ",host=" << NPVariantProxy::toString(args[2]) | 
 |       << ",module=" << NPVariantProxy::toString(args[3]) << ",hostedHtmlVers=" << NPVariantProxy::toString(args[4]) | 
 |       << ")" << Debug::flush; | 
 |   const std::string urlStr = convertToString(url); | 
 |   bool allowed = false;
 | 
 |   // TODO(jat): load access list
 | 
 |   if (!AllowedConnections::matchesRule(urlStr, &allowed)) {
 | 
 |     // If we didn't match an existing rule, prompt the user
 | 
 |     bool remember = false;
 | 
 |     allowed = askUserToAllow(urlStr, &remember);
 | 
 |     if (remember) {
 | 
 |       // TODO(jat): update access list
 | 
 |     }
 | 
 |   }
 | 
 |   if (!allowed) {
 | 
 |     BOOLEAN_TO_NPVARIANT(false, *result); | 
 |     result->type = NPVariantType_Bool; | 
 |     return;
 | 
 |   }
 | 
 |  | 
 |   bool connected = false; | 
 |   unsigned port = 9997;  // TODO(jat): should there be a default? | 
 |   int n = GetNPStringUTF8Length(hostAddr); | 
 |   scoped_ptr<char> host(new char[n + 1]); | 
 |   const char* s = GetNPStringUTF8Characters(hostAddr); | 
 |   char* d = host.get(); | 
 |   while (n > 0 && *s != ':') { | 
 |     n--; | 
 |     *d++ = *s++; | 
 |   } | 
 |   *d = 0; | 
 |   if (n > 0) { | 
 |     port = atoi(s + 1); | 
 |   } | 
 |   Debug::log(Debug::Info) << "  host=" << host.get() << ",port=" << port << Debug::flush; | 
 |  | 
 |  | 
 |   if (!_channel->connectToHost(host.get(), port)) { | 
 |     BOOLEAN_TO_NPVARIANT(false, *result); | 
 |     result->type = NPVariantType_Bool; | 
 |   } | 
 |  | 
 |   string hostedHtmlVersionStr = convertToString(hostedHtmlVersion); | 
 |   if (!_channel->init(this, BROWSERCHANNEL_PROTOCOL_VERSION, | 
 |       BROWSERCHANNEL_PROTOCOL_VERSION, hostedHtmlVersionStr)) { | 
 |     BOOLEAN_TO_NPVARIANT(false, *result); | 
 |     result->type = NPVariantType_Bool; | 
 |   } | 
 |  | 
 |   string moduleNameStr = convertToString(moduleName); | 
 |   string userAgent(NPN_UserAgent(getNPP())); | 
 |   string tabKeyStr = computeTabIdentity(); | 
 |   string sessionKeyStr = convertToString(sessionKey); | 
 |   Debug::log(Debug::Debugging) << "  connected, sending loadModule" << Debug::flush; | 
 |   connected = LoadModuleMessage::send(*_channel, urlStr, tabKeyStr, sessionKeyStr, | 
 |       moduleNameStr, userAgent, this); | 
 |   BOOLEAN_TO_NPVARIANT(connected, *result); | 
 |   result->type = NPVariantType_Bool; | 
 | } | 
 |  | 
 | void ScriptableInstance::fatalError(HostChannel& channel, const std::string& message) { | 
 |   // TODO(jat): better error handling | 
 |   Debug::log(Debug::Error) << "Fatal error: " << message << Debug::flush; | 
 | } | 
 |  | 
 | void ScriptableInstance::dupString(const char* str, NPString& npString) { | 
 |   npString.UTF8Length = static_cast<uint32_t>(strlen(str)); | 
 |   NPUTF8* chars = static_cast<NPUTF8*>(NPN_MemAlloc(npString.UTF8Length)); | 
 |   memcpy(chars, str, npString.UTF8Length); | 
 |   npString.UTF8Characters = chars; | 
 | } | 
 |  | 
 | // SessionHandler methods | 
 | void ScriptableInstance::freeValue(HostChannel& channel, int idCount, const int* const ids) { | 
 |   Debug::log(Debug::Debugging) << "freeValue(#ids=" << idCount << ")" << Debug::flush; | 
 |   for (int i = 0; i < idCount; ++i) { | 
 | 	  Debug::log(Debug::Spam) << " id=" << ids[i] << Debug::flush; | 
 |     localObjects.free(ids[i]); | 
 |   } | 
 | } | 
 |  | 
 | void ScriptableInstance::loadJsni(HostChannel& channel, const std::string& js) { | 
 |   NPString npScript; | 
 |   dupString(js.c_str(), npScript); | 
 |   NPVariantWrapper npResult(*this); | 
 |   Debug::log(Debug::Spam) << "loadJsni - \n" << js << Debug::flush; | 
 |   if (!NPN_Evaluate(getNPP(), window, &npScript, npResult.addressForReturn())) { | 
 |     Debug::log(Debug::Error) << "loadJsni failed\n" << js << Debug::flush; | 
 |   } | 
 | } | 
 |  | 
 | Value ScriptableInstance::clientMethod_getProperty(HostChannel& channel, int numArgs, const Value* const args) { | 
 |   if (numArgs != 2 || !args[0].isInt() || (!args[1].isString() && !args[1].isInt())) { | 
 |     Debug::log(Debug::Error) << "Incorrect invocation of getProperty: #args=" << numArgs << ":"; | 
 |     for (int i = 0; i < numArgs; ++i) { | 
 |       Debug::log(Debug::Error) << " " << i << "=" << args[i].toString(); | 
 |     } | 
 |     Debug::log(Debug::Error) << Debug::flush; | 
 |     return Value(); | 
 |   } | 
 |   int id = args[0].getInt(); | 
 |   NPObject* obj = localObjects.get(id); | 
 |   NPIdentifier propID; | 
 |   if (args[1].isString()) { | 
 |     std::string propName = args[1].getString(); | 
 |     propID = NPN_GetStringIdentifier(propName.c_str()); | 
 |   } else { | 
 |     int propNum = args[1].getInt(); | 
 |     propID = NPN_GetIntIdentifier(propNum); | 
 |   } | 
 |   NPVariantWrapper npResult(*this); | 
 |   if (!NPN_GetProperty(getNPP(), obj, propID, npResult.addressForReturn())) { | 
 |     Debug::log(Debug::Warning) << "getProperty(id=" << id << ", prop=" | 
 |         << NPN_UTF8FromIdentifier(propID) << ") failed" << Debug::flush; | 
 |     return Value(); | 
 |   } | 
 |   return npResult.getAsValue(*this); | 
 | } | 
 |  | 
 | Value ScriptableInstance::clientMethod_setProperty(HostChannel& channel, int numArgs, const Value* const args) { | 
 |   if (numArgs != 2 || !args[0].isInt() || (!args[1].isString() && !args[1].isInt())) { | 
 |     Debug::log(Debug::Error) << "Incorrect invocation of setProperty: #args=" | 
 |         << numArgs << ":"; | 
 |     for (int i = 0; i < numArgs; ++i) { | 
 |       Debug::log(Debug::Error) << " " << i << "=" << args[i].toString(); | 
 |     } | 
 |     Debug::log(Debug::Error) << Debug::flush; | 
 |     return Value(); | 
 |   } | 
 |   int id = args[0].getInt(); | 
 |   NPObject* obj = localObjects.get(id); | 
 |   NPIdentifier propID; | 
 |   if (args[1].isString()) { | 
 |     std::string propName = args[1].getString(); | 
 |     propID = NPN_GetStringIdentifier(propName.c_str()); | 
 |   } else { | 
 |     int propNum = args[1].getInt(); | 
 |     propID = NPN_GetIntIdentifier(propNum); | 
 |   } | 
 |   NPVariantWrapper npValue(*this); | 
 |   npValue.operator=(args[2]); | 
 |   if (!NPN_SetProperty(getNPP(), obj, propID, npValue.address())) { | 
 |     Debug::log(Debug::Warning) << "setProperty(id=" << id << ", prop=" | 
 |         << NPN_UTF8FromIdentifier(propID) << ", val=" << args[2].toString() | 
 |         << ") failed" << Debug::flush; | 
 |     return Value(); | 
 |   } | 
 |   return Value(); | 
 | } | 
 |  | 
 | /** | 
 |  * SessionHandler.invoke - used by LoadModule and reactToMessages* to process server-side | 
 |  * requests to invoke methods in Javascript or the plugin. | 
 |  */ | 
 | bool ScriptableInstance::invokeSpecial(HostChannel& channel, SpecialMethodId dispatchId, | 
 |     int numArgs, const Value* const args, Value* returnValue) { | 
 |   switch (dispatchId) { | 
 |   case SessionHandler::HasMethod: | 
 |   case SessionHandler::HasProperty: | 
 |     break; | 
 |   case SessionHandler::SetProperty: | 
 |     *returnValue = clientMethod_setProperty(channel, numArgs, args); | 
 |     return false; | 
 |   case SessionHandler::GetProperty: | 
 |     *returnValue = clientMethod_getProperty(channel, numArgs, args); | 
 |     return false; | 
 |   default: | 
 |     break; | 
 |   } | 
 |   Debug::log(Debug::Error) << "Unexpected special method " << dispatchId | 
 |       << " called on plugin; #args=" << numArgs << ":"; | 
 |   for (int i = 0; i < numArgs; ++i) { | 
 |     Debug::log(Debug::Error) << " " << i << "=" << args[i].toString(); | 
 |   } | 
 |   Debug::log(Debug::Error) << Debug::flush; | 
 |   // TODO(jat): should we create a real exception object? | 
 |   std::string buf("unexpected invokeSpecial("); | 
 |   buf += static_cast<int>(dispatchId); | 
 |   buf += ")"; | 
 |   returnValue->setString(buf); | 
 |   return true; | 
 | } | 
 |  | 
 | bool ScriptableInstance::invoke(HostChannel& channel, const Value& thisRef, | 
 |     const std::string& methodName, int numArgs, const Value* const args, | 
 |     Value* returnValue) { | 
 |   Debug::log(Debug::Debugging) << "invokeJS(" << methodName << ", this="  | 
 |       << thisRef.toString() << ", numArgs=" << numArgs << ")" << Debug::flush; | 
 |   NPVariantArray varArgs(*this, numArgs + 2); | 
 |   varArgs[0] = thisRef; | 
 |   varArgs[1] = methodName; | 
 |   for (int i = 0; i < numArgs; ++i) { | 
 |     varArgs[i + 2] = args[i]; | 
 |   } | 
 |   const NPVariant* argArray = varArgs.getArray(); | 
 |   if (Debug::level(Debug::Spam)) { | 
 |     for (int i = 0; i < varArgs.getSize(); ++i) { | 
 |       Debug::log(Debug::Spam) << "  arg " << i << " is " | 
 |           << NPVariantProxy::toString(argArray[i]) << Debug::flush; | 
 |     } | 
 |   } | 
 |   NPVariantWrapper wrappedRetVal(*this); | 
 |   if (!NPN_Invoke(getNPP(), window, jsInvokeID, argArray, varArgs.getSize(), | 
 |       wrappedRetVal.addressForReturn())) { | 
 |     Debug::log(Debug::Error) << "*** invokeJS(" << methodName << ", this=" | 
 |         << thisRef.toString() << ", numArgs=" << numArgs << ") failed" | 
 |         << Debug::flush; | 
 |     // TODO(jat): should we create a real exception object? | 
 |     returnValue->setString("invoke of " + methodName + " failed"); | 
 |     return true; | 
 |   } | 
 |   Debug::log(Debug::Spam) << "  wrapped return is " << wrappedRetVal.toString() << Debug::flush; | 
 |   NPVariantWrapper exceptFlag(*this); | 
 |   NPVariantWrapper retval(*this); | 
 |   NPObject* wrappedArray = wrappedRetVal.getAsObject(); | 
 |   if (!NPN_GetProperty(getNPP(), wrappedArray, idx0, exceptFlag.addressForReturn())) { | 
 |     Debug::log(Debug::Error) << " Error getting element 0 of wrapped return value (" | 
 |         << wrappedRetVal << ") on call to " << methodName << Debug::flush; | 
 |   } | 
 |   if (!NPN_GetProperty(getNPP(), wrappedArray, idx1, retval.addressForReturn())) { | 
 |     Debug::log(Debug::Error) << " Error getting element 1 of wrapped return value (" | 
 |         << wrappedRetVal << ") on call to " << methodName << Debug::flush; | 
 |   } | 
 |   Debug::log(Debug::Debugging) << "  return value " << retval.toString() << Debug::flush; | 
 |   *returnValue = retval.getAsValue(*this); | 
 |   if (exceptFlag.isInt() && exceptFlag.getAsInt() != 0) { | 
 |     Debug::log(Debug::Debugging) << "  exception: " << retval << Debug::flush; | 
 |     return true; | 
 |   } | 
 |   return false; | 
 | } | 
 |  | 
 | bool ScriptableInstance::JavaObject_invoke(int objectId, int dispId, | 
 |     const NPVariant* args, uint32_t numArgs, NPVariant* result) { | 
 |   Debug::log(Debug::Debugging) << "JavaObject_invoke(dispId= " << dispId << ", numArgs=" << numArgs << ")" << Debug::flush; | 
 |   if (Debug::level(Debug::Spam)) { | 
 |     for (uint32_t i = 0; i < numArgs; ++i) { | 
 |       Debug::log(Debug::Spam) << "  " << i << " = " << args[i] << Debug::flush; | 
 |     } | 
 |   } | 
 |  | 
 |   bool isRawToString = false; | 
 |   if (dispId == -1) { | 
 |     dispId = 0; | 
 |     isRawToString = true; | 
 |   } | 
 |  | 
 |   Value javaThis; | 
 |   javaThis.setJavaObject(objectId); | 
 |   scoped_array<Value> vargs(new Value[numArgs]); | 
 |   for (unsigned i = 0; i < numArgs; ++i) { | 
 |     vargs[i] = NPVariantProxy::getAsValue(args[i], *this); | 
 |   } | 
 |   if (!InvokeMessage::send(*_channel, javaThis, dispId, numArgs, vargs.get())) { | 
 |     Debug::log(Debug::Error) << "JavaObject_invoke: failed to send invoke message" << Debug::flush; | 
 |     // TODO(jat): returning false here spams the browser console | 
 |     return true; | 
 |   } | 
 |   Debug::log(Debug::Debugging) << " return from invoke" << Debug::flush; | 
 |   scoped_ptr<ReturnMessage> retMsg(_channel->reactToMessagesWhileWaitingForReturn(this)); | 
 |   if (!retMsg.get()) { | 
 |     Debug::log(Debug::Error) << "JavaObject_invoke: failed to get return value" << Debug::flush; | 
 |     return false; | 
 |   } | 
 |   if (isRawToString) { | 
 |     // toString() needs the raw value | 
 |     NPVariantProxy::assignFrom(*this, *result, retMsg->getReturnValue()); | 
 |     return !retMsg->isException(); | 
 |   } | 
 |   // Wrap the result | 
 |   return makeResult(retMsg->isException(), retMsg->getReturnValue(), result); | 
 | } | 
 |  | 
 | bool ScriptableInstance::JavaObject_getProperty(int objectId, int dispId, | 
 |     NPVariant* result) { | 
 |   Debug::log(Debug::Debugging) << "JavaObject_getProperty(objectid=" | 
 |       << objectId << ", dispId=" << dispId << ")" << Debug::flush; | 
 |   VOID_TO_NPVARIANT(*result); | 
 |   Value propertyValue = ServerMethods::getProperty(*_channel, this, objectId, dispId); | 
 |   if (propertyValue.isJsObject()) { | 
 |     // TODO(jat): special-case for testing | 
 |     NPObject* npObj = localObjects.get(propertyValue.getJsObjectId()); | 
 |     OBJECT_TO_NPVARIANT(npObj, *result); | 
 |     NPN_RetainObject(npObj); | 
 |   } else { | 
 |     NPVariantProxy::assignFrom(*this, *result, propertyValue); | 
 |   } | 
 |   Debug::log(Debug::Debugging) << " return val=" << propertyValue | 
 |       << ", NPVariant=" << *result << Debug::flush; | 
 |   if (NPVariantProxy::isObject(*result)) { | 
 |     dumpObjectBytes(NPVariantProxy::getAsObject(*result)); | 
 |   } | 
 |   if (NPVariantProxy::isObject(*result)) { | 
 |     Debug::log(Debug::Debugging) << "  final return refcount = " | 
 |         << NPVariantProxy::getAsObject(*result)->referenceCount << Debug::flush;  | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | bool ScriptableInstance::JavaObject_setProperty(int objectId, int dispId, | 
 |     const NPVariant* npValue) { | 
 |   Debug::log(Debug::Debugging) << "JavaObject_setProperty(objectid=" | 
 |       << objectId << ", dispId=" << dispId << ", value=" << *npValue << ")" << Debug::flush; | 
 |   if (NPVariantProxy::isObject(*npValue)) { | 
 |     Debug::log(Debug::Debugging) << "  before localObj: refcount = " | 
 |         << NPVariantProxy::getAsObject(*npValue)->referenceCount << Debug::flush;  | 
 |   } | 
 |   Value value = NPVariantProxy::getAsValue(*npValue, *this, true); | 
 |   if (NPVariantProxy::isObject(*npValue)) { | 
 |     Debug::log(Debug::Debugging) << "  after localObj: refcount = " | 
 |         << NPVariantProxy::getAsObject(*npValue)->referenceCount << Debug::flush;  | 
 |   } | 
 |   if (NPVariantProxy::isObject(*npValue)) { | 
 |     dumpObjectBytes(NPVariantProxy::getAsObject(*npValue)); | 
 |   } | 
 |   Debug::log(Debug::Debugging) << "  as value: " << value << Debug::flush; | 
 |   // TODO: no way to set an actual exception object! (Could ClassCastException on server.) | 
 |   return ServerMethods::setProperty(*_channel, this, objectId, dispId, value); | 
 | } | 
 |  | 
 | bool ScriptableInstance::JavaObject_getToStringTearOff(NPVariant* result) { | 
 |   Debug::log(Debug::Debugging) << "JavaObject_getToStringTearOff()" << Debug::flush; | 
 |   VOID_TO_NPVARIANT(*result); | 
 |  | 
 |   Value temp; | 
 |   NPVariantArray varArgs(*this, 3); | 
 |   temp.setNull();  varArgs[0] = temp; // proxy: no proxy needed | 
 |   temp.setInt(0);  varArgs[1] = temp; // dispId: always 0 for toString() | 
 |   temp.setInt(0);  varArgs[2] = temp; // argCount: always 0 for toString() | 
 |  | 
 |   if (!NPN_Invoke(getNPP(), window, jsTearOffID, varArgs.getArray(), 3, result)) { | 
 |     Debug::log(Debug::Error) << "*** JavaObject_getToStringTearOff() failed" | 
 |         << Debug::flush; | 
 |     return true; | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | JavaObject* ScriptableInstance::createJavaWrapper(int objectId) { | 
 |   Debug::log(Debug::Debugging) << "createJavaWrapper(objectId=" << objectId <<  ")" << Debug::flush; | 
 |   JavaObject* jObj; | 
 |   hash_map<int, JavaObject*>::iterator it = javaObjects.find(objectId); | 
 |   if (it != javaObjects.end()) { | 
 |     jObj = it->second; | 
 |     NPN_RetainObject(jObj); | 
 |     return jObj; | 
 |   } | 
 |   jObj = JavaObject::create(this, objectId); | 
 |   javaObjects[objectId] = jObj; | 
 |   return jObj; | 
 | } | 
 |  | 
 | void ScriptableInstance::destroyJavaWrapper(JavaObject* jObj) { | 
 |   int objectId = jObj->getObjectId(); | 
 |   if (!javaObjects.erase(objectId)) { | 
 |     Debug::log(Debug::Error) << "destroyJavaWrapper(id=" << objectId | 
 |         << "): trying to free unknown JavaObject" << Debug::flush; | 
 |   } | 
 |   Debug::log(Debug::Debugging) << "destroyJavaWrapper(id=" << objectId << ")" << Debug::flush; | 
 |   javaObjectsToFree.insert(objectId); | 
 | } | 
 |  | 
 | void ScriptableInstance::sendFreeValues(HostChannel& channel) { | 
 |   unsigned n = javaObjectsToFree.size(); | 
 |   if (n) { | 
 |     scoped_array<int> ids(new int[n]); | 
 |     int i = 0; | 
 |     for (std::set<int>::iterator it = javaObjectsToFree.begin(); | 
 |         it != javaObjectsToFree.end(); ++it) { | 
 |       ids[i++] = *it; | 
 |     } | 
 |     if (ServerMethods::freeJava(channel, this, n, ids.get())) { | 
 |       javaObjectsToFree.clear(); | 
 |     } | 
 |   } | 
 | } | 
 |  |