/* * Copyright (C) 2004, 2006 Apple Computer, Inc. All rights reserved. * Copyright (C) 2007-2009 Google, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "NPV8Object.h" #include "npruntime_impl.h" #include "npruntime_priv.h" #include "V8NPObject.h" #include #include #include // FIXME: Consider removing locks if we're singlethreaded already. // The static initializer here should work okay, but we want to avoid // static initialization in general. namespace npruntime { // We use StringKey here as the key-type to avoid a string copy to // construct the map key and for faster comparisons than strcmp. class StringKey { public: explicit StringKey(const char* str) : m_string(str), m_length(strlen(str)) { } StringKey() : m_string(0), m_length(0) { } explicit StringKey(WTF::HashTableDeletedValueType) : m_string(hashTableDeletedValue()), m_length(0) { } StringKey& operator=(const StringKey& other) { this->m_string = other.m_string; this->m_length = other.m_length; return *this; } bool isHashTableDeletedValue() const { return m_string == hashTableDeletedValue(); } const char* m_string; size_t m_length; private: const char* hashTableDeletedValue() const { return reinterpret_cast(-1); } }; inline bool operator==(const StringKey& x, const StringKey& y) { if (x.m_length != y.m_length) return false; if (x.m_string == y.m_string) return true; ASSERT(!x.isHashTableDeletedValue() && !y.isHashTableDeletedValue()); return !memcmp(x.m_string, y.m_string, y.m_length); } // Implement WTF::DefaultHash::Hash interface. struct StringKeyHash { static unsigned hash(const StringKey& key) { // Compute string hash. unsigned hash = 0; size_t len = key.m_length; const char* str = key.m_string; for (size_t i = 0; i < len; i++) { char c = str[i]; hash += c; hash += (hash << 10); hash ^= (hash >> 6); } hash += (hash << 3); hash ^= (hash >> 11); hash += (hash << 15); if (hash == 0) hash = 27; return hash; } static bool equal(const StringKey& x, const StringKey& y) { return x == y; } static const bool safeToCompareToEmptyOrDeleted = true; }; } // namespace npruntime using npruntime::StringKey; using npruntime::StringKeyHash; // Implement HashTraits struct StringKeyHashTraits : WTF::GenericHashTraits { static void constructDeletedValue(StringKey& slot) { new (&slot) StringKey(WTF::HashTableDeletedValue); } static bool isDeletedValue(const StringKey& value) { return value.isHashTableDeletedValue(); } }; typedef WTF::HashMap StringIdentifierMap; static StringIdentifierMap* getStringIdentifierMap() { static StringIdentifierMap* stringIdentifierMap = 0; if (!stringIdentifierMap) stringIdentifierMap = new StringIdentifierMap(); return stringIdentifierMap; } typedef WTF::HashMap IntIdentifierMap; static IntIdentifierMap* getIntIdentifierMap() { static IntIdentifierMap* intIdentifierMap = 0; if (!intIdentifierMap) intIdentifierMap = new IntIdentifierMap(); return intIdentifierMap; } extern "C" { NPIdentifier _NPN_GetStringIdentifier(const NPUTF8* name) { ASSERT(name); if (name) { StringKey key(name); StringIdentifierMap* identMap = getStringIdentifierMap(); StringIdentifierMap::iterator iter = identMap->find(key); if (iter != identMap->end()) return static_cast(iter->second); size_t nameLen = key.m_length; // We never release identifiers, so this dictionary will grow. PrivateIdentifier* identifier = static_cast(malloc(sizeof(PrivateIdentifier) + nameLen + 1)); char* nameStorage = reinterpret_cast(identifier + 1); memcpy(nameStorage, name, nameLen + 1); identifier->isString = true; identifier->value.string = reinterpret_cast(nameStorage); key.m_string = nameStorage; identMap->set(key, identifier); return (NPIdentifier)identifier; } return 0; } void _NPN_GetStringIdentifiers(const NPUTF8** names, int32_t nameCount, NPIdentifier* identifiers) { ASSERT(names); ASSERT(identifiers); if (names && identifiers) { for (int i = 0; i < nameCount; i++) identifiers[i] = _NPN_GetStringIdentifier(names[i]); } } NPIdentifier _NPN_GetIntIdentifier(int32_t intId) { // Special case for -1 and 0, both cannot be used as key in HashMap. if (!intId || intId == -1) { static PrivateIdentifier* minusOneOrZeroIds[2]; PrivateIdentifier* id = minusOneOrZeroIds[intId + 1]; if (!id) { id = reinterpret_cast(malloc(sizeof(PrivateIdentifier))); id->isString = false; id->value.number = intId; minusOneOrZeroIds[intId + 1] = id; } return (NPIdentifier) id; } IntIdentifierMap* identMap = getIntIdentifierMap(); IntIdentifierMap::iterator iter = identMap->find(intId); if (iter != identMap->end()) return static_cast(iter->second); // We never release identifiers, so this dictionary will grow. PrivateIdentifier* identifier = reinterpret_cast(malloc(sizeof(PrivateIdentifier))); identifier->isString = false; identifier->value.number = intId; identMap->set(intId, identifier); return (NPIdentifier)identifier; } bool _NPN_IdentifierIsString(NPIdentifier identifier) { PrivateIdentifier* privateIdentifier = reinterpret_cast(identifier); return privateIdentifier->isString; } NPUTF8 *_NPN_UTF8FromIdentifier(NPIdentifier identifier) { PrivateIdentifier* privateIdentifier = reinterpret_cast(identifier); if (!privateIdentifier->isString || !privateIdentifier->value.string) return 0; return (NPUTF8*) strdup(privateIdentifier->value.string); } int32_t _NPN_IntFromIdentifier(NPIdentifier identifier) { PrivateIdentifier* privateIdentifier = reinterpret_cast(identifier); if (privateIdentifier->isString) return 0; return privateIdentifier->value.number; } void _NPN_ReleaseVariantValue(NPVariant* variant) { ASSERT(variant); if (variant->type == NPVariantType_Object) { _NPN_ReleaseObject(variant->value.objectValue); variant->value.objectValue = 0; } else if (variant->type == NPVariantType_String) { free((void*)variant->value.stringValue.UTF8Characters); variant->value.stringValue.UTF8Characters = 0; variant->value.stringValue.UTF8Length = 0; } variant->type = NPVariantType_Void; } NPObject *_NPN_CreateObject(NPP npp, NPClass* npClass) { ASSERT(npClass); if (npClass) { NPObject* npObject; if (npClass->allocate != 0) npObject = npClass->allocate(npp, npClass); else npObject = reinterpret_cast(malloc(sizeof(NPObject))); npObject->_class = npClass; npObject->referenceCount = 1; return npObject; } return 0; } NPObject* _NPN_RetainObject(NPObject* npObject) { ASSERT(npObject); ASSERT(npObject->referenceCount > 0); if (npObject) npObject->referenceCount++; return npObject; } // _NPN_DeallocateObject actually deletes the object. Technically, // callers should use _NPN_ReleaseObject. Webkit exposes this function // to kill objects which plugins may not have properly released. void _NPN_DeallocateObject(NPObject* npObject) { ASSERT(npObject); ASSERT(npObject->referenceCount >= 0); if (npObject) { // NPObjects that remain in pure C++ may never have wrappers. // Hence, if it's not already alive, don't unregister it. // If it is alive, unregister it as the *last* thing we do // so that it can do as much cleanup as possible on its own. if (_NPN_IsAlive(npObject)) _NPN_UnregisterObject(npObject); npObject->referenceCount = -1; if (npObject->_class->deallocate) npObject->_class->deallocate(npObject); else free(npObject); } } #if PLATFORM(ANDROID) // Android uses NPN_ReleaseObject (the 'public' version of _NPN_ReleaseObject) // in WebCoreFrameBridge.cpp. See http://trac.webkit.org/changeset/47021. // TODO: Upstream this to webkit.org. void NPN_ReleaseObject(NPObject *obj) { _NPN_ReleaseObject(obj); } #endif void _NPN_ReleaseObject(NPObject* npObject) { ASSERT(npObject); ASSERT(npObject->referenceCount >= 1); if (npObject && npObject->referenceCount >= 1) { if (!--npObject->referenceCount) _NPN_DeallocateObject(npObject); } } void _NPN_InitializeVariantWithStringCopy(NPVariant* variant, const NPString* value) { variant->type = NPVariantType_String; variant->value.stringValue.UTF8Length = value->UTF8Length; variant->value.stringValue.UTF8Characters = reinterpret_cast(malloc(sizeof(NPUTF8) * value->UTF8Length)); memcpy((void*)variant->value.stringValue.UTF8Characters, value->UTF8Characters, sizeof(NPUTF8) * value->UTF8Length); } // NPN_Registry // // The registry is designed for quick lookup of NPObjects. // JS needs to be able to quickly lookup a given NPObject to determine // if it is alive or not. // The browser needs to be able to quickly lookup all NPObjects which are // "owned" by an object. // // The liveObjectMap is a hash table of all live objects to their owner // objects. Presence in this table is used primarily to determine if // objects are live or not. // // The rootObjectMap is a hash table of root objects to a set of // objects that should be deactivated in sync with the root. A // root is defined as a top-level owner object. This is used on // Frame teardown to deactivate all objects associated // with a particular plugin. typedef WTF::HashSet NPObjectSet; typedef WTF::HashMap NPObjectMap; typedef WTF::HashMap NPRootObjectMap; // A map of live NPObjects with pointers to their Roots. NPObjectMap liveObjectMap; // A map of the root objects and the list of NPObjects // associated with that object. NPRootObjectMap rootObjectMap; void _NPN_RegisterObject(NPObject* npObject, NPObject* owner) { ASSERT(npObject); // Check if already registered. if (liveObjectMap.find(npObject) != liveObjectMap.end()) return; if (!owner) { // Registering a new owner object. ASSERT(rootObjectMap.find(npObject) == rootObjectMap.end()); rootObjectMap.set(npObject, new NPObjectSet()); } else { // Always associate this object with it's top-most parent. // Since we always flatten, we only have to look up one level. NPObjectMap::iterator ownerEntry = liveObjectMap.find(owner); NPObject* parent = 0; if (liveObjectMap.end() != ownerEntry) parent = ownerEntry->second; if (parent) owner = parent; ASSERT(rootObjectMap.find(npObject) == rootObjectMap.end()); if (rootObjectMap.find(owner) != rootObjectMap.end()) rootObjectMap.get(owner)->add(npObject); } ASSERT(liveObjectMap.find(npObject) == liveObjectMap.end()); liveObjectMap.set(npObject, owner); } void _NPN_UnregisterObject(NPObject* npObject) { ASSERT(npObject); ASSERT(liveObjectMap.find(npObject) != liveObjectMap.end()); NPObject* owner = 0; if (liveObjectMap.find(npObject) != liveObjectMap.end()) owner = liveObjectMap.find(npObject)->second; if (!owner) { // Unregistering a owner object; also unregister it's descendants. ASSERT(rootObjectMap.find(npObject) != rootObjectMap.end()); NPObjectSet* set = rootObjectMap.get(npObject); while (set->size() > 0) { #ifndef NDEBUG int size = set->size(); #endif NPObject* sub_object = *(set->begin()); // The sub-object should not be a owner! ASSERT(rootObjectMap.find(sub_object) == rootObjectMap.end()); // First, unregister the object. set->remove(sub_object); liveObjectMap.remove(sub_object); // Remove the JS references to the object. forgetV8ObjectForNPObject(sub_object); ASSERT(set->size() < size); } delete set; rootObjectMap.remove(npObject); } else { NPRootObjectMap::iterator ownerEntry = rootObjectMap.find(owner); if (ownerEntry != rootObjectMap.end()) { NPObjectSet* list = ownerEntry->second; ASSERT(list->find(npObject) != list->end()); list->remove(npObject); } } liveObjectMap.remove(npObject); forgetV8ObjectForNPObject(npObject); } bool _NPN_IsAlive(NPObject* npObject) { return liveObjectMap.find(npObject) != liveObjectMap.end(); } } // extern "C"