/* | |
* 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(); | |
} | |
} | |
} | |