CLOSED TREE
Backed out changeset 076d1d47d601 (bug 1070755)
Backed out changeset 43819af59ca5 (bug 998863)
Backed out changeset 5f587697ae63 (bug 998863)
Backed out changeset e2cf239e8572 (bug 998863)
Backed out changeset fe21b6b789ce (bug 998863)
Backed out changeset 404f59f86edc (bug 998863)
Backed out changeset 5dd57abaf2b0 (bug 998863)
Backed out changeset 6c1f006a03bc (bug 998863)
Backed out changeset 9e69875e3667 (bug 998863)
Backed out changeset c6b68f8f72ba (bug 998863)
Backed out changeset 713799a7afe4 (bug 998863)
Backed out changeset 4244d662787c (bug 998863)
Backed out changeset ba058cc7a1b2 (bug 998863)
Backed out changeset dabc69b0b09a (bug 998863)
Backed out changeset 18dad6d2e7cc (bug 998863)
1372 lines
35 KiB
C++
1372 lines
35 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
* vim: sw=2 ts=2 et :
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "PluginScriptableObjectParent.h"
|
|
|
|
#include "jsapi.h"
|
|
#include "mozilla/DebugOnly.h"
|
|
#include "mozilla/dom/ScriptSettings.h"
|
|
#include "mozilla/plugins/PluginTypes.h"
|
|
#include "mozilla/unused.h"
|
|
#include "nsNPAPIPlugin.h"
|
|
#include "PluginScriptableObjectUtils.h"
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::plugins;
|
|
using namespace mozilla::plugins::parent;
|
|
|
|
/**
|
|
* NPIdentifiers in the chrome process are stored as jsids. The difficulty is in
|
|
* ensuring that string identifiers are rooted without interning them all. We
|
|
* assume that all NPIdentifiers passed into nsJSNPRuntime will not be used
|
|
* outside the scope of the NPAPI call (i.e., they won't be stored in the
|
|
* heap). Rooting is done using the StackIdentifier class, which roots the
|
|
* identifier via RootedId.
|
|
*
|
|
* This system does not allow jsids to be moved, as would be needed for
|
|
* generational or compacting GC. When Firefox implements a moving GC for
|
|
* strings, we will need to ensure that no movement happens while NPAPI code is
|
|
* on the stack: although StackIdentifier roots all identifiers used, the GC has
|
|
* no way to no that a jsid cast to an NPIdentifier needs to be fixed up if it
|
|
* is moved.
|
|
*/
|
|
|
|
class MOZ_STACK_CLASS StackIdentifier
|
|
{
|
|
public:
|
|
explicit StackIdentifier(const PluginIdentifier& aIdentifier,
|
|
bool aIntern = false);
|
|
|
|
bool Failed() const { return mFailed; }
|
|
NPIdentifier ToNPIdentifier() const { return mIdentifier; }
|
|
|
|
private:
|
|
bool mFailed;
|
|
NPIdentifier mIdentifier;
|
|
AutoSafeJSContext mCx;
|
|
JS::RootedId mId;
|
|
};
|
|
|
|
StackIdentifier::StackIdentifier(const PluginIdentifier& aIdentifier, bool aIntern)
|
|
: mFailed(false),
|
|
mId(mCx)
|
|
{
|
|
if (aIdentifier.type() == PluginIdentifier::TnsCString) {
|
|
// We don't call _getstringidentifier because we may not want to intern the string.
|
|
NS_ConvertUTF8toUTF16 utf16name(aIdentifier.get_nsCString());
|
|
JS::RootedString str(mCx, JS_NewUCStringCopyN(mCx, utf16name.get(), utf16name.Length()));
|
|
if (!str) {
|
|
NS_ERROR("Id can't be allocated");
|
|
mFailed = true;
|
|
return;
|
|
}
|
|
if (aIntern) {
|
|
str = JS_InternJSString(mCx, str);
|
|
if (!str) {
|
|
NS_ERROR("Id can't be allocated");
|
|
mFailed = true;
|
|
return;
|
|
}
|
|
}
|
|
if (!JS_StringToId(mCx, str, &mId)) {
|
|
NS_ERROR("Id can't be allocated");
|
|
mFailed = true;
|
|
return;
|
|
}
|
|
mIdentifier = JSIdToNPIdentifier(mId);
|
|
return;
|
|
}
|
|
|
|
mIdentifier = mozilla::plugins::parent::_getintidentifier(aIdentifier.get_int32_t());
|
|
}
|
|
|
|
static bool
|
|
FromNPIdentifier(NPIdentifier aIdentifier, PluginIdentifier* aResult)
|
|
{
|
|
if (mozilla::plugins::parent::_identifierisstring(aIdentifier)) {
|
|
nsCString string;
|
|
NPUTF8* chars =
|
|
mozilla::plugins::parent::_utf8fromidentifier(aIdentifier);
|
|
if (!chars) {
|
|
return false;
|
|
}
|
|
string.Adopt(chars);
|
|
*aResult = PluginIdentifier(string);
|
|
return true;
|
|
}
|
|
else {
|
|
int32_t intval = mozilla::plugins::parent::_intfromidentifier(aIdentifier);
|
|
*aResult = PluginIdentifier(intval);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
|
|
inline void
|
|
ReleaseVariant(NPVariant& aVariant,
|
|
PluginInstanceParent* aInstance)
|
|
{
|
|
const NPNetscapeFuncs* npn = GetNetscapeFuncs(aInstance);
|
|
if (npn) {
|
|
npn->releasevariantvalue(&aVariant);
|
|
}
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
// static
|
|
NPObject*
|
|
PluginScriptableObjectParent::ScriptableAllocate(NPP aInstance,
|
|
NPClass* aClass)
|
|
{
|
|
if (aClass != GetClass()) {
|
|
NS_ERROR("Huh?! Wrong class!");
|
|
return nullptr;
|
|
}
|
|
|
|
return new ParentNPObject();
|
|
}
|
|
|
|
// static
|
|
void
|
|
PluginScriptableObjectParent::ScriptableInvalidate(NPObject* aObject)
|
|
{
|
|
if (aObject->_class != GetClass()) {
|
|
NS_ERROR("Don't know what kind of object this is!");
|
|
return;
|
|
}
|
|
|
|
ParentNPObject* object = reinterpret_cast<ParentNPObject*>(aObject);
|
|
if (object->invalidated) {
|
|
// This can happen more than once, and is just fine.
|
|
return;
|
|
}
|
|
|
|
object->invalidated = true;
|
|
|
|
// |object->parent| may be null already if the instance has gone away.
|
|
if (object->parent && !object->parent->CallInvalidate()) {
|
|
NS_ERROR("Failed to send message!");
|
|
}
|
|
}
|
|
|
|
// static
|
|
void
|
|
PluginScriptableObjectParent::ScriptableDeallocate(NPObject* aObject)
|
|
{
|
|
if (aObject->_class != GetClass()) {
|
|
NS_ERROR("Don't know what kind of object this is!");
|
|
return;
|
|
}
|
|
|
|
ParentNPObject* object = reinterpret_cast<ParentNPObject*>(aObject);
|
|
PluginScriptableObjectParent* actor = object->parent;
|
|
if (actor) {
|
|
NS_ASSERTION(actor->Type() == Proxy, "Bad type!");
|
|
actor->DropNPObject();
|
|
}
|
|
|
|
delete object;
|
|
}
|
|
|
|
// static
|
|
bool
|
|
PluginScriptableObjectParent::ScriptableHasMethod(NPObject* aObject,
|
|
NPIdentifier aName)
|
|
{
|
|
if (aObject->_class != GetClass()) {
|
|
NS_ERROR("Don't know what kind of object this is!");
|
|
return false;
|
|
}
|
|
|
|
ParentNPObject* object = reinterpret_cast<ParentNPObject*>(aObject);
|
|
if (object->invalidated) {
|
|
NS_WARNING("Calling method on an invalidated object!");
|
|
return false;
|
|
}
|
|
|
|
ProtectedActor<PluginScriptableObjectParent> actor(object->parent);
|
|
if (!actor) {
|
|
return false;
|
|
}
|
|
|
|
PluginIdentifier identifier;
|
|
if (!FromNPIdentifier(aName, &identifier)) {
|
|
return false;
|
|
}
|
|
|
|
NS_ASSERTION(actor->Type() == Proxy, "Bad type!");
|
|
|
|
bool result;
|
|
if (!actor->CallHasMethod(identifier, &result)) {
|
|
NS_WARNING("Failed to send message!");
|
|
return false;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// static
|
|
bool
|
|
PluginScriptableObjectParent::ScriptableInvoke(NPObject* aObject,
|
|
NPIdentifier aName,
|
|
const NPVariant* aArgs,
|
|
uint32_t aArgCount,
|
|
NPVariant* aResult)
|
|
{
|
|
if (aObject->_class != GetClass()) {
|
|
NS_ERROR("Don't know what kind of object this is!");
|
|
return false;
|
|
}
|
|
|
|
ParentNPObject* object = reinterpret_cast<ParentNPObject*>(aObject);
|
|
if (object->invalidated) {
|
|
NS_WARNING("Calling method on an invalidated object!");
|
|
return false;
|
|
}
|
|
|
|
ProtectedActor<PluginScriptableObjectParent> actor(object->parent);
|
|
if (!actor) {
|
|
return false;
|
|
}
|
|
|
|
PluginIdentifier identifier;
|
|
if (!FromNPIdentifier(aName, &identifier)) {
|
|
return false;
|
|
}
|
|
|
|
NS_ASSERTION(actor->Type() == Proxy, "Bad type!");
|
|
|
|
ProtectedVariantArray args(aArgs, aArgCount, actor->GetInstance());
|
|
if (!args.IsOk()) {
|
|
NS_ERROR("Failed to convert arguments!");
|
|
return false;
|
|
}
|
|
|
|
Variant remoteResult;
|
|
bool success;
|
|
if (!actor->CallInvoke(identifier, args, &remoteResult,
|
|
&success)) {
|
|
NS_WARNING("Failed to send message!");
|
|
return false;
|
|
}
|
|
|
|
if (!success) {
|
|
return false;
|
|
}
|
|
|
|
if (!ConvertToVariant(remoteResult, *aResult, actor->GetInstance())) {
|
|
NS_WARNING("Failed to convert result!");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// static
|
|
bool
|
|
PluginScriptableObjectParent::ScriptableInvokeDefault(NPObject* aObject,
|
|
const NPVariant* aArgs,
|
|
uint32_t aArgCount,
|
|
NPVariant* aResult)
|
|
{
|
|
if (aObject->_class != GetClass()) {
|
|
NS_ERROR("Don't know what kind of object this is!");
|
|
return false;
|
|
}
|
|
|
|
ParentNPObject* object = reinterpret_cast<ParentNPObject*>(aObject);
|
|
if (object->invalidated) {
|
|
NS_WARNING("Calling method on an invalidated object!");
|
|
return false;
|
|
}
|
|
|
|
ProtectedActor<PluginScriptableObjectParent> actor(object->parent);
|
|
if (!actor) {
|
|
return false;
|
|
}
|
|
|
|
NS_ASSERTION(actor->Type() == Proxy, "Bad type!");
|
|
|
|
ProtectedVariantArray args(aArgs, aArgCount, actor->GetInstance());
|
|
if (!args.IsOk()) {
|
|
NS_ERROR("Failed to convert arguments!");
|
|
return false;
|
|
}
|
|
|
|
Variant remoteResult;
|
|
bool success;
|
|
if (!actor->CallInvokeDefault(args, &remoteResult, &success)) {
|
|
NS_WARNING("Failed to send message!");
|
|
return false;
|
|
}
|
|
|
|
if (!success) {
|
|
return false;
|
|
}
|
|
|
|
if (!ConvertToVariant(remoteResult, *aResult, actor->GetInstance())) {
|
|
NS_WARNING("Failed to convert result!");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// static
|
|
bool
|
|
PluginScriptableObjectParent::ScriptableHasProperty(NPObject* aObject,
|
|
NPIdentifier aName)
|
|
{
|
|
if (aObject->_class != GetClass()) {
|
|
NS_ERROR("Don't know what kind of object this is!");
|
|
return false;
|
|
}
|
|
|
|
ParentNPObject* object = reinterpret_cast<ParentNPObject*>(aObject);
|
|
if (object->invalidated) {
|
|
NS_WARNING("Calling method on an invalidated object!");
|
|
return false;
|
|
}
|
|
|
|
ProtectedActor<PluginScriptableObjectParent> actor(object->parent);
|
|
if (!actor) {
|
|
return false;
|
|
}
|
|
|
|
PluginIdentifier identifier;
|
|
if (!FromNPIdentifier(aName, &identifier)) {
|
|
return false;
|
|
}
|
|
|
|
NS_ASSERTION(actor->Type() == Proxy, "Bad type!");
|
|
|
|
bool result;
|
|
if (!actor->CallHasProperty(identifier, &result)) {
|
|
NS_WARNING("Failed to send message!");
|
|
return false;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// static
|
|
bool
|
|
PluginScriptableObjectParent::ScriptableGetProperty(NPObject* aObject,
|
|
NPIdentifier aName,
|
|
NPVariant* aResult)
|
|
{
|
|
// See GetPropertyHelper below.
|
|
NS_NOTREACHED("Shouldn't ever call this directly!");
|
|
return false;
|
|
}
|
|
|
|
// static
|
|
bool
|
|
PluginScriptableObjectParent::ScriptableSetProperty(NPObject* aObject,
|
|
NPIdentifier aName,
|
|
const NPVariant* aValue)
|
|
{
|
|
if (aObject->_class != GetClass()) {
|
|
NS_ERROR("Don't know what kind of object this is!");
|
|
return false;
|
|
}
|
|
|
|
ParentNPObject* object = reinterpret_cast<ParentNPObject*>(aObject);
|
|
if (object->invalidated) {
|
|
NS_WARNING("Calling method on an invalidated object!");
|
|
return false;
|
|
}
|
|
|
|
ProtectedActor<PluginScriptableObjectParent> actor(object->parent);
|
|
if (!actor) {
|
|
return false;
|
|
}
|
|
|
|
PluginIdentifier identifier;
|
|
if (!FromNPIdentifier(aName, &identifier)) {
|
|
return false;
|
|
}
|
|
|
|
NS_ASSERTION(actor->Type() == Proxy, "Bad type!");
|
|
|
|
ProtectedVariant value(*aValue, actor->GetInstance());
|
|
if (!value.IsOk()) {
|
|
NS_WARNING("Failed to convert variant!");
|
|
return false;
|
|
}
|
|
|
|
bool success;
|
|
if (!actor->CallSetProperty(identifier, value, &success)) {
|
|
NS_WARNING("Failed to send message!");
|
|
return false;
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
// static
|
|
bool
|
|
PluginScriptableObjectParent::ScriptableRemoveProperty(NPObject* aObject,
|
|
NPIdentifier aName)
|
|
{
|
|
if (aObject->_class != GetClass()) {
|
|
NS_ERROR("Don't know what kind of object this is!");
|
|
return false;
|
|
}
|
|
|
|
ParentNPObject* object = reinterpret_cast<ParentNPObject*>(aObject);
|
|
if (object->invalidated) {
|
|
NS_WARNING("Calling method on an invalidated object!");
|
|
return false;
|
|
}
|
|
|
|
ProtectedActor<PluginScriptableObjectParent> actor(object->parent);
|
|
if (!actor) {
|
|
return false;
|
|
}
|
|
|
|
PluginIdentifier identifier;
|
|
if (!FromNPIdentifier(aName, &identifier)) {
|
|
return false;
|
|
}
|
|
|
|
NS_ASSERTION(actor->Type() == Proxy, "Bad type!");
|
|
|
|
bool success;
|
|
if (!actor->CallRemoveProperty(identifier, &success)) {
|
|
NS_WARNING("Failed to send message!");
|
|
return false;
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
// static
|
|
bool
|
|
PluginScriptableObjectParent::ScriptableEnumerate(NPObject* aObject,
|
|
NPIdentifier** aIdentifiers,
|
|
uint32_t* aCount)
|
|
{
|
|
if (aObject->_class != GetClass()) {
|
|
NS_ERROR("Don't know what kind of object this is!");
|
|
return false;
|
|
}
|
|
|
|
ParentNPObject* object = reinterpret_cast<ParentNPObject*>(aObject);
|
|
if (object->invalidated) {
|
|
NS_WARNING("Calling method on an invalidated object!");
|
|
return false;
|
|
}
|
|
|
|
ProtectedActor<PluginScriptableObjectParent> actor(object->parent);
|
|
if (!actor) {
|
|
return false;
|
|
}
|
|
|
|
NS_ASSERTION(actor->Type() == Proxy, "Bad type!");
|
|
|
|
const NPNetscapeFuncs* npn = GetNetscapeFuncs(aObject);
|
|
if (!npn) {
|
|
NS_ERROR("No netscape funcs!");
|
|
return false;
|
|
}
|
|
|
|
AutoInfallibleTArray<PluginIdentifier, 10> identifiers;
|
|
bool success;
|
|
if (!actor->CallEnumerate(&identifiers, &success)) {
|
|
NS_WARNING("Failed to send message!");
|
|
return false;
|
|
}
|
|
|
|
if (!success) {
|
|
return false;
|
|
}
|
|
|
|
*aCount = identifiers.Length();
|
|
if (!*aCount) {
|
|
*aIdentifiers = nullptr;
|
|
return true;
|
|
}
|
|
|
|
*aIdentifiers = (NPIdentifier*)npn->memalloc(*aCount * sizeof(NPIdentifier));
|
|
if (!*aIdentifiers) {
|
|
NS_ERROR("Out of memory!");
|
|
return false;
|
|
}
|
|
|
|
for (uint32_t index = 0; index < *aCount; index++) {
|
|
// We intern the ID to avoid a GC hazard here. This could probably be fixed
|
|
// if the interface with nsJSNPRuntime were smarter.
|
|
StackIdentifier stackID(identifiers[index], true /* aIntern */);
|
|
if (stackID.Failed()) {
|
|
return false;
|
|
}
|
|
(*aIdentifiers)[index] = stackID.ToNPIdentifier();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// static
|
|
bool
|
|
PluginScriptableObjectParent::ScriptableConstruct(NPObject* aObject,
|
|
const NPVariant* aArgs,
|
|
uint32_t aArgCount,
|
|
NPVariant* aResult)
|
|
{
|
|
if (aObject->_class != GetClass()) {
|
|
NS_ERROR("Don't know what kind of object this is!");
|
|
return false;
|
|
}
|
|
|
|
ParentNPObject* object = reinterpret_cast<ParentNPObject*>(aObject);
|
|
if (object->invalidated) {
|
|
NS_WARNING("Calling method on an invalidated object!");
|
|
return false;
|
|
}
|
|
|
|
ProtectedActor<PluginScriptableObjectParent> actor(object->parent);
|
|
if (!actor) {
|
|
return false;
|
|
}
|
|
|
|
NS_ASSERTION(actor->Type() == Proxy, "Bad type!");
|
|
|
|
ProtectedVariantArray args(aArgs, aArgCount, actor->GetInstance());
|
|
if (!args.IsOk()) {
|
|
NS_ERROR("Failed to convert arguments!");
|
|
return false;
|
|
}
|
|
|
|
Variant remoteResult;
|
|
bool success;
|
|
if (!actor->CallConstruct(args, &remoteResult, &success)) {
|
|
NS_WARNING("Failed to send message!");
|
|
return false;
|
|
}
|
|
|
|
if (!success) {
|
|
return false;
|
|
}
|
|
|
|
if (!ConvertToVariant(remoteResult, *aResult, actor->GetInstance())) {
|
|
NS_WARNING("Failed to convert result!");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const NPClass PluginScriptableObjectParent::sNPClass = {
|
|
NP_CLASS_STRUCT_VERSION,
|
|
PluginScriptableObjectParent::ScriptableAllocate,
|
|
PluginScriptableObjectParent::ScriptableDeallocate,
|
|
PluginScriptableObjectParent::ScriptableInvalidate,
|
|
PluginScriptableObjectParent::ScriptableHasMethod,
|
|
PluginScriptableObjectParent::ScriptableInvoke,
|
|
PluginScriptableObjectParent::ScriptableInvokeDefault,
|
|
PluginScriptableObjectParent::ScriptableHasProperty,
|
|
PluginScriptableObjectParent::ScriptableGetProperty,
|
|
PluginScriptableObjectParent::ScriptableSetProperty,
|
|
PluginScriptableObjectParent::ScriptableRemoveProperty,
|
|
PluginScriptableObjectParent::ScriptableEnumerate,
|
|
PluginScriptableObjectParent::ScriptableConstruct
|
|
};
|
|
|
|
PluginScriptableObjectParent::PluginScriptableObjectParent(
|
|
ScriptableObjectType aType)
|
|
: mInstance(nullptr),
|
|
mObject(nullptr),
|
|
mProtectCount(0),
|
|
mType(aType)
|
|
{
|
|
}
|
|
|
|
PluginScriptableObjectParent::~PluginScriptableObjectParent()
|
|
{
|
|
if (mObject) {
|
|
if (mObject->_class == GetClass()) {
|
|
NS_ASSERTION(mType == Proxy, "Wrong type!");
|
|
static_cast<ParentNPObject*>(mObject)->parent = nullptr;
|
|
}
|
|
else {
|
|
NS_ASSERTION(mType == LocalObject, "Wrong type!");
|
|
GetInstance()->GetNPNIface()->releaseobject(mObject);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
PluginScriptableObjectParent::InitializeProxy()
|
|
{
|
|
NS_ASSERTION(mType == Proxy, "Bad type!");
|
|
NS_ASSERTION(!mObject, "Calling Initialize more than once!");
|
|
|
|
mInstance = static_cast<PluginInstanceParent*>(Manager());
|
|
NS_ASSERTION(mInstance, "Null manager?!");
|
|
|
|
NPObject* object = CreateProxyObject();
|
|
NS_ASSERTION(object, "Failed to create object!");
|
|
|
|
if (!mInstance->RegisterNPObjectForActor(object, this)) {
|
|
NS_ERROR("Out of memory?");
|
|
}
|
|
|
|
mObject = object;
|
|
}
|
|
|
|
void
|
|
PluginScriptableObjectParent::InitializeLocal(NPObject* aObject)
|
|
{
|
|
NS_ASSERTION(mType == LocalObject, "Bad type!");
|
|
NS_ASSERTION(!(mInstance && mObject), "Calling Initialize more than once!");
|
|
|
|
mInstance = static_cast<PluginInstanceParent*>(Manager());
|
|
NS_ASSERTION(mInstance, "Null manager?!");
|
|
|
|
mInstance->GetNPNIface()->retainobject(aObject);
|
|
|
|
NS_ASSERTION(!mProtectCount, "Should be zero!");
|
|
mProtectCount++;
|
|
|
|
if (!mInstance->RegisterNPObjectForActor(aObject, this)) {
|
|
NS_ERROR("Out of memory?");
|
|
}
|
|
|
|
mObject = aObject;
|
|
}
|
|
|
|
NPObject*
|
|
PluginScriptableObjectParent::CreateProxyObject()
|
|
{
|
|
NS_ASSERTION(mInstance, "Must have an instance!");
|
|
NS_ASSERTION(mType == Proxy, "Shouldn't call this for non-proxy object!");
|
|
|
|
const NPNetscapeFuncs* npn = GetNetscapeFuncs(mInstance);
|
|
|
|
NPObject* npobject = npn->createobject(mInstance->GetNPP(),
|
|
const_cast<NPClass*>(GetClass()));
|
|
NS_ASSERTION(npobject, "Failed to create object?!");
|
|
NS_ASSERTION(npobject->_class == GetClass(), "Wrong kind of object!");
|
|
NS_ASSERTION(npobject->referenceCount == 1, "Some kind of live object!");
|
|
|
|
ParentNPObject* object = static_cast<ParentNPObject*>(npobject);
|
|
NS_ASSERTION(!object->invalidated, "Bad object!");
|
|
NS_ASSERTION(!object->parent, "Bad object!");
|
|
|
|
// We don't want to have the actor own this object but rather let the object
|
|
// own this actor. Set the reference count to 0 here so that when the object
|
|
// dies we will send the destructor message to the child.
|
|
object->referenceCount = 0;
|
|
NS_LOG_RELEASE(object, 0, "BrowserNPObject");
|
|
|
|
object->parent = const_cast<PluginScriptableObjectParent*>(this);
|
|
return object;
|
|
}
|
|
|
|
bool
|
|
PluginScriptableObjectParent::ResurrectProxyObject()
|
|
{
|
|
NS_ASSERTION(mInstance, "Must have an instance already!");
|
|
NS_ASSERTION(!mObject, "Should not have an object already!");
|
|
NS_ASSERTION(mType == Proxy, "Shouldn't call this for non-proxy object!");
|
|
|
|
InitializeProxy();
|
|
NS_ASSERTION(mObject, "Initialize failed!");
|
|
|
|
if (!SendProtect()) {
|
|
NS_WARNING("Failed to send message!");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
NPObject*
|
|
PluginScriptableObjectParent::GetObject(bool aCanResurrect)
|
|
{
|
|
if (!mObject && aCanResurrect && !ResurrectProxyObject()) {
|
|
NS_ERROR("Null object!");
|
|
return nullptr;
|
|
}
|
|
return mObject;
|
|
}
|
|
|
|
void
|
|
PluginScriptableObjectParent::Protect()
|
|
{
|
|
NS_ASSERTION(mObject, "No object!");
|
|
NS_ASSERTION(mProtectCount >= 0, "Negative protect count?!");
|
|
|
|
if (mType == LocalObject) {
|
|
++mProtectCount;
|
|
}
|
|
}
|
|
|
|
void
|
|
PluginScriptableObjectParent::Unprotect()
|
|
{
|
|
NS_ASSERTION(mObject, "No object!");
|
|
NS_ASSERTION(mProtectCount >= 0, "Negative protect count?!");
|
|
|
|
if (mType == LocalObject) {
|
|
if (--mProtectCount == 0) {
|
|
unused << PluginScriptableObjectParent::Send__delete__(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
PluginScriptableObjectParent::DropNPObject()
|
|
{
|
|
NS_ASSERTION(mObject, "Invalidated object!");
|
|
NS_ASSERTION(mObject->_class == GetClass(), "Wrong type of object!");
|
|
NS_ASSERTION(mType == Proxy, "Shouldn't call this for non-proxy object!");
|
|
|
|
// We think we're about to be deleted, but we could be racing with the other
|
|
// process.
|
|
PluginInstanceParent* instance = GetInstance();
|
|
NS_ASSERTION(instance, "Must have an instance!");
|
|
|
|
instance->UnregisterNPObject(mObject);
|
|
mObject = nullptr;
|
|
|
|
unused << SendUnprotect();
|
|
}
|
|
|
|
void
|
|
PluginScriptableObjectParent::ActorDestroy(ActorDestroyReason aWhy)
|
|
{
|
|
// Implement me! Bug 1005163
|
|
}
|
|
|
|
bool
|
|
PluginScriptableObjectParent::AnswerHasMethod(const PluginIdentifier& aId,
|
|
bool* aHasMethod)
|
|
{
|
|
if (!mObject) {
|
|
NS_WARNING("Calling AnswerHasMethod with an invalidated object!");
|
|
*aHasMethod = false;
|
|
return true;
|
|
}
|
|
|
|
NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!");
|
|
NS_ASSERTION(mType == LocalObject, "Bad type!");
|
|
|
|
PluginInstanceParent* instance = GetInstance();
|
|
if (!instance) {
|
|
NS_ERROR("No instance?!");
|
|
*aHasMethod = false;
|
|
return true;
|
|
}
|
|
|
|
const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance);
|
|
if (!npn) {
|
|
NS_ERROR("No netscape funcs?!");
|
|
*aHasMethod = false;
|
|
return true;
|
|
}
|
|
|
|
StackIdentifier stackID(aId);
|
|
if (stackID.Failed()) {
|
|
*aHasMethod = false;
|
|
return true;
|
|
}
|
|
*aHasMethod = npn->hasmethod(instance->GetNPP(), mObject, stackID.ToNPIdentifier());
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
PluginScriptableObjectParent::AnswerInvoke(const PluginIdentifier& aId,
|
|
const InfallibleTArray<Variant>& aArgs,
|
|
Variant* aResult,
|
|
bool* aSuccess)
|
|
{
|
|
if (!mObject) {
|
|
NS_WARNING("Calling AnswerInvoke with an invalidated object!");
|
|
*aResult = void_t();
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!");
|
|
NS_ASSERTION(mType == LocalObject, "Bad type!");
|
|
|
|
PluginInstanceParent* instance = GetInstance();
|
|
if (!instance) {
|
|
NS_ERROR("No instance?!");
|
|
*aResult = void_t();
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance);
|
|
if (!npn) {
|
|
NS_ERROR("No netscape funcs?!");
|
|
*aResult = void_t();
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
StackIdentifier stackID(aId);
|
|
if (stackID.Failed()) {
|
|
*aResult = void_t();
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
AutoFallibleTArray<NPVariant, 10> convertedArgs;
|
|
uint32_t argCount = aArgs.Length();
|
|
|
|
if (!convertedArgs.SetLength(argCount)) {
|
|
*aResult = void_t();
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
for (uint32_t index = 0; index < argCount; index++) {
|
|
if (!ConvertToVariant(aArgs[index], convertedArgs[index], instance)) {
|
|
// Don't leak things we've already converted!
|
|
while (index-- > 0) {
|
|
ReleaseVariant(convertedArgs[index], instance);
|
|
}
|
|
*aResult = void_t();
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
NPVariant result;
|
|
bool success = npn->invoke(instance->GetNPP(), mObject, stackID.ToNPIdentifier(),
|
|
convertedArgs.Elements(), argCount, &result);
|
|
|
|
for (uint32_t index = 0; index < argCount; index++) {
|
|
ReleaseVariant(convertedArgs[index], instance);
|
|
}
|
|
|
|
if (!success) {
|
|
*aResult = void_t();
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
Variant convertedResult;
|
|
success = ConvertToRemoteVariant(result, convertedResult, GetInstance());
|
|
|
|
DeferNPVariantLastRelease(npn, &result);
|
|
|
|
if (!success) {
|
|
*aResult = void_t();
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
*aResult = convertedResult;
|
|
*aSuccess = true;
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
PluginScriptableObjectParent::AnswerInvokeDefault(const InfallibleTArray<Variant>& aArgs,
|
|
Variant* aResult,
|
|
bool* aSuccess)
|
|
{
|
|
if (!mObject) {
|
|
NS_WARNING("Calling AnswerInvoke with an invalidated object!");
|
|
*aResult = void_t();
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!");
|
|
NS_ASSERTION(mType == LocalObject, "Bad type!");
|
|
|
|
PluginInstanceParent* instance = GetInstance();
|
|
if (!instance) {
|
|
NS_ERROR("No instance?!");
|
|
*aResult = void_t();
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance);
|
|
if (!npn) {
|
|
NS_ERROR("No netscape funcs?!");
|
|
*aResult = void_t();
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
AutoFallibleTArray<NPVariant, 10> convertedArgs;
|
|
uint32_t argCount = aArgs.Length();
|
|
|
|
if (!convertedArgs.SetLength(argCount)) {
|
|
*aResult = void_t();
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
for (uint32_t index = 0; index < argCount; index++) {
|
|
if (!ConvertToVariant(aArgs[index], convertedArgs[index], instance)) {
|
|
// Don't leak things we've already converted!
|
|
while (index-- > 0) {
|
|
ReleaseVariant(convertedArgs[index], instance);
|
|
}
|
|
*aResult = void_t();
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
NPVariant result;
|
|
bool success = npn->invokeDefault(instance->GetNPP(), mObject,
|
|
convertedArgs.Elements(), argCount,
|
|
&result);
|
|
|
|
for (uint32_t index = 0; index < argCount; index++) {
|
|
ReleaseVariant(convertedArgs[index], instance);
|
|
}
|
|
|
|
if (!success) {
|
|
*aResult = void_t();
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
Variant convertedResult;
|
|
success = ConvertToRemoteVariant(result, convertedResult, GetInstance());
|
|
|
|
DeferNPVariantLastRelease(npn, &result);
|
|
|
|
if (!success) {
|
|
*aResult = void_t();
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
*aResult = convertedResult;
|
|
*aSuccess = true;
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
PluginScriptableObjectParent::AnswerHasProperty(const PluginIdentifier& aId,
|
|
bool* aHasProperty)
|
|
{
|
|
if (!mObject) {
|
|
NS_WARNING("Calling AnswerHasProperty with an invalidated object!");
|
|
*aHasProperty = false;
|
|
return true;
|
|
}
|
|
|
|
NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!");
|
|
NS_ASSERTION(mType == LocalObject, "Bad type!");
|
|
|
|
PluginInstanceParent* instance = GetInstance();
|
|
if (!instance) {
|
|
NS_ERROR("No instance?!");
|
|
*aHasProperty = false;
|
|
return true;
|
|
}
|
|
|
|
const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance);
|
|
if (!npn) {
|
|
NS_ERROR("No netscape funcs?!");
|
|
*aHasProperty = false;
|
|
return true;
|
|
}
|
|
|
|
StackIdentifier stackID(aId);
|
|
if (stackID.Failed()) {
|
|
*aHasProperty = false;
|
|
return true;
|
|
}
|
|
|
|
*aHasProperty = npn->hasproperty(instance->GetNPP(), mObject,
|
|
stackID.ToNPIdentifier());
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
PluginScriptableObjectParent::AnswerGetParentProperty(
|
|
const PluginIdentifier& aId,
|
|
Variant* aResult,
|
|
bool* aSuccess)
|
|
{
|
|
if (!mObject) {
|
|
NS_WARNING("Calling AnswerGetProperty with an invalidated object!");
|
|
*aResult = void_t();
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!");
|
|
NS_ASSERTION(mType == LocalObject, "Bad type!");
|
|
|
|
PluginInstanceParent* instance = GetInstance();
|
|
if (!instance) {
|
|
NS_ERROR("No instance?!");
|
|
*aResult = void_t();
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance);
|
|
if (!npn) {
|
|
NS_ERROR("No netscape funcs?!");
|
|
*aResult = void_t();
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
StackIdentifier stackID(aId);
|
|
if (stackID.Failed()) {
|
|
*aResult = void_t();
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
NPVariant result;
|
|
if (!npn->getproperty(instance->GetNPP(), mObject, stackID.ToNPIdentifier(),
|
|
&result)) {
|
|
*aResult = void_t();
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
Variant converted;
|
|
if ((*aSuccess = ConvertToRemoteVariant(result, converted, instance))) {
|
|
DeferNPVariantLastRelease(npn, &result);
|
|
*aResult = converted;
|
|
}
|
|
else {
|
|
*aResult = void_t();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
PluginScriptableObjectParent::AnswerSetProperty(const PluginIdentifier& aId,
|
|
const Variant& aValue,
|
|
bool* aSuccess)
|
|
{
|
|
if (!mObject) {
|
|
NS_WARNING("Calling AnswerSetProperty with an invalidated object!");
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!");
|
|
NS_ASSERTION(mType == LocalObject, "Bad type!");
|
|
|
|
PluginInstanceParent* instance = GetInstance();
|
|
if (!instance) {
|
|
NS_ERROR("No instance?!");
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance);
|
|
if (!npn) {
|
|
NS_ERROR("No netscape funcs?!");
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
NPVariant converted;
|
|
if (!ConvertToVariant(aValue, converted, instance)) {
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
StackIdentifier stackID(aId);
|
|
if (stackID.Failed()) {
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
if ((*aSuccess = npn->setproperty(instance->GetNPP(), mObject,
|
|
stackID.ToNPIdentifier(), &converted))) {
|
|
ReleaseVariant(converted, instance);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
PluginScriptableObjectParent::AnswerRemoveProperty(const PluginIdentifier& aId,
|
|
bool* aSuccess)
|
|
{
|
|
if (!mObject) {
|
|
NS_WARNING("Calling AnswerRemoveProperty with an invalidated object!");
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!");
|
|
NS_ASSERTION(mType == LocalObject, "Bad type!");
|
|
|
|
PluginInstanceParent* instance = GetInstance();
|
|
if (!instance) {
|
|
NS_ERROR("No instance?!");
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance);
|
|
if (!npn) {
|
|
NS_ERROR("No netscape funcs?!");
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
StackIdentifier stackID(aId);
|
|
if (stackID.Failed()) {
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
*aSuccess = npn->removeproperty(instance->GetNPP(), mObject,
|
|
stackID.ToNPIdentifier());
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
PluginScriptableObjectParent::AnswerEnumerate(InfallibleTArray<PluginIdentifier>* aProperties,
|
|
bool* aSuccess)
|
|
{
|
|
if (!mObject) {
|
|
NS_WARNING("Calling AnswerEnumerate with an invalidated object!");
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!");
|
|
NS_ASSERTION(mType == LocalObject, "Bad type!");
|
|
|
|
PluginInstanceParent* instance = GetInstance();
|
|
if (!instance) {
|
|
NS_ERROR("No instance?!");
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance);
|
|
if (!npn) {
|
|
NS_WARNING("No netscape funcs?!");
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
NPIdentifier* ids;
|
|
uint32_t idCount;
|
|
if (!npn->enumerate(instance->GetNPP(), mObject, &ids, &idCount)) {
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
aProperties->SetCapacity(idCount);
|
|
|
|
for (uint32_t index = 0; index < idCount; index++) {
|
|
PluginIdentifier id;
|
|
if (!FromNPIdentifier(ids[index], &id)) {
|
|
return false;
|
|
}
|
|
aProperties->AppendElement(id);
|
|
}
|
|
|
|
npn->memfree(ids);
|
|
*aSuccess = true;
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
PluginScriptableObjectParent::AnswerConstruct(const InfallibleTArray<Variant>& aArgs,
|
|
Variant* aResult,
|
|
bool* aSuccess)
|
|
{
|
|
if (!mObject) {
|
|
NS_WARNING("Calling AnswerConstruct with an invalidated object!");
|
|
*aResult = void_t();
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!");
|
|
NS_ASSERTION(mType == LocalObject, "Bad type!");
|
|
|
|
PluginInstanceParent* instance = GetInstance();
|
|
if (!instance) {
|
|
NS_ERROR("No instance?!");
|
|
*aResult = void_t();
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance);
|
|
if (!npn) {
|
|
NS_ERROR("No netscape funcs?!");
|
|
*aResult = void_t();
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
AutoFallibleTArray<NPVariant, 10> convertedArgs;
|
|
uint32_t argCount = aArgs.Length();
|
|
|
|
if (!convertedArgs.SetLength(argCount)) {
|
|
*aResult = void_t();
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
for (uint32_t index = 0; index < argCount; index++) {
|
|
if (!ConvertToVariant(aArgs[index], convertedArgs[index], instance)) {
|
|
// Don't leak things we've already converted!
|
|
while (index-- > 0) {
|
|
ReleaseVariant(convertedArgs[index], instance);
|
|
}
|
|
*aResult = void_t();
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
NPVariant result;
|
|
bool success = npn->construct(instance->GetNPP(), mObject,
|
|
convertedArgs.Elements(), argCount, &result);
|
|
|
|
for (uint32_t index = 0; index < argCount; index++) {
|
|
ReleaseVariant(convertedArgs[index], instance);
|
|
}
|
|
|
|
if (!success) {
|
|
*aResult = void_t();
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
Variant convertedResult;
|
|
success = ConvertToRemoteVariant(result, convertedResult, instance);
|
|
|
|
DeferNPVariantLastRelease(npn, &result);
|
|
|
|
if (!success) {
|
|
*aResult = void_t();
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
*aSuccess = true;
|
|
*aResult = convertedResult;
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
PluginScriptableObjectParent::RecvProtect()
|
|
{
|
|
NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!");
|
|
NS_ASSERTION(mType == LocalObject, "Bad type!");
|
|
|
|
Protect();
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
PluginScriptableObjectParent::RecvUnprotect()
|
|
{
|
|
NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!");
|
|
NS_ASSERTION(mType == LocalObject, "Bad type!");
|
|
|
|
Unprotect();
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
PluginScriptableObjectParent::AnswerNPN_Evaluate(const nsCString& aScript,
|
|
Variant* aResult,
|
|
bool* aSuccess)
|
|
{
|
|
PluginInstanceParent* instance = GetInstance();
|
|
if (!instance) {
|
|
NS_ERROR("No instance?!");
|
|
*aResult = void_t();
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance);
|
|
if (!npn) {
|
|
NS_ERROR("No netscape funcs?!");
|
|
*aResult = void_t();
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
NPString script = { aScript.get(), aScript.Length() };
|
|
|
|
NPVariant result;
|
|
bool success = npn->evaluate(instance->GetNPP(), mObject, &script, &result);
|
|
if (!success) {
|
|
*aResult = void_t();
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
Variant convertedResult;
|
|
success = ConvertToRemoteVariant(result, convertedResult, instance);
|
|
|
|
DeferNPVariantLastRelease(npn, &result);
|
|
|
|
if (!success) {
|
|
*aResult = void_t();
|
|
*aSuccess = false;
|
|
return true;
|
|
}
|
|
|
|
*aSuccess = true;
|
|
*aResult = convertedResult;
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
PluginScriptableObjectParent::GetPropertyHelper(NPIdentifier aName,
|
|
bool* aHasProperty,
|
|
bool* aHasMethod,
|
|
NPVariant* aResult)
|
|
{
|
|
NS_ASSERTION(Type() == Proxy, "Bad type!");
|
|
|
|
ParentNPObject* object = static_cast<ParentNPObject*>(mObject);
|
|
if (object->invalidated) {
|
|
NS_WARNING("Calling method on an invalidated object!");
|
|
return false;
|
|
}
|
|
|
|
PluginIdentifier identifier;
|
|
if (!FromNPIdentifier(aName, &identifier)) {
|
|
return false;
|
|
}
|
|
|
|
bool hasProperty, hasMethod, success;
|
|
Variant result;
|
|
if (!CallGetChildProperty(identifier, &hasProperty, &hasMethod, &result,
|
|
&success)) {
|
|
return false;
|
|
}
|
|
|
|
if (!success) {
|
|
return false;
|
|
}
|
|
|
|
if (!ConvertToVariant(result, *aResult, GetInstance())) {
|
|
NS_WARNING("Failed to convert result!");
|
|
return false;
|
|
}
|
|
|
|
*aHasProperty = hasProperty;
|
|
*aHasMethod = hasMethod;
|
|
return true;
|
|
}
|