/* * Copyright (C) 2006, 2007, 2008, 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: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "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 THE COPYRIGHT * OWNER 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 "V8Binding.h" #include "DOMStringList.h" #include "Element.h" #include "MathExtras.h" #include "PlatformString.h" #include "QualifiedName.h" #include "StdLibExtras.h" #include "Threading.h" #include "V8Element.h" #include "V8Proxy.h" #include <wtf/text/AtomicString.h> #include <wtf/text/CString.h> #include <wtf/text/StringBuffer.h> #include <wtf/text/StringHash.h> namespace WebCore { // WebCoreStringResource is a helper class for v8ExternalString. It is used // to manage the life-cycle of the underlying buffer of the external string. class WebCoreStringResource : public v8::String::ExternalStringResource { public: explicit WebCoreStringResource(const String& string) : m_plainString(string) { #ifndef NDEBUG m_threadId = WTF::currentThread(); #endif ASSERT(!string.isNull()); v8::V8::AdjustAmountOfExternalAllocatedMemory(2 * string.length()); } explicit WebCoreStringResource(const AtomicString& string) : m_plainString(string.string()) , m_atomicString(string) { #ifndef NDEBUG m_threadId = WTF::currentThread(); #endif ASSERT(!string.isNull()); v8::V8::AdjustAmountOfExternalAllocatedMemory(2 * string.length()); } virtual ~WebCoreStringResource() { #ifndef NDEBUG ASSERT(m_threadId == WTF::currentThread()); #endif int reducedExternalMemory = -2 * m_plainString.length(); if (m_plainString.impl() != m_atomicString.impl() && !m_atomicString.isNull()) reducedExternalMemory *= 2; v8::V8::AdjustAmountOfExternalAllocatedMemory(reducedExternalMemory); } virtual const uint16_t* data() const { return reinterpret_cast<const uint16_t*>(m_plainString.impl()->characters()); } virtual size_t length() const { return m_plainString.impl()->length(); } String webcoreString() { return m_plainString; } AtomicString atomicString() { #ifndef NDEBUG ASSERT(m_threadId == WTF::currentThread()); #endif if (m_atomicString.isNull()) { m_atomicString = AtomicString(m_plainString); ASSERT(!m_atomicString.isNull()); if (m_plainString.impl() != m_atomicString.impl()) v8::V8::AdjustAmountOfExternalAllocatedMemory(2 * m_atomicString.length()); } return m_atomicString; } static WebCoreStringResource* toStringResource(v8::Handle<v8::String> v8String) { return static_cast<WebCoreStringResource*>(v8String->GetExternalStringResource()); } private: // A shallow copy of the string. Keeps the string buffer alive until the V8 engine garbage collects it. String m_plainString; // If this string is atomic or has been made atomic earlier the // atomic string is held here. In the case where the string starts // off non-atomic and becomes atomic later it is necessary to keep // the original string alive because v8 may keep derived pointers // into that string. AtomicString m_atomicString; #ifndef NDEBUG WTF::ThreadIdentifier m_threadId; #endif }; String v8ValueToWebCoreString(v8::Handle<v8::Value> value) { if (value->IsString()) return v8StringToWebCoreString(v8::Handle<v8::String>::Cast(value)); return v8NonStringValueToWebCoreString(value); } AtomicString v8ValueToAtomicWebCoreString(v8::Handle<v8::Value> value) { if (value->IsString()) return v8StringToAtomicWebCoreString(v8::Handle<v8::String>::Cast(value)); return v8NonStringValueToAtomicWebCoreString(value); } int toInt32(v8::Handle<v8::Value> value, bool& ok) { ok = true; // Fast case. The value is already a 32-bit integer. if (value->IsInt32()) return value->Int32Value(); // Can the value be converted to a number? v8::Local<v8::Number> numberObject = value->ToNumber(); if (numberObject.IsEmpty()) { ok = false; return 0; } // Does the value convert to nan or to an infinity? double numberValue = numberObject->Value(); if (isnan(numberValue) || isinf(numberValue)) { ok = false; return 0; } // Can the value be converted to a 32-bit integer? v8::Local<v8::Int32> intValue = value->ToInt32(); if (intValue.IsEmpty()) { ok = false; return 0; } // Return the result of the int32 conversion. return intValue->Value(); } uint32_t toUInt32(v8::Handle<v8::Value> value, bool& ok) { ok = true; // FIXME: there is currently no Value::IsUint32(). This code does // some contortions to avoid silently converting out-of-range // values to uint32_t. // Fast case. The value is already a 32-bit positive integer. if (value->IsInt32()) { int32_t result = value->Int32Value(); if (result >= 0) return result; } // Can the value be converted to a number? v8::Local<v8::Number> numberObject = value->ToNumber(); if (numberObject.IsEmpty()) { ok = false; return 0; } // Does the value convert to nan or to an infinity? double numberValue = numberObject->Value(); if (isnan(numberValue) || isinf(numberValue)) { ok = false; return 0; } // Can the value be converted to a 32-bit unsigned integer? v8::Local<v8::Uint32> uintValue = value->ToUint32(); if (uintValue.IsEmpty()) { ok = false; return 0; } // FIXME: v8::Uint32::Value is not defined! // http://code.google.com/p/v8/issues/detail?id=624 v8::Local<v8::Int32> intValue = value->ToInt32(); if (intValue.IsEmpty()) { ok = false; return 0; } return static_cast<uint32_t>(intValue->Value()); } String toWebCoreString(const v8::Arguments& args, int index) { return v8ValueToWebCoreString(args[index]); } String toWebCoreStringWithNullCheck(v8::Handle<v8::Value> value) { if (value->IsNull()) return String(); return v8ValueToWebCoreString(value); } AtomicString toAtomicWebCoreStringWithNullCheck(v8::Handle<v8::Value> value) { if (value->IsNull()) return AtomicString(); return v8ValueToAtomicWebCoreString(value); } String toWebCoreStringWithNullOrUndefinedCheck(v8::Handle<v8::Value> value) { if (value->IsNull() || value->IsUndefined()) return String(); return toWebCoreString(value); } bool isUndefinedOrNull(v8::Handle<v8::Value> value) { return value->IsNull() || value->IsUndefined(); } v8::Handle<v8::Boolean> v8Boolean(bool value) { return value ? v8::True() : v8::False(); } v8::Handle<v8::String> v8UndetectableString(const String& str) { return v8::String::NewUndetectable(fromWebCoreString(str), str.length()); } v8::Handle<v8::Value> v8StringOrNull(const String& str) { return str.isNull() ? v8::Handle<v8::Value>(v8::Null()) : v8::Handle<v8::Value>(v8String(str)); } v8::Handle<v8::Value> v8StringOrUndefined(const String& str) { return str.isNull() ? v8::Handle<v8::Value>(v8::Undefined()) : v8::Handle<v8::Value>(v8String(str)); } v8::Handle<v8::Value> v8StringOrFalse(const String& str) { return str.isNull() ? v8::Handle<v8::Value>(v8::False()) : v8::Handle<v8::Value>(v8String(str)); } double toWebCoreDate(v8::Handle<v8::Value> object) { return (object->IsDate() || object->IsNumber()) ? object->NumberValue() : std::numeric_limits<double>::quiet_NaN(); } v8::Handle<v8::Value> v8DateOrNull(double value) { if (isfinite(value)) return v8::Date::New(value); return v8::Null(); } template <class S> struct StringTraits { static S fromStringResource(WebCoreStringResource* resource); static S fromV8String(v8::Handle<v8::String> v8String, int length); }; template<> struct StringTraits<String> { static String fromStringResource(WebCoreStringResource* resource) { return resource->webcoreString(); } static String fromV8String(v8::Handle<v8::String> v8String, int length) { ASSERT(v8String->Length() == length); // NOTE: as of now, String(const UChar*, int) performs String::createUninitialized // anyway, so no need to optimize like we do for AtomicString below. UChar* buffer; String result = String::createUninitialized(length, buffer); v8String->Write(reinterpret_cast<uint16_t*>(buffer), 0, length); return result; } }; template<> struct StringTraits<AtomicString> { static AtomicString fromStringResource(WebCoreStringResource* resource) { return resource->atomicString(); } static AtomicString fromV8String(v8::Handle<v8::String> v8String, int length) { ASSERT(v8String->Length() == length); static const int inlineBufferSize = 16; if (length <= inlineBufferSize) { UChar inlineBuffer[inlineBufferSize]; v8String->Write(reinterpret_cast<uint16_t*>(inlineBuffer), 0, length); return AtomicString(inlineBuffer, length); } UChar* buffer; String tmp = String::createUninitialized(length, buffer); v8String->Write(reinterpret_cast<uint16_t*>(buffer), 0, length); return AtomicString(tmp); } }; template <typename StringType> StringType v8StringToWebCoreString(v8::Handle<v8::String> v8String, ExternalMode external) { WebCoreStringResource* stringResource = WebCoreStringResource::toStringResource(v8String); if (stringResource) return StringTraits<StringType>::fromStringResource(stringResource); int length = v8String->Length(); if (!length) { // Avoid trying to morph empty strings, as they do not have enough room to contain the external reference. return StringImpl::empty(); } StringType result(StringTraits<StringType>::fromV8String(v8String, length)); if (external == Externalize && v8String->CanMakeExternal()) { stringResource = new WebCoreStringResource(result); if (!v8String->MakeExternal(stringResource)) { // In case of a failure delete the external resource as it was not used. delete stringResource; } } return result; } // Explicitly instantiate the above template with the expected parameterizations, // to ensure the compiler generates the code; otherwise link errors can result in GCC 4.4. template String v8StringToWebCoreString<String>(v8::Handle<v8::String>, ExternalMode); template AtomicString v8StringToWebCoreString<AtomicString>(v8::Handle<v8::String>, ExternalMode); // Fast but non thread-safe version. String int32ToWebCoreStringFast(int value) { // Caching of small strings below is not thread safe: newly constructed AtomicString // are not safely published. ASSERT(WTF::isMainThread()); // Most numbers used are <= 100. Even if they aren't used there's very little cost in using the space. const int kLowNumbers = 100; DEFINE_STATIC_LOCAL(Vector<AtomicString>, lowNumbers, (kLowNumbers + 1)); String webCoreString; if (0 <= value && value <= kLowNumbers) { webCoreString = lowNumbers[value]; if (!webCoreString) { AtomicString valueString = AtomicString(String::number(value)); lowNumbers[value] = valueString; webCoreString = valueString; } } else webCoreString = String::number(value); return webCoreString; } String int32ToWebCoreString(int value) { // If we are on the main thread (this should always true for non-workers), call the faster one. if (WTF::isMainThread()) return int32ToWebCoreStringFast(value); return String::number(value); } String v8NonStringValueToWebCoreString(v8::Handle<v8::Value> object) { ASSERT(!object->IsString()); if (object->IsInt32()) return int32ToWebCoreString(object->Int32Value()); v8::TryCatch block; v8::Handle<v8::String> v8String = object->ToString(); // Handle the case where an exception is thrown as part of invoking toString on the object. if (block.HasCaught()) { throwError(block.Exception()); return StringImpl::empty(); } // This path is unexpected. However there is hypothesis that it // might be combination of v8 and v8 bindings bugs. For now // just bailout as we'll crash if attempt to convert empty handle into a string. if (v8String.IsEmpty()) { ASSERT_NOT_REACHED(); return StringImpl::empty(); } return v8StringToWebCoreString<String>(v8String, DoNotExternalize); } AtomicString v8NonStringValueToAtomicWebCoreString(v8::Handle<v8::Value> object) { ASSERT(!object->IsString()); return AtomicString(v8NonStringValueToWebCoreString(object)); } static bool stringImplCacheEnabled = false; void enableStringImplCache() { stringImplCacheEnabled = true; } static v8::Local<v8::String> makeExternalString(const String& string) { WebCoreStringResource* stringResource = new WebCoreStringResource(string); v8::Local<v8::String> newString = v8::String::NewExternal(stringResource); if (newString.IsEmpty()) delete stringResource; return newString; } typedef HashMap<StringImpl*, v8::String*> StringCache; static StringCache& getStringCache() { ASSERT(WTF::isMainThread()); DEFINE_STATIC_LOCAL(StringCache, mainThreadStringCache, ()); return mainThreadStringCache; } static void cachedStringCallback(v8::Persistent<v8::Value> wrapper, void* parameter) { ASSERT(WTF::isMainThread()); StringImpl* stringImpl = static_cast<StringImpl*>(parameter); ASSERT(getStringCache().contains(stringImpl)); getStringCache().remove(stringImpl); wrapper.Dispose(); stringImpl->deref(); } RefPtr<StringImpl> lastStringImpl = 0; v8::Persistent<v8::String> lastV8String; v8::Local<v8::String> v8ExternalStringSlow(StringImpl* stringImpl) { if (!stringImpl->length()) return v8::String::Empty(); if (!stringImplCacheEnabled) return makeExternalString(String(stringImpl)); StringCache& stringCache = getStringCache(); v8::String* cachedV8String = stringCache.get(stringImpl); if (cachedV8String) { v8::Persistent<v8::String> handle(cachedV8String); if (!handle.IsNearDeath() && !handle.IsEmpty()) { lastStringImpl = stringImpl; lastV8String = handle; return v8::Local<v8::String>::New(handle); } } v8::Local<v8::String> newString = makeExternalString(String(stringImpl)); if (newString.IsEmpty()) return newString; v8::Persistent<v8::String> wrapper = v8::Persistent<v8::String>::New(newString); if (wrapper.IsEmpty()) return newString; stringImpl->ref(); wrapper.MakeWeak(stringImpl, cachedStringCallback); stringCache.set(stringImpl, *wrapper); lastStringImpl = stringImpl; lastV8String = wrapper; return newString; } v8::Persistent<v8::FunctionTemplate> createRawTemplate() { v8::HandleScope scope; v8::Local<v8::FunctionTemplate> result = v8::FunctionTemplate::New(V8Proxy::checkNewLegal); return v8::Persistent<v8::FunctionTemplate>::New(result); } v8::Local<v8::Signature> configureTemplate(v8::Persistent<v8::FunctionTemplate> desc, const char *interfaceName, v8::Persistent<v8::FunctionTemplate> parentClass, int fieldCount, const BatchedAttribute* attributes, size_t attributeCount, const BatchedCallback* callbacks, size_t callbackCount) { desc->SetClassName(v8::String::New(interfaceName)); v8::Local<v8::ObjectTemplate> instance = desc->InstanceTemplate(); instance->SetInternalFieldCount(fieldCount); if (!parentClass.IsEmpty()) desc->Inherit(parentClass); if (attributeCount) batchConfigureAttributes(instance, desc->PrototypeTemplate(), attributes, attributeCount); v8::Local<v8::Signature> defaultSignature = v8::Signature::New(desc); if (callbackCount) batchConfigureCallbacks(desc->PrototypeTemplate(), defaultSignature, static_cast<v8::PropertyAttribute>(v8::DontDelete), callbacks, callbackCount); return defaultSignature; } v8::Persistent<v8::String> getToStringName() { DEFINE_STATIC_LOCAL(v8::Persistent<v8::String>, value, ()); if (value.IsEmpty()) value = v8::Persistent<v8::String>::New(v8::String::New("toString")); return value; } static v8::Handle<v8::Value> constructorToString(const v8::Arguments& args) { // The DOM constructors' toString functions grab the current toString // for Functions by taking the toString function of itself and then // calling it with the constructor as its receiver. This means that // changes to the Function prototype chain or toString function are // reflected when printing DOM constructors. The only wart is that // changes to a DOM constructor's toString's toString will cause the // toString of the DOM constructor itself to change. This is extremely // obscure and unlikely to be a problem. v8::Handle<v8::Value> value = args.Callee()->Get(getToStringName()); if (!value->IsFunction()) return v8::String::New(""); return v8::Handle<v8::Function>::Cast(value)->Call(args.This(), 0, 0); } v8::Persistent<v8::FunctionTemplate> getToStringTemplate() { DEFINE_STATIC_LOCAL(v8::Persistent<v8::FunctionTemplate>, toStringTemplate, ()); if (toStringTemplate.IsEmpty()) toStringTemplate = v8::Persistent<v8::FunctionTemplate>::New(v8::FunctionTemplate::New(constructorToString)); return toStringTemplate; } v8::Handle<v8::Value> getElementStringAttr(const v8::AccessorInfo& info, const QualifiedName& name) { Element* imp = V8Element::toNative(info.Holder()); return v8ExternalString(imp->getAttribute(name)); } void setElementStringAttr(const v8::AccessorInfo& info, const QualifiedName& name, v8::Local<v8::Value> value) { Element* imp = V8Element::toNative(info.Holder()); AtomicString v = toAtomicWebCoreStringWithNullCheck(value); imp->setAttribute(name, v); } PassRefPtr<DOMStringList> v8ValueToWebCoreDOMStringList(v8::Handle<v8::Value> value) { v8::Local<v8::Value> v8Value(v8::Local<v8::Value>::New(value)); if (!v8Value->IsArray()) return 0; RefPtr<DOMStringList> ret = DOMStringList::create(); v8::Local<v8::Array> v8Array = v8::Local<v8::Array>::Cast(v8Value); for (size_t i = 0; i < v8Array->Length(); ++i) { v8::Local<v8::Value> indexedValue = v8Array->Get(v8::Integer::New(i)); ret->append(v8ValueToWebCoreString(indexedValue)); } return ret.release(); } } // namespace WebCore