| /* |
| * 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; |
| const static string BACKGROUND_PAGE_STR = "chrome-extension://jpjpnpmbddbjkfaccnmhnkdgjideieim/background.html"; |
| const static string UNKNOWN_STR = "unknown"; |
| const static string INCLUDE_STR = "include"; |
| const static string EXCLUDE_STR = "exclude"; |
| |
| static inline string convertToString(const NPString& str) { |
| return string(GetNPStringUTF8Characters(str), GetNPStringUTF8Length(str)); |
| } |
| |
| 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")), |
| loadHostEntriesID(NPN_GetStringIdentifier("loadHostEntries")), |
| locationID(NPN_GetStringIdentifier("location")), |
| hrefID(NPN_GetStringIdentifier("href")), |
| urlID(NPN_GetStringIdentifier("url")), |
| includeID(NPN_GetStringIdentifier("include")), |
| getHostPermissionID(NPN_GetStringIdentifier("getHostPermission")), |
| connectedID(NPN_GetStringIdentifier("connected")), |
| statsID(NPN_GetStringIdentifier("stats")), |
| gwtId(NPN_GetStringIdentifier("__gwt_ObjectId")), |
| jsDisconnectedID(NPN_GetStringIdentifier("__gwt_disconnected")), |
| 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; |
| } |
| |
| 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; |
| } |
| 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; |
| 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 || |
| name == loadHostEntriesID || |
| name == getHostPermissionID) { |
| 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); |
| } else if (name == loadHostEntriesID) { |
| loadHostEntries(args, argCount, result); |
| } else if (name == getHostPermissionID) { |
| getHostPermission(args, argCount, result); |
| } |
| 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; |
| } |
| |
| string ScriptableInstance::getLocationHref() { |
| NPVariantWrapper locationVariant(*this); |
| NPVariantWrapper hrefVariant(*this); |
| |
| // window.location |
| NPN_GetProperty(getNPP(), window, locationID, locationVariant.addressForReturn()); |
| //window.location.href |
| NPN_GetProperty(getNPP(), locationVariant.getAsObject(), hrefID, hrefVariant.addressForReturn()); |
| |
| const NPString* locationHref = NPVariantProxy::getAsNPString(hrefVariant); |
| return convertToString(*locationHref); |
| } |
| |
| |
| void ScriptableInstance::loadHostEntries(const NPVariant* args, unsigned argCount, NPVariant* result) { |
| string locationHref = getLocationHref(); |
| if (locationHref.compare(BACKGROUND_PAGE_STR) == 0) { |
| AllowedConnections::clearRules(); |
| for (unsigned i = 0; i < argCount; ++i) { |
| //Get the host entry object {url: "somehost.net", include: true/false} |
| NPObject* hostEntry = NPVariantProxy::getAsObject(args[i]); |
| if (!hostEntry) { |
| Debug::log(Debug::Error) << "Got a host entry that is not an object.\n"; |
| break; |
| } |
| |
| //Get the url |
| NPVariantWrapper urlVariant(*this); |
| if (!NPN_GetProperty(getNPP(), hostEntry, urlID, urlVariant.addressForReturn()) || |
| !urlVariant.isString()) { |
| Debug::log(Debug::Error) << "Got a host.url entry that is not a string.\n"; |
| break; |
| } |
| const NPString* urlNPString = urlVariant.getAsNPString(); |
| string urlString = convertToString(*urlNPString); |
| |
| //Include/Exclude? |
| NPVariantWrapper includeVariant(*this); |
| if (!NPN_GetProperty(getNPP(), hostEntry, includeID, includeVariant.addressForReturn()) || |
| !includeVariant.isBoolean()) { |
| Debug::log(Debug::Error) << "Got a host.include entry that is not a boolean.\n"; |
| break; |
| } |
| bool include = includeVariant.getAsBoolean(); |
| Debug::log(Debug::Info) << "Adding " << urlString << "(" << (include ? "include" : "exclude") << ")\n"; |
| AllowedConnections::addRule(urlString, !include); |
| } |
| } else { |
| Debug::log(Debug::Error) << "ScriptableInstance::loadHostEntries called from outside the background page: " << |
| locationHref << "\n"; |
| } |
| } |
| |
| void ScriptableInstance::getHostPermission(const NPVariant* args, unsigned argCount, NPVariant* result) { |
| if (argCount != 1 || !NPVariantProxy::isString(args[0])) { |
| Debug::log(Debug::Error) << "ScriptableInstance::getHostPermission called with incorrect arguments.\n"; |
| } |
| |
| const NPString url = args[0].value.stringValue; |
| const string urlStr = convertToString(url); |
| bool allowed = false; |
| bool matches = AllowedConnections::matchesRule(urlStr, &allowed); |
| string retStr; |
| |
| if (!matches) { |
| retStr = UNKNOWN_STR; |
| } else if (allowed) { |
| retStr = INCLUDE_STR; |
| } else { |
| retStr = EXCLUDE_STR; |
| } |
| |
| NPVariantProxy(*this, *result) = retStr; |
| } |
| |
| 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; |
| } |
| //ignore args[0]. Get the URL from window.location.href instead. |
| const string urlStr = getLocationHref(); |
| 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; |
| |
| bool allowed = false; |
| AllowedConnections::matchesRule(urlStr, &allowed); |
| 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; |
| } |
| |
| int ScriptableInstance::getLocalObjectRef(NPObject* obj) { |
| NPVariantWrapper wrappedRetVal(*this); |
| int id; |
| if (NPN_GetProperty(getNPP(), obj, gwtId, wrappedRetVal.addressForReturn()) |
| && wrappedRetVal.isInt()) { |
| id = wrappedRetVal.getAsInt(); |
| localObjects.set(id, obj); |
| } else { |
| id = localObjects.add(obj); |
| wrappedRetVal = id; |
| if (!NPN_SetProperty(getNPP(), obj, gwtId, wrappedRetVal.address())) { |
| Debug::log(Debug::Error) << "Setting GWT id on object failed" << Debug::flush; |
| } |
| } |
| return id; |
| } |
| |
| void ScriptableInstance::fatalError(HostChannel& channel, const 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; |
| NPObject* obj = localObjects.get(ids[i]); |
| if (!NPN_RemoveProperty(getNPP(), obj, gwtId)) { |
| Debug::log(Debug::Error) << "Unable to remove GWT ID from object " << ids[i] << Debug::flush; |
| } else { |
| localObjects.free(ids[i]); |
| } |
| } |
| } |
| |
| void ScriptableInstance::loadJsni(HostChannel& channel, const 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()) { |
| 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()) { |
| 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? |
| string buf("unexpected invokeSpecial("); |
| buf += static_cast<int>(dispatchId); |
| buf += ")"; |
| returnValue->setString(buf); |
| return true; |
| } |
| |
| bool ScriptableInstance::invoke(HostChannel& channel, const Value& thisRef, |
| const 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); |
| } |
| bool isException = false; |
| Value returnValue; |
| if (!InvokeMessage::send(*_channel, javaThis, dispId, numArgs, vargs.get())) { |
| Debug::log(Debug::Error) << "JavaObject_invoke: failed to send invoke message" << Debug::flush; |
| } else { |
| 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; |
| } else { |
| if (isRawToString) { |
| // toString() needs the raw value |
| NPVariantProxy::assignFrom(*this, *result, retMsg->getReturnValue()); |
| return !retMsg->isException(); |
| } |
| isException = retMsg->isException(); |
| returnValue = retMsg->getReturnValue(); |
| } |
| } |
| // Wrap the result |
| return makeResult(isException, returnValue, 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::disconnectDetectedImpl() { |
| NPVariantWrapper result(*this); |
| NPN_Invoke(getNPP(), window, jsDisconnectedID, 0, 0, result.addressForReturn()); |
| } |
| |
| 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(); |
| } |
| } |
| } |