| /* | |
| * 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 "Preferences.h" | |
| #include "AllowDialog.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)); | |
| } | |
| 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")), | |
| 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) { | |
| 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); | |
| Preferences::loadAccessList(); | |
| bool allowed = false; | |
| if (!AllowedConnections::matchesRule(urlStr, &allowed)) { | |
| bool remember = false; | |
| allowed = AllowDialog::askUserToAllow(&remember); | |
| if (remember) { | |
| std::string host = AllowedConnections::getHostFromUrl(urlStr); | |
| Preferences::addNewRule(host, !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 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; | |
| 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 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); | |
| } | |
| 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(); | |
| } | |
| } | |
| } | |