blob: 37e3dbb7faded21038b3f668c33fbcee6af7cba5 [file] [log] [blame]
// Copyright (c) 2008, 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 <algorithm>
#include <utility>
#include <v8.h>
#ifdef ENABLE_DEBUGGER_SUPPORT
#include <v8-debug.h>
#endif
#include "v8_proxy.h"
#include "v8_index.h"
#include "v8_binding.h"
#include "V8CustomBinding.h"
#include "V8Collection.h"
#include "V8DOMWindow.h"
#if PLATFORM(CHROMIUM)
#include "ChromiumBridge.h"
#endif
#include "CSSMutableStyleDeclaration.h"
#include "DOMObjectsInclude.h"
#include "DocumentLoader.h"
#include "ScriptController.h"
#include "V8DOMMap.h"
#include "CString.h"
#ifdef ANDROID_INSTRUMENT
#include "TimeCounter.h"
#endif
namespace WebCore {
// Static utility context.
v8::Persistent<v8::Context> V8Proxy::m_utilityContext;
// Static list of registered extensions
V8ExtensionList V8Proxy::m_extensions;
#ifndef NDEBUG
// Keeps track of global handles created (not JS wrappers
// of DOM objects). Often these global handles are source
// of leaks.
//
// If you want to let a C++ object hold a persistent handle
// to a JS object, you should register the handle here to
// keep track of leaks.
//
// When creating a persistent handle, call:
//
// #ifndef NDEBUG
// V8Proxy::RegisterGlobalHandle(type, host, handle);
// #endif
//
// When releasing the handle, call:
//
// #ifndef NDEBUG
// V8Proxy::UnregisterGlobalHandle(type, host, handle);
// #endif
//
typedef HashMap<v8::Value*, GlobalHandleInfo*> GlobalHandleMap;
static GlobalHandleMap& global_handle_map()
{
static GlobalHandleMap static_global_handle_map;
return static_global_handle_map;
}
// The USE_VAR(x) template is used to silence C++ compiler warnings
// issued for unused variables (typically parameters or values that
// we want to watch in the debugger).
template <typename T>
static inline void USE_VAR(T) { }
// The function is the place to set the break point to inspect
// live global handles. Leaks are often come from leaked global handles.
static void EnumerateGlobalHandles()
{
for (GlobalHandleMap::iterator it = global_handle_map().begin(),
end = global_handle_map().end(); it != end; ++it) {
GlobalHandleInfo* info = it->second;
USE_VAR(info);
v8::Value* handle = it->first;
USE_VAR(handle);
}
}
void V8Proxy::RegisterGlobalHandle(GlobalHandleType type, void* host,
v8::Persistent<v8::Value> handle)
{
ASSERT(!global_handle_map().contains(*handle));
global_handle_map().set(*handle, new GlobalHandleInfo(host, type));
}
void V8Proxy::UnregisterGlobalHandle(void* host, v8::Persistent<v8::Value> handle)
{
ASSERT(global_handle_map().contains(*handle));
GlobalHandleInfo* info = global_handle_map().take(*handle);
ASSERT(info->host_ == host);
delete info;
}
#endif // ifndef NDEBUG
void BatchConfigureAttributes(v8::Handle<v8::ObjectTemplate> inst,
v8::Handle<v8::ObjectTemplate> proto,
const BatchedAttribute* attrs,
size_t num_attrs)
{
for (size_t i = 0; i < num_attrs; ++i) {
const BatchedAttribute* a = &attrs[i];
(a->on_proto ? proto : inst)->SetAccessor(
v8::String::New(a->name),
a->getter,
a->setter,
a->data == V8ClassIndex::INVALID_CLASS_INDEX
? v8::Handle<v8::Value>()
: v8::Integer::New(V8ClassIndex::ToInt(a->data)),
a->settings,
a->attribute);
}
}
void BatchConfigureConstants(v8::Handle<v8::FunctionTemplate> desc,
v8::Handle<v8::ObjectTemplate> proto,
const BatchedConstant* consts,
size_t num_consts)
{
for (size_t i = 0; i < num_consts; ++i) {
const BatchedConstant* c = &consts[i];
desc->Set(v8::String::New(c->name),
v8::Integer::New(c->value),
v8::ReadOnly);
proto->Set(v8::String::New(c->name),
v8::Integer::New(c->value),
v8::ReadOnly);
}
}
typedef HashMap<Node*, v8::Object*> DOMNodeMap;
typedef HashMap<void*, v8::Object*> DOMObjectMap;
#ifndef NDEBUG
static void EnumerateDOMObjectMap(DOMObjectMap& wrapper_map)
{
for (DOMObjectMap::iterator it = wrapper_map.begin(), end = wrapper_map.end();
it != end; ++it) {
v8::Persistent<v8::Object> wrapper(it->second);
V8ClassIndex::V8WrapperType type = V8Proxy::GetDOMWrapperType(wrapper);
void* obj = it->first;
USE_VAR(type);
USE_VAR(obj);
}
}
static void EnumerateDOMNodeMap(DOMNodeMap& node_map)
{
for (DOMNodeMap::iterator it = node_map.begin(), end = node_map.end();
it != end; ++it) {
Node* node = it->first;
USE_VAR(node);
ASSERT(v8::Persistent<v8::Object>(it->second).IsWeak());
}
}
#endif // NDEBUG
#if ENABLE(SVG)
v8::Handle<v8::Value> V8Proxy::SVGElementInstanceToV8Object(
SVGElementInstance* instance)
{
if (!instance)
return v8::Null();
v8::Handle<v8::Object> existing_instance = getDOMSVGElementInstanceMap().get(instance);
if (!existing_instance.IsEmpty())
return existing_instance;
instance->ref();
// Instantiate the V8 object and remember it
v8::Handle<v8::Object> result =
InstantiateV8Object(V8ClassIndex::SVGELEMENTINSTANCE,
V8ClassIndex::SVGELEMENTINSTANCE,
instance);
if (!result.IsEmpty()) {
// Only update the DOM SVG element map if the result is non-empty.
getDOMSVGElementInstanceMap().set(instance,
v8::Persistent<v8::Object>::New(result));
}
return result;
}
// Map of SVG objects with contexts to their contexts
static HashMap<void*, SVGElement*>& svg_object_to_context_map()
{
static HashMap<void*, SVGElement*> static_svg_object_to_context_map;
return static_svg_object_to_context_map;
}
v8::Handle<v8::Value> V8Proxy::SVGObjectWithContextToV8Object(
V8ClassIndex::V8WrapperType type, void* object)
{
if (!object)
return v8::Null();
v8::Persistent<v8::Object> result =
getDOMSVGObjectWithContextMap().get(object);
if (!result.IsEmpty()) return result;
// Special case: SVGPathSegs need to be downcast to their real type
if (type == V8ClassIndex::SVGPATHSEG)
type = V8Custom::DowncastSVGPathSeg(object);
v8::Local<v8::Object> v8obj = InstantiateV8Object(type, type, object);
if (!v8obj.IsEmpty()) {
result = v8::Persistent<v8::Object>::New(v8obj);
switch (type) {
#define MAKE_CASE(TYPE, NAME) \
case V8ClassIndex::TYPE: static_cast<NAME*>(object)->ref(); break;
SVG_OBJECT_TYPES(MAKE_CASE)
#undef MAKE_CASE
#define MAKE_CASE(TYPE, NAME) \
case V8ClassIndex::TYPE: \
static_cast<V8SVGPODTypeWrapper<NAME>*>(object)->ref(); break;
SVG_POD_NATIVE_TYPES(MAKE_CASE)
#undef MAKE_CASE
default:
ASSERT(false);
}
getDOMSVGObjectWithContextMap().set(object, result);
}
return result;
}
void V8Proxy::SetSVGContext(void* obj, SVGElement* context)
{
if (obj == NULL)
return;
SVGElement* old_context = svg_object_to_context_map().get(obj);
if (old_context == context)
return;
if (old_context)
old_context->deref();
if (context)
context->ref();
svg_object_to_context_map().set(obj, context);
}
SVGElement* V8Proxy::GetSVGContext(void* obj)
{
return svg_object_to_context_map().get(obj);
}
#endif
// A map from a DOM node to its JS wrapper, the wrapper
// is kept as a strong reference to survive GCs.
static DOMObjectMap& gc_protected_map() {
static DOMObjectMap static_gc_protected_map;
return static_gc_protected_map;
}
// static
void V8Proxy::GCProtect(void* dom_object)
{
if (!dom_object)
return;
if (gc_protected_map().contains(dom_object))
return;
if (!getDOMObjectMap().contains(dom_object))
return;
// Create a new (strong) persistent handle for the object.
v8::Persistent<v8::Object> wrapper = getDOMObjectMap().get(dom_object);
if (wrapper.IsEmpty()) return;
gc_protected_map().set(dom_object, *v8::Persistent<v8::Object>::New(wrapper));
}
// static
void V8Proxy::GCUnprotect(void* dom_object)
{
if (!dom_object)
return;
if (!gc_protected_map().contains(dom_object))
return;
// Dispose the strong reference.
v8::Persistent<v8::Object> wrapper(gc_protected_map().take(dom_object));
wrapper.Dispose();
}
typedef std::pair<uintptr_t, Node*> GrouperPair;
typedef Vector<GrouperPair> GrouperList;
#if PLATFORM(ANDROID)
// Sort GrouperPair by the group id. Node* is only involved to sort within
// a group id, so it will be fine.
// TODO(andreip): used by std::stable_sort function. We can implement
// the std::sort function and remove this one.
static bool ComparePair(const GrouperPair& p1, const GrouperPair& p2) {
return p1.first < p2.first;
}
#endif
// Create object groups for DOM tree nodes.
static void GCPrologue()
{
v8::HandleScope scope;
#ifndef NDEBUG
EnumerateDOMObjectMap(getDOMObjectMap().impl());
#endif
// Run through all objects with possible pending activity making their
// wrappers non weak if there is pending activity.
DOMObjectMap active_map = getActiveDOMObjectMap().impl();
for (DOMObjectMap::iterator it = active_map.begin(), end = active_map.end();
it != end; ++it) {
void* obj = it->first;
v8::Persistent<v8::Object> wrapper = v8::Persistent<v8::Object>(it->second);
ASSERT(wrapper.IsWeak());
V8ClassIndex::V8WrapperType type = V8Proxy::GetDOMWrapperType(wrapper);
switch (type) {
#define MAKE_CASE(TYPE, NAME) \
case V8ClassIndex::TYPE: { \
NAME* impl = static_cast<NAME*>(obj); \
if (impl->hasPendingActivity()) \
wrapper.ClearWeak(); \
break; \
}
ACTIVE_DOM_OBJECT_TYPES(MAKE_CASE)
default:
ASSERT(false);
#undef MAKE_CASE
}
// Additional handling of message port ensuring that entangled ports also
// have their wrappers entangled. This should ideally be handled when the
// ports are actually entangled in MessagePort::entangle, but to avoid
// forking MessagePort.* this is postponed to GC time. Having this postponed
// has the drawback that the wrappers are "entangled/unentangled" for each
// GC even though their entnaglement most likely is still the same.
if (type == V8ClassIndex::MESSAGEPORT) {
// Get the port and its entangled port.
MessagePort* port1 = static_cast<MessagePort*>(obj);
MessagePortProxy* port2 = port1->entangledPort();
if (port2 != NULL) {
// As ports are always entangled in pairs only perform the entanglement
// once for each pair (see ASSERT in MessagePort::unentangle()).
if (port1 < port2) {
v8::Handle<v8::Value> port1_wrapper =
V8Proxy::ToV8Object(V8ClassIndex::MESSAGEPORT, port1);
v8::Handle<v8::Value> port2_wrapper =
V8Proxy::ToV8Object(V8ClassIndex::MESSAGEPORT, port2);
ASSERT(port1_wrapper->IsObject());
v8::Handle<v8::Object>::Cast(port1_wrapper)->SetInternalField(
V8Custom::kMessagePortEntangledPortIndex, port2_wrapper);
ASSERT(port2_wrapper->IsObject());
v8::Handle<v8::Object>::Cast(port2_wrapper)->SetInternalField(
V8Custom::kMessagePortEntangledPortIndex, port1_wrapper);
}
} else {
// Remove the wrapper entanglement when a port is not entangled.
if (V8Proxy::DOMObjectHasJSWrapper(port1)) {
v8::Handle<v8::Value> wrapper =
V8Proxy::ToV8Object(V8ClassIndex::MESSAGEPORT, port1);
ASSERT(wrapper->IsObject());
v8::Handle<v8::Object>::Cast(wrapper)->SetInternalField(
V8Custom::kMessagePortEntangledPortIndex, v8::Undefined());
}
}
}
}
// Create object groups.
DOMNodeMap node_map = getDOMNodeMap().impl();
GrouperList grouper;
grouper.reserveCapacity(node_map.size());
for (DOMNodeMap::iterator it = node_map.begin(), end = node_map.end();
it != end; ++it) {
Node* node = it->first;
// If the node is in document, put it in the ownerDocument's object group.
//
// If an image element was created by JavaScript "new Image",
// it is not in a document. However, if the load event has not
// been fired (still onloading), it is treated as in the document.
//
// Otherwise, the node is put in an object group identified by the root
// elment of the tree to which it belongs.
uintptr_t group_id;
if (node->inDocument() ||
(node->hasTagName(HTMLNames::imgTag) &&
!static_cast<HTMLImageElement*>(node)->haveFiredLoadEvent())) {
group_id = reinterpret_cast<uintptr_t>(node->document());
} else {
Node* root = node;
while (root->parent())
root = root->parent();
// If the node is alone in its DOM tree (doesn't have a parent or any
// children) then the group will be filtered out later anyway.
if (root == node && !node->hasChildNodes())
continue;
group_id = reinterpret_cast<uintptr_t>(root);
}
grouper.append(GrouperPair(group_id, node));
}
#if PLATFORM(ANDROID)
// TODO(andreip): implement std::sort() and get rid of this.
std::stable_sort<GrouperPair>(grouper.begin(), grouper.end(), ComparePair);
#else
// Group by sorting by the group id. This will use the std::pair operator<,
// which will really sort by both the group id and the Node*. However the
// Node* is only involved to sort within a group id, so it will be fine.
std::sort(grouper.begin(), grouper.end());
#endif
// TODO(deanm): Should probably work in iterators here, but indexes were
// easier for my simple mind.
for (size_t i = 0; i < grouper.size(); ) {
// Seek to the next key (or the end of the list).
size_t next_key_index = grouper.size();
for (size_t j = i; j < grouper.size(); ++j) {
if (grouper[i].first != grouper[j].first) {
next_key_index = j;
break;
}
}
ASSERT(next_key_index > i);
// We only care about a group if it has more than one object. If it only
// has one object, it has nothing else that needs to be kept alive.
if (next_key_index - i <= 1) {
i = next_key_index;
continue;
}
Vector<v8::Persistent<v8::Value> > group;
group.reserveCapacity(next_key_index - i);
for (; i < next_key_index; ++i) {
Node* node = grouper[i].second;
v8::Persistent<v8::Value> wrapper =
getDOMNodeMap().get(node);
if (!wrapper.IsEmpty())
group.append(wrapper);
// If the node is styled and there is a wrapper for the inline
// style declaration, we need to keep that style declaration
// wrapper alive as well, so we add it to the object group.
if (node->isStyledElement()) {
StyledElement* element = reinterpret_cast<StyledElement*>(node);
CSSStyleDeclaration* style = element->inlineStyleDecl();
if (style != NULL) {
wrapper = getDOMObjectMap().get(style);
if (!wrapper.IsEmpty())
group.append(wrapper);
}
}
}
if (group.size() > 1)
v8::V8::AddObjectGroup(&group[0], group.size());
ASSERT(i == next_key_index);
}
}
static void GCEpilogue()
{
v8::HandleScope scope;
// Run through all objects with pending activity making their wrappers weak
// again.
DOMObjectMap active_map = getActiveDOMObjectMap().impl();
for (DOMObjectMap::iterator it = active_map.begin(), end = active_map.end();
it != end; ++it) {
void* obj = it->first;
v8::Persistent<v8::Object> wrapper = v8::Persistent<v8::Object>(it->second);
V8ClassIndex::V8WrapperType type = V8Proxy::GetDOMWrapperType(wrapper);
switch (type) {
#define MAKE_CASE(TYPE, NAME) \
case V8ClassIndex::TYPE: { \
NAME* impl = static_cast<NAME*>(obj); \
if (impl->hasPendingActivity()) { \
ASSERT(!wrapper.IsWeak()); \
wrapper.MakeWeak(impl, &weakActiveDOMObjectCallback); \
} \
break; \
}
ACTIVE_DOM_OBJECT_TYPES(MAKE_CASE)
default:
ASSERT(false);
#undef MAKE_CASE
}
}
#ifndef NDEBUG
// Check all survivals are weak.
EnumerateDOMObjectMap(getDOMObjectMap().impl());
EnumerateDOMNodeMap(getDOMNodeMap().impl());
EnumerateDOMObjectMap(gc_protected_map());
EnumerateGlobalHandles();
#undef USE_VAR
#endif
}
typedef HashMap<int, v8::FunctionTemplate*> FunctionTemplateMap;
bool AllowAllocation::m_current = false;
// JavaScriptConsoleMessages encapsulate everything needed to
// log messages originating from JavaScript to the Chrome console.
class JavaScriptConsoleMessage {
public:
JavaScriptConsoleMessage(const String& str,
const String& sourceID,
unsigned lineNumber)
: m_string(str)
, m_sourceID(sourceID)
, m_lineNumber(lineNumber) { }
void AddToPage(Page* page) const;
private:
const String m_string;
const String m_sourceID;
const unsigned m_lineNumber;
};
void JavaScriptConsoleMessage::AddToPage(Page* page) const
{
ASSERT(page);
Console* console = page->mainFrame()->domWindow()->console();
console->addMessage(JSMessageSource, ErrorMessageLevel, m_string, m_lineNumber, m_sourceID);
}
// The ConsoleMessageManager handles all console messages that stem
// from JavaScript. It keeps a list of messages that have been delayed but
// it makes sure to add all messages to the console in the right order.
class ConsoleMessageManager {
public:
// Add a message to the console. May end up calling JavaScript code
// indirectly through the inspector so only call this function when
// it is safe to do allocations.
static void AddMessage(Page* page, const JavaScriptConsoleMessage& message);
// Add a message to the console but delay the reporting until it
// is safe to do so: Either when we leave JavaScript execution or
// when adding other console messages. The primary purpose of this
// method is to avoid calling into V8 to handle console messages
// when the VM is in a state that does not support GCs or allocations.
// Delayed messages are always reported in the page corresponding
// to the active context.
static void AddDelayedMessage(const JavaScriptConsoleMessage& message);
// Process any delayed messages. May end up calling JavaScript code
// indirectly through the inspector so only call this function when
// it is safe to do allocations.
static void ProcessDelayedMessages();
private:
// All delayed messages are stored in this vector. If the vector
// is NULL, there are no delayed messages.
static Vector<JavaScriptConsoleMessage>* m_delayed;
};
Vector<JavaScriptConsoleMessage>* ConsoleMessageManager::m_delayed = NULL;
void ConsoleMessageManager::AddMessage(
Page* page,
const JavaScriptConsoleMessage& message)
{
// Process any delayed messages to make sure that messages
// appear in the right order in the console.
ProcessDelayedMessages();
message.AddToPage(page);
}
void ConsoleMessageManager::AddDelayedMessage(const JavaScriptConsoleMessage& message)
{
if (!m_delayed)
// Allocate a vector for the delayed messages. Will be
// deallocated when the delayed messages are processed
// in ProcessDelayedMessages().
m_delayed = new Vector<JavaScriptConsoleMessage>();
m_delayed->append(message);
}
void ConsoleMessageManager::ProcessDelayedMessages()
{
// If we have a delayed vector it cannot be empty.
if (!m_delayed)
return;
ASSERT(!m_delayed->isEmpty());
// Add the delayed messages to the page of the active
// context. If that for some bizarre reason does not
// exist, we clear the list of delayed messages to avoid
// posting messages. We still deallocate the vector.
Frame* frame = V8Proxy::retrieveFrameForEnteredContext();
Page* page = NULL;
if (frame)
page = frame->page();
if (!page)
m_delayed->clear();
// Iterate through all the delayed messages and add them
// to the console.
const int size = m_delayed->size();
for (int i = 0; i < size; i++) {
m_delayed->at(i).AddToPage(page);
}
// Deallocate the delayed vector.
delete m_delayed;
m_delayed = NULL;
}
// Convenience class for ensuring that delayed messages in the
// ConsoleMessageManager are processed quickly.
class ConsoleMessageScope {
public:
ConsoleMessageScope() { ConsoleMessageManager::ProcessDelayedMessages(); }
~ConsoleMessageScope() { ConsoleMessageManager::ProcessDelayedMessages(); }
};
void log_info(Frame* frame, const String& msg, const String& url)
{
Page* page = frame->page();
if (!page)
return;
JavaScriptConsoleMessage message(msg, url, 0);
ConsoleMessageManager::AddMessage(page, message);
}
static void HandleConsoleMessage(v8::Handle<v8::Message> message,
v8::Handle<v8::Value> data)
{
// Use the frame where JavaScript is called from.
Frame* frame = V8Proxy::retrieveFrameForEnteredContext();
if (!frame)
return;
Page* page = frame->page();
if (!page)
return;
v8::Handle<v8::String> errorMessageString = message->Get();
ASSERT(!errorMessageString.IsEmpty());
String errorMessage = ToWebCoreString(errorMessageString);
v8::Handle<v8::Value> resourceName = message->GetScriptResourceName();
bool useURL = (resourceName.IsEmpty() || !resourceName->IsString());
String resourceNameString = (useURL)
? frame->document()->url()
: ToWebCoreString(resourceName);
JavaScriptConsoleMessage consoleMessage(errorMessage,
resourceNameString,
message->GetLineNumber());
ConsoleMessageManager::AddMessage(page, consoleMessage);
}
enum DelayReporting {
REPORT_LATER,
REPORT_NOW
};
static void ReportUnsafeAccessTo(Frame* target, DelayReporting delay)
{
ASSERT(target);
Document* targetDocument = target->document();
if (!targetDocument)
return;
Frame* source = V8Proxy::retrieveFrameForEnteredContext();
if (!source || !source->document())
return; // Ignore error if the source document is gone.
Document* sourceDocument = source->document();
// FIXME: This error message should contain more specifics of why the same
// origin check has failed.
String str = String::format("Unsafe JavaScript attempt to access frame "
"with URL %s from frame with URL %s. "
"Domains, protocols and ports must match.\n",
targetDocument->url().string().utf8().data(),
sourceDocument->url().string().utf8().data());
// Build a console message with fake source ID and line number.
const String kSourceID = "";
const int kLineNumber = 1;
JavaScriptConsoleMessage message(str, kSourceID, kLineNumber);
if (delay == REPORT_NOW) {
// NOTE(tc): Apple prints the message in the target page, but it seems like
// it should be in the source page. Even for delayed messages, we put it in
// the source page; see ConsoleMessageManager::ProcessDelayedMessages().
ConsoleMessageManager::AddMessage(source->page(), message);
} else {
ASSERT(delay == REPORT_LATER);
// We cannot safely report the message eagerly, because this may cause
// allocations and GCs internally in V8 and we cannot handle that at this
// point. Therefore we delay the reporting.
ConsoleMessageManager::AddDelayedMessage(message);
}
}
static void ReportUnsafeJavaScriptAccess(v8::Local<v8::Object> host,
v8::AccessType type,
v8::Local<v8::Value> data)
{
Frame* target = V8Custom::GetTargetFrame(host, data);
if (target)
ReportUnsafeAccessTo(target, REPORT_LATER);
}
static void HandleFatalErrorInV8()
{
// TODO: We temporarily deal with V8 internal error situations
// such as out-of-memory by crashing the renderer.
CRASH();
}
static void ReportFatalErrorInV8(const char* location, const char* message)
{
// V8 is shutdown, we cannot use V8 api.
// The only thing we can do is to disable JavaScript.
// TODO: clean up V8Proxy and disable JavaScript.
printf("V8 error: %s (%s)\n", message, location);
HandleFatalErrorInV8();
}
V8Proxy::~V8Proxy()
{
clearForClose();
DestroyGlobal();
}
void V8Proxy::DestroyGlobal()
{
if (!m_global.IsEmpty()) {
#ifndef NDEBUG
UnregisterGlobalHandle(this, m_global);
#endif
m_global.Dispose();
m_global.Clear();
}
}
bool V8Proxy::DOMObjectHasJSWrapper(void* obj) {
return getDOMObjectMap().contains(obj) ||
getActiveDOMObjectMap().contains(obj);
}
// The caller must have increased obj's ref count.
void V8Proxy::SetJSWrapperForDOMObject(void* obj, v8::Persistent<v8::Object> wrapper)
{
ASSERT(MaybeDOMWrapper(wrapper));
#ifndef NDEBUG
V8ClassIndex::V8WrapperType type = V8Proxy::GetDOMWrapperType(wrapper);
switch (type) {
#define MAKE_CASE(TYPE, NAME) case V8ClassIndex::TYPE:
ACTIVE_DOM_OBJECT_TYPES(MAKE_CASE)
ASSERT(false);
#undef MAKE_CASE
default: break;
}
#endif
getDOMObjectMap().set(obj, wrapper);
}
// The caller must have increased obj's ref count.
void V8Proxy::SetJSWrapperForActiveDOMObject(void* obj, v8::Persistent<v8::Object> wrapper)
{
ASSERT(MaybeDOMWrapper(wrapper));
#ifndef NDEBUG
V8ClassIndex::V8WrapperType type = V8Proxy::GetDOMWrapperType(wrapper);
switch (type) {
#define MAKE_CASE(TYPE, NAME) case V8ClassIndex::TYPE: break;
ACTIVE_DOM_OBJECT_TYPES(MAKE_CASE)
default: ASSERT(false);
#undef MAKE_CASE
}
#endif
getActiveDOMObjectMap().set(obj, wrapper);
}
// The caller must have increased node's ref count.
void V8Proxy::SetJSWrapperForDOMNode(Node* node, v8::Persistent<v8::Object> wrapper)
{
ASSERT(MaybeDOMWrapper(wrapper));
getDOMNodeMap().set(node, wrapper);
}
// Event listeners
static V8EventListener* FindEventListenerInList(V8EventListenerList& list,
v8::Local<v8::Value> listener,
bool isInline)
{
ASSERT(v8::Context::InContext());
if (!listener->IsObject())
return 0;
return list.find(listener->ToObject(), isInline);
}
// Find an existing wrapper for a JS event listener in the map.
PassRefPtr<V8EventListener> V8Proxy::FindV8EventListener(v8::Local<v8::Value> listener,
bool isInline)
{
return FindEventListenerInList(m_event_listeners, listener, isInline);
}
PassRefPtr<V8EventListener> V8Proxy::FindOrCreateV8EventListener(v8::Local<v8::Value> obj, bool isInline)
{
ASSERT(v8::Context::InContext());
if (!obj->IsObject())
return 0;
V8EventListener* wrapper =
FindEventListenerInList(m_event_listeners, obj, isInline);
if (wrapper)
return wrapper;
// Create a new one, and add to cache.
RefPtr<V8EventListener> new_listener =
V8EventListener::create(m_frame, v8::Local<v8::Object>::Cast(obj), isInline);
m_event_listeners.add(new_listener.get());
return new_listener;
}
// Object event listeners (such as XmlHttpRequest and MessagePort) are
// different from listeners on DOM nodes. An object event listener wrapper
// only holds a weak reference to the JS function. A strong reference can
// create a cycle.
//
// The lifetime of these objects is bounded by the life time of its JS
// wrapper. So we can create a hidden reference from the JS wrapper to
// to its JS function.
//
// (map)
// XHR <---------- JS_wrapper
// | (hidden) : ^
// V V : (may reachable by closure)
// V8_listener --------> JS_function
// (weak) <-- may create a cycle if it is strong
//
// The persistent reference is made weak in the constructor
// of V8ObjectEventListener.
PassRefPtr<V8EventListener> V8Proxy::FindObjectEventListener(
v8::Local<v8::Value> listener, bool isInline)
{
return FindEventListenerInList(m_xhr_listeners, listener, isInline);
}
PassRefPtr<V8EventListener> V8Proxy::FindOrCreateObjectEventListener(
v8::Local<v8::Value> obj, bool isInline)
{
ASSERT(v8::Context::InContext());
if (!obj->IsObject())
return 0;
V8EventListener* wrapper =
FindEventListenerInList(m_xhr_listeners, obj, isInline);
if (wrapper)
return wrapper;
// Create a new one, and add to cache.
RefPtr<V8EventListener> new_listener =
V8ObjectEventListener::create(m_frame, v8::Local<v8::Object>::Cast(obj), isInline);
m_xhr_listeners.add(new_listener.get());
return new_listener.release();
}
static void RemoveEventListenerFromList(V8EventListenerList& list,
V8EventListener* listener)
{
list.remove(listener);
}
void V8Proxy::RemoveV8EventListener(V8EventListener* listener)
{
RemoveEventListenerFromList(m_event_listeners, listener);
}
void V8Proxy::RemoveObjectEventListener(V8ObjectEventListener* listener)
{
RemoveEventListenerFromList(m_xhr_listeners, listener);
}
static void DisconnectEventListenersInList(V8EventListenerList& list)
{
V8EventListenerList::iterator p = list.begin();
while (p != list.end()) {
(*p)->disconnectFrame();
++p;
}
list.clear();
}
void V8Proxy::DisconnectEventListeners()
{
DisconnectEventListenersInList(m_event_listeners);
DisconnectEventListenersInList(m_xhr_listeners);
}
v8::Handle<v8::Script> V8Proxy::CompileScript(v8::Handle<v8::String> code,
const String& fileName,
int baseLine)
#ifdef ANDROID_INSTRUMENT
{
android::TimeCounter::start(android::TimeCounter::JavaScriptParseTimeCounter);
v8::Handle<v8::Script> script = CompileScriptInternal(code, fileName, baseLine);
android::TimeCounter::record(android::TimeCounter::JavaScriptParseTimeCounter, __FUNCTION__);
return script;
}
v8::Handle<v8::Script> V8Proxy::CompileScriptInternal(v8::Handle<v8::String> code,
const String& fileName,
int baseLine)
#endif
{
const uint16_t* fileNameString = FromWebCoreString(fileName);
v8::Handle<v8::String> name =
v8::String::New(fileNameString, fileName.length());
v8::Handle<v8::Integer> line = v8::Integer::New(baseLine);
v8::ScriptOrigin origin(name, line);
v8::Handle<v8::Script> script = v8::Script::Compile(code, &origin);
return script;
}
bool V8Proxy::HandleOutOfMemory()
{
v8::Local<v8::Context> context = v8::Context::GetCurrent();
if (!context->HasOutOfMemoryException())
return false;
// Warning, error, disable JS for this frame?
Frame* frame = V8Proxy::retrieveFrame(context);
V8Proxy* proxy = V8Proxy::retrieve(frame);
if (proxy != NULL) {
// Clean m_context, and event handlers.
proxy->clearForClose();
// Destroy the global object.
proxy->DestroyGlobal();
}
#if PLATFORM(CHROMIUM)
// TODO(andreip): ChromeBridge->BrowserBridge?
ChromiumBridge::notifyJSOutOfMemory(frame);
#endif
// Disable JS.
Settings* settings = frame->settings();
ASSERT(settings);
settings->setJavaScriptEnabled(false);
return true;
}
void V8Proxy::evaluateInNewContext(const Vector<ScriptSourceCode>& sources)
{
InitContextIfNeeded();
v8::HandleScope handleScope;
// Set up the DOM window as the prototype of the new global object.
v8::Handle<v8::Context> windowContext = m_context;
v8::Handle<v8::Object> windowGlobal = windowContext->Global();
v8::Handle<v8::Value> windowWrapper =
V8Proxy::LookupDOMWrapper(V8ClassIndex::DOMWINDOW, windowGlobal);
ASSERT(V8Proxy::DOMWrapperToNative<DOMWindow>(windowWrapper) ==
m_frame->domWindow());
v8::Persistent<v8::Context> context =
createNewContext(v8::Handle<v8::Object>());
v8::Context::Scope context_scope(context);
v8::Handle<v8::Object> global = context->Global();
v8::Handle<v8::String> implicitProtoString = v8::String::New("__proto__");
global->Set(implicitProtoString, windowWrapper);
// Give the code running in the new context a way to get access to the
// original context.
global->Set(v8::String::New("contentWindow"), windowGlobal);
// Run code in the new context.
for (size_t i = 0; i < sources.size(); ++i)
evaluate(sources[i], 0);
// Using the default security token means that the canAccess is always
// called, which is slow.
// TODO(aa): Use tokens where possible. This will mean keeping track of all
// created contexts so that they can all be updated when the document domain
// changes.
context->UseDefaultSecurityToken();
context.Dispose();
}
v8::Local<v8::Value> V8Proxy::evaluate(const ScriptSourceCode& source, Node* n)
{
ASSERT(v8::Context::InContext());
LOCK_V8;
// Compile the script.
v8::Local<v8::String> code = v8ExternalString(source.source());
#if PLATFORM(CHROMIUM)
// TODO(andreip): ChromeBridge->BrowserBridge?
ChromiumBridge::traceEventBegin("v8.compile", n, "");
#endif
// NOTE: For compatibility with WebCore, ScriptSourceCode's line starts at
// 1, whereas v8 starts at 0.
v8::Handle<v8::Script> script = CompileScript(code, source.url(),
source.startLine() - 1);
#if PLATFORM(CHROMIUM)
// TODO(andreip): ChromeBridge->BrowserBridge?
ChromiumBridge::traceEventEnd("v8.compile", n, "");
ChromiumBridge::traceEventBegin("v8.run", n, "");
#endif
v8::Local<v8::Value> result;
{
// Isolate exceptions that occur when executing the code. These
// exceptions should not interfere with javascript code we might
// evaluate from C++ when returning from here
v8::TryCatch try_catch;
try_catch.SetVerbose(true);
// Set inlineCode to true for <a href="javascript:doSomething()">
// and false for <script>doSomething</script>. We make a rough guess at
// this based on whether the script source has a URL.
result = RunScript(script, source.url().string().isNull());
}
#if PLATFORM(CHROMIUM)
// TODO(andreip): ChromeBridge->BrowserBridge?
ChromiumBridge::traceEventEnd("v8.run", n, "");
#endif
return result;
}
v8::Local<v8::Value> V8Proxy::RunScript(v8::Handle<v8::Script> script,
bool inline_code)
#ifdef ANDROID_INSTRUMENT
{
android::TimeCounter::start(android::TimeCounter::JavaScriptExecuteTimeCounter);
v8::Local<v8::Value> result = RunScriptInternal(script, inline_code);
android::TimeCounter::record(android::TimeCounter::JavaScriptExecuteTimeCounter, __FUNCTION__);
return result;
}
v8::Local<v8::Value> V8Proxy::RunScriptInternal(v8::Handle<v8::Script> script,
bool inline_code)
#endif
{
if (script.IsEmpty())
return v8::Local<v8::Value>();
// Compute the source string and prevent against infinite recursion.
if (m_recursion >= kMaxRecursionDepth) {
v8::Local<v8::String> code =
v8ExternalString("throw RangeError('Recursion too deep')");
// TODO(kasperl): Ideally, we should be able to re-use the origin of the
// script passed to us as the argument instead of using an empty string
// and 0 baseLine.
script = CompileScript(code, "", 0);
}
if (HandleOutOfMemory())
ASSERT(script.IsEmpty());
if (script.IsEmpty())
return v8::Local<v8::Value>();
// Save the previous value of the inlineCode flag and update the flag for
// the duration of the script invocation.
bool previous_inline_code = inlineCode();
setInlineCode(inline_code);
// Run the script and keep track of the current recursion depth.
v8::Local<v8::Value> result;
{ ConsoleMessageScope scope;
m_recursion++;
// Evaluating the JavaScript could cause the frame to be deallocated,
// so we start the keep alive timer here.
// Frame::keepAlive method adds the ref count of the frame and sets a
// timer to decrease the ref count. It assumes that the current JavaScript
// execution finishs before firing the timer.
// See issue 1218756 and 914430.
m_frame->keepAlive();
result = script->Run();
m_recursion--;
}
if (HandleOutOfMemory())
ASSERT(result.IsEmpty());
// Handle V8 internal error situation (Out-of-memory).
if (result.IsEmpty())
return v8::Local<v8::Value>();
// Restore inlineCode flag.
setInlineCode(previous_inline_code);
if (v8::V8::IsDead())
HandleFatalErrorInV8();
return result;
}
v8::Local<v8::Value> V8Proxy::CallFunction(v8::Handle<v8::Function> function,
v8::Handle<v8::Object> receiver,
int argc,
v8::Handle<v8::Value> args[])
{
#ifdef ANDROID_INSTRUMENT
android::TimeCounter::start(android::TimeCounter::JavaScriptExecuteTimeCounter);
#endif
// For now, we don't put any artificial limitations on the depth
// of recursion that stems from calling functions. This is in
// contrast to the script evaluations.
v8::Local<v8::Value> result;
{
ConsoleMessageScope scope;
// Evaluating the JavaScript could cause the frame to be deallocated,
// so we start the keep alive timer here.
// Frame::keepAlive method adds the ref count of the frame and sets a
// timer to decrease the ref count. It assumes that the current JavaScript
// execution finishs before firing the timer.
// See issue 1218756 and 914430.
m_frame->keepAlive();
result = function->Call(receiver, argc, args);
}
if (v8::V8::IsDead())
HandleFatalErrorInV8();
#ifdef ANDROID_INSTRUMENT
android::TimeCounter::record(android::TimeCounter::JavaScriptExecuteTimeCounter, __FUNCTION__);
#endif
return result;
}
v8::Local<v8::Value> V8Proxy::NewInstance(v8::Handle<v8::Function> constructor,
int argc,
v8::Handle<v8::Value> args[])
{
// No artificial limitations on the depth of recursion, see comment in
// V8Proxy::CallFunction.
v8::Local<v8::Value> result;
{
ConsoleMessageScope scope;
// See comment in V8Proxy::CallFunction.
m_frame->keepAlive();
result = constructor->NewInstance(argc, args);
}
if (v8::V8::IsDead())
HandleFatalErrorInV8();
return result;
}
v8::Local<v8::Function> V8Proxy::GetConstructor(V8ClassIndex::V8WrapperType t){
// A DOM constructor is a function instance created from a DOM constructor
// template. There is one instance per context. A DOM constructor is
// different from a normal function in two ways:
// 1) it cannot be called as constructor (aka, used to create a DOM object)
// 2) its __proto__ points to Object.prototype rather than
// Function.prototype.
// The reason for 2) is that, in Safari, a DOM constructor is a normal JS
// object, but not a function. Hotmail relies on the fact that, in Safari,
// HTMLElement.__proto__ == Object.prototype.
//
// m_object_prototype is a cache of the original Object.prototype.
ASSERT(ContextInitialized());
// Enter the context of the proxy to make sure that the
// function is constructed in the context corresponding to
// this proxy.
v8::Context::Scope scope(m_context);
v8::Handle<v8::FunctionTemplate> templ = GetTemplate(t);
// Getting the function might fail if we're running out of
// stack or memory.
v8::TryCatch try_catch;
v8::Local<v8::Function> value = templ->GetFunction();
if (value.IsEmpty())
return v8::Local<v8::Function>();
// Hotmail fix, see comments above.
value->Set(v8::String::New("__proto__"), m_object_prototype);
return value;
}
v8::Local<v8::Object> V8Proxy::CreateWrapperFromCache(V8ClassIndex::V8WrapperType type) {
int class_index = V8ClassIndex::ToInt(type);
v8::Local<v8::Value> cached_object =
m_wrapper_boilerplates->Get(v8::Integer::New(class_index));
if (cached_object->IsObject()) {
v8::Local<v8::Object> object = v8::Local<v8::Object>::Cast(cached_object);
return object->Clone();
}
// Not in cache.
InitContextIfNeeded();
v8::Context::Scope scope(m_context);
v8::Local<v8::Function> function = GetConstructor(type);
v8::Local<v8::Object> instance = SafeAllocation::NewInstance(function);
if (!instance.IsEmpty()) {
m_wrapper_boilerplates->Set(v8::Integer::New(class_index), instance);
return instance->Clone();
}
return v8::Local<v8::Object>();
}
// Get the string 'toString'.
static v8::Persistent<v8::String> GetToStringName() {
static 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> val = args.Callee()->Get(GetToStringName());
if (!val->IsFunction()) return v8::String::New("");
return v8::Handle<v8::Function>::Cast(val)->Call(args.This(), 0, NULL);
}
v8::Persistent<v8::FunctionTemplate> V8Proxy::GetTemplate(
V8ClassIndex::V8WrapperType type)
{
v8::Persistent<v8::FunctionTemplate>* cache_cell =
V8ClassIndex::GetCache(type);
if (!(*cache_cell).IsEmpty())
return *cache_cell;
// not found
FunctionTemplateFactory factory = V8ClassIndex::GetFactory(type);
v8::Persistent<v8::FunctionTemplate> desc = factory();
// DOM constructors are functions and should print themselves as such.
// However, we will later replace their prototypes with Object
// prototypes so we need to explicitly override toString on the
// instance itself. If we later make DOM constructors full objects
// we can give them class names instead and Object.prototype.toString
// will work so we can remove this code.
static v8::Persistent<v8::FunctionTemplate> to_string_template;
if (to_string_template.IsEmpty()) {
to_string_template = v8::Persistent<v8::FunctionTemplate>::New(
v8::FunctionTemplate::New(ConstructorToString));
}
desc->Set(GetToStringName(), to_string_template);
switch (type) {
case V8ClassIndex::CSSSTYLEDECLARATION:
// The named property handler for style declarations has a
// setter. Therefore, the interceptor has to be on the object
// itself and not on the prototype object.
desc->InstanceTemplate()->SetNamedPropertyHandler(
USE_NAMED_PROPERTY_GETTER(CSSStyleDeclaration),
USE_NAMED_PROPERTY_SETTER(CSSStyleDeclaration));
setCollectionStringOrNullIndexedGetter<CSSStyleDeclaration>(desc);
break;
case V8ClassIndex::CSSRULELIST:
setCollectionIndexedGetter<CSSRuleList, CSSRule>(desc,
V8ClassIndex::CSSRULE);
break;
case V8ClassIndex::CSSVALUELIST:
setCollectionIndexedGetter<CSSValueList, CSSValue>(
desc,
V8ClassIndex::CSSVALUE);
break;
case V8ClassIndex::CSSVARIABLESDECLARATION:
setCollectionStringOrNullIndexedGetter<CSSVariablesDeclaration>(desc);
break;
case V8ClassIndex::WEBKITCSSTRANSFORMVALUE:
setCollectionIndexedGetter<WebKitCSSTransformValue, CSSValue>(
desc,
V8ClassIndex::CSSVALUE);
break;
case V8ClassIndex::UNDETECTABLEHTMLCOLLECTION:
desc->InstanceTemplate()->MarkAsUndetectable(); // fall through
case V8ClassIndex::HTMLCOLLECTION:
desc->InstanceTemplate()->SetNamedPropertyHandler(
USE_NAMED_PROPERTY_GETTER(HTMLCollection));
desc->InstanceTemplate()->SetCallAsFunctionHandler(
USE_CALLBACK(HTMLCollectionCallAsFunction));
setCollectionIndexedGetter<HTMLCollection, Node>(desc,
V8ClassIndex::NODE);
break;
case V8ClassIndex::HTMLOPTIONSCOLLECTION:
desc->InstanceTemplate()->SetNamedPropertyHandler(
USE_NAMED_PROPERTY_GETTER(HTMLCollection));
desc->InstanceTemplate()->SetIndexedPropertyHandler(
USE_INDEXED_PROPERTY_GETTER(HTMLOptionsCollection),
USE_INDEXED_PROPERTY_SETTER(HTMLOptionsCollection));
desc->InstanceTemplate()->SetCallAsFunctionHandler(
USE_CALLBACK(HTMLCollectionCallAsFunction));
break;
case V8ClassIndex::HTMLSELECTELEMENT:
desc->InstanceTemplate()->SetNamedPropertyHandler(
USE_NAMED_PROPERTY_GETTER(HTMLSelectElementCollection));
desc->InstanceTemplate()->SetIndexedPropertyHandler(
nodeCollectionIndexedPropertyGetter<HTMLSelectElement>,
USE_INDEXED_PROPERTY_SETTER(HTMLSelectElementCollection),
0,
0,
nodeCollectionIndexedPropertyEnumerator<HTMLSelectElement>,
v8::Integer::New(V8ClassIndex::NODE));
break;
case V8ClassIndex::HTMLDOCUMENT: {
desc->InstanceTemplate()->SetNamedPropertyHandler(
USE_NAMED_PROPERTY_GETTER(HTMLDocument),
0,
0,
USE_NAMED_PROPERTY_DELETER(HTMLDocument));
// We add an extra internal field to all Document wrappers for
// storing a per document DOMImplementation wrapper.
//
// Additionally, we add two extra internal fields for
// HTMLDocuments to implement temporary shadowing of
// document.all. One field holds an object that is used as a
// marker. The other field holds the marker object if
// document.all is not shadowed and some other value if
// document.all is shadowed.
v8::Local<v8::ObjectTemplate> instance_template =
desc->InstanceTemplate();
ASSERT(instance_template->InternalFieldCount() ==
V8Custom::kDefaultWrapperInternalFieldCount);
instance_template->SetInternalFieldCount(
V8Custom::kHTMLDocumentInternalFieldCount);
break;
}
#if ENABLE(SVG)
case V8ClassIndex::SVGDOCUMENT: // fall through
#endif
case V8ClassIndex::DOCUMENT: {
// We add an extra internal field to all Document wrappers for
// storing a per document DOMImplementation wrapper.
v8::Local<v8::ObjectTemplate> instance_template =
desc->InstanceTemplate();
ASSERT(instance_template->InternalFieldCount() ==
V8Custom::kDefaultWrapperInternalFieldCount);
instance_template->SetInternalFieldCount(
V8Custom::kDocumentMinimumInternalFieldCount);
break;
}
case V8ClassIndex::HTMLAPPLETELEMENT: // fall through
case V8ClassIndex::HTMLEMBEDELEMENT: // fall through
case V8ClassIndex::HTMLOBJECTELEMENT:
// HTMLAppletElement, HTMLEmbedElement and HTMLObjectElement are
// inherited from HTMLPlugInElement, and they share the same property
// handling code.
desc->InstanceTemplate()->SetNamedPropertyHandler(
USE_NAMED_PROPERTY_GETTER(HTMLPlugInElement),
USE_NAMED_PROPERTY_SETTER(HTMLPlugInElement));
desc->InstanceTemplate()->SetIndexedPropertyHandler(
USE_INDEXED_PROPERTY_GETTER(HTMLPlugInElement),
USE_INDEXED_PROPERTY_SETTER(HTMLPlugInElement));
desc->InstanceTemplate()->SetCallAsFunctionHandler(
USE_CALLBACK(HTMLPlugInElement));
break;
case V8ClassIndex::HTMLFRAMESETELEMENT:
desc->InstanceTemplate()->SetNamedPropertyHandler(
USE_NAMED_PROPERTY_GETTER(HTMLFrameSetElement));
break;
case V8ClassIndex::HTMLFORMELEMENT:
desc->InstanceTemplate()->SetNamedPropertyHandler(
USE_NAMED_PROPERTY_GETTER(HTMLFormElement));
desc->InstanceTemplate()->SetIndexedPropertyHandler(
USE_INDEXED_PROPERTY_GETTER(HTMLFormElement),
0,
0,
0,
nodeCollectionIndexedPropertyEnumerator<HTMLFormElement>,
v8::Integer::New(V8ClassIndex::NODE));
break;
case V8ClassIndex::CANVASPIXELARRAY:
desc->InstanceTemplate()->SetIndexedPropertyHandler(
USE_INDEXED_PROPERTY_GETTER(CanvasPixelArray),
USE_INDEXED_PROPERTY_SETTER(CanvasPixelArray));
break;
case V8ClassIndex::STYLESHEET: // fall through
case V8ClassIndex::CSSSTYLESHEET: {
// We add an extra internal field to hold a reference to
// the owner node.
v8::Local<v8::ObjectTemplate> instance_template =
desc->InstanceTemplate();
ASSERT(instance_template->InternalFieldCount() ==
V8Custom::kDefaultWrapperInternalFieldCount);
instance_template->SetInternalFieldCount(
V8Custom::kStyleSheetInternalFieldCount);
break;
}
case V8ClassIndex::MEDIALIST:
setCollectionStringOrNullIndexedGetter<MediaList>(desc);
break;
case V8ClassIndex::MIMETYPEARRAY:
setCollectionIndexedAndNamedGetters<MimeTypeArray, MimeType>(
desc,
V8ClassIndex::MIMETYPE);
break;
case V8ClassIndex::NAMEDNODEMAP:
desc->InstanceTemplate()->SetNamedPropertyHandler(
USE_NAMED_PROPERTY_GETTER(NamedNodeMap));
desc->InstanceTemplate()->SetIndexedPropertyHandler(
USE_INDEXED_PROPERTY_GETTER(NamedNodeMap),
0,
0,
0,
collectionIndexedPropertyEnumerator<NamedNodeMap>,
v8::Integer::New(V8ClassIndex::NODE));
break;
#if ENABLE(DOM_STORAGE)
case V8ClassIndex::STORAGE:
desc->InstanceTemplate()->SetNamedPropertyHandler(
USE_NAMED_PROPERTY_GETTER(Storage),
USE_NAMED_PROPERTY_SETTER(Storage),
0,
USE_NAMED_PROPERTY_DELETER(Storage),
V8Custom::v8StorageNamedPropertyEnumerator);
desc->InstanceTemplate()->SetIndexedPropertyHandler(
USE_INDEXED_PROPERTY_GETTER(Storage),
USE_INDEXED_PROPERTY_SETTER(Storage),
0,
USE_INDEXED_PROPERTY_DELETER(Storage));
break;
#endif
case V8ClassIndex::NODELIST:
setCollectionIndexedGetter<NodeList, Node>(desc, V8ClassIndex::NODE);
desc->InstanceTemplate()->SetNamedPropertyHandler(
USE_NAMED_PROPERTY_GETTER(NodeList));
break;
case V8ClassIndex::PLUGIN:
setCollectionIndexedAndNamedGetters<Plugin, MimeType>(
desc,
V8ClassIndex::MIMETYPE);
break;
case V8ClassIndex::PLUGINARRAY:
setCollectionIndexedAndNamedGetters<PluginArray, Plugin>(
desc,
V8ClassIndex::PLUGIN);
break;
case V8ClassIndex::STYLESHEETLIST:
desc->InstanceTemplate()->SetNamedPropertyHandler(
USE_NAMED_PROPERTY_GETTER(StyleSheetList));
setCollectionIndexedGetter<StyleSheetList, StyleSheet>(
desc,
V8ClassIndex::STYLESHEET);
break;
case V8ClassIndex::DOMWINDOW: {
v8::Local<v8::Signature> default_signature = v8::Signature::New(desc);
desc->PrototypeTemplate()->SetNamedPropertyHandler(
USE_NAMED_PROPERTY_GETTER(DOMWindow));
desc->PrototypeTemplate()->SetIndexedPropertyHandler(
USE_INDEXED_PROPERTY_GETTER(DOMWindow));
desc->SetHiddenPrototype(true);
// Reserve spaces for references to location, history and
// navigator objects.
v8::Local<v8::ObjectTemplate> instance_template =
desc->InstanceTemplate();
instance_template->SetInternalFieldCount(
V8Custom::kDOMWindowInternalFieldCount);
// Set access check callbacks, but turned off initially.
// When a context is detached from a frame, turn on the access check.
// Turning on checks also invalidates inline caches of the object.
instance_template->SetAccessCheckCallbacks(
V8Custom::v8DOMWindowNamedSecurityCheck,
V8Custom::v8DOMWindowIndexedSecurityCheck,
v8::Integer::New(V8ClassIndex::DOMWINDOW),
false);
break;
}
case V8ClassIndex::LOCATION: {
// For security reasons, these functions are on the instance
// instead of on the prototype object to insure that they cannot
// be overwritten.
v8::Local<v8::ObjectTemplate> instance = desc->InstanceTemplate();
instance->SetAccessor(
v8::String::New("reload"),
V8Custom::v8LocationReloadAccessorGetter,
0,
v8::Handle<v8::Value>(),
v8::ALL_CAN_READ,
static_cast<v8::PropertyAttribute>(v8::DontDelete|v8::ReadOnly));
instance->SetAccessor(
v8::String::New("replace"),
V8Custom::v8LocationReplaceAccessorGetter,
0,
v8::Handle<v8::Value>(),
v8::ALL_CAN_READ,
static_cast<v8::PropertyAttribute>(v8::DontDelete|v8::ReadOnly));
instance->SetAccessor(
v8::String::New("assign"),
V8Custom::v8LocationAssignAccessorGetter,
0,
v8::Handle<v8::Value>(),
v8::ALL_CAN_READ,
static_cast<v8::PropertyAttribute>(v8::DontDelete|v8::ReadOnly));
break;
}
case V8ClassIndex::HISTORY: {
break;
}
case V8ClassIndex::MESSAGECHANNEL: {
// Reserve two more internal fields for referencing the port1
// and port2 wrappers. This ensures that the port wrappers are
// kept alive when the channel wrapper is.
desc->SetCallHandler(USE_CALLBACK(MessageChannelConstructor));
v8::Local<v8::ObjectTemplate> instance_template =
desc->InstanceTemplate();
instance_template->SetInternalFieldCount(
V8Custom::kMessageChannelInternalFieldCount);
break;
}
case V8ClassIndex::MESSAGEPORT: {
// Reserve one more internal field for keeping event listeners.
v8::Local<v8::ObjectTemplate> instance_template =
desc->InstanceTemplate();
instance_template->SetInternalFieldCount(
V8Custom::kMessagePortInternalFieldCount);
break;
}
#if ENABLE(WORKERS)
case V8ClassIndex::WORKER: {
// Reserve one more internal field for keeping event listeners.
v8::Local<v8::ObjectTemplate> instance_template =
desc->InstanceTemplate();
instance_template->SetInternalFieldCount(
V8Custom::kWorkerInternalFieldCount);
desc->SetCallHandler(USE_CALLBACK(WorkerConstructor));
break;
}
case V8ClassIndex::WORKERCONTEXT: {
// Reserve one more internal field for keeping event listeners.
v8::Local<v8::ObjectTemplate> instance_template =
desc->InstanceTemplate();
instance_template->SetInternalFieldCount(
V8Custom::kWorkerContextInternalFieldCount);
break;
}
#endif // WORKERS
#if ENABLE(OFFLINE_WEB_APPLICATIONS)
case V8ClassIndex::DOMAPPLICATIONCACHE: {
// Reserve one more internal field for keeping event listeners.
v8::Local<v8::ObjectTemplate> instance_template =
desc->InstanceTemplate();
instance_template->SetInternalFieldCount(
V8Custom::kDOMApplicationCacheFieldCount);
break;
}
#endif
// The following objects are created from JavaScript.
case V8ClassIndex::DOMPARSER:
desc->SetCallHandler(USE_CALLBACK(DOMParserConstructor));
break;
case V8ClassIndex::HTMLIMAGEELEMENT:
desc->SetCallHandler(USE_CALLBACK(HTMLImageElementConstructor));
break;
case V8ClassIndex::HTMLOPTIONELEMENT:
desc->SetCallHandler(USE_CALLBACK(HTMLOptionElementConstructor));
break;
case V8ClassIndex::WEBKITCSSMATRIX:
desc->SetCallHandler(USE_CALLBACK(WebKitCSSMatrixConstructor));
break;
case V8ClassIndex::WEBKITPOINT:
desc->SetCallHandler(USE_CALLBACK(WebKitPointConstructor));
break;
case V8ClassIndex::XMLSERIALIZER:
desc->SetCallHandler(USE_CALLBACK(XMLSerializerConstructor));
break;
case V8ClassIndex::XMLHTTPREQUEST: {
// Reserve one more internal field for keeping event listeners.
v8::Local<v8::ObjectTemplate> instance_template =
desc->InstanceTemplate();
instance_template->SetInternalFieldCount(
V8Custom::kXMLHttpRequestInternalFieldCount);
desc->SetCallHandler(USE_CALLBACK(XMLHttpRequestConstructor));
break;
}
case V8ClassIndex::XMLHTTPREQUESTUPLOAD: {
// Reserve one more internal field for keeping event listeners.
v8::Local<v8::ObjectTemplate> instance_template =
desc->InstanceTemplate();
instance_template->SetInternalFieldCount(
V8Custom::kXMLHttpRequestInternalFieldCount);
break;
}
#if ENABLE(XPATH)
case V8ClassIndex::XPATHEVALUATOR:
desc->SetCallHandler(USE_CALLBACK(XPathEvaluatorConstructor));
break;
#endif
#if ENABLE(XSLT)
case V8ClassIndex::XSLTPROCESSOR:
desc->SetCallHandler(USE_CALLBACK(XSLTProcessorConstructor));
break;
#endif
#if ENABLE(TOUCH_EVENTS)
// TODO(andreip): upstream touch related changes to Chromium
case V8ClassIndex::TOUCHLIST:
desc->InstanceTemplate()->SetIndexedPropertyHandler(
USE_INDEXED_PROPERTY_GETTER(TouchList));
break;
#endif
case V8ClassIndex::CLIENTRECTLIST:
desc->InstanceTemplate()->SetIndexedPropertyHandler(
USE_INDEXED_PROPERTY_GETTER(ClientRectList));
break;
default:
break;
}
*cache_cell = desc;
return desc;
}
bool V8Proxy::ContextInitialized()
{
// m_context, m_global, m_object_prototype and m_wrapper_boilerplates should
// all be non-empty if if m_context is non-empty.
ASSERT(m_context.IsEmpty() || !m_global.IsEmpty());
ASSERT(m_context.IsEmpty() || !m_object_prototype.IsEmpty());
ASSERT(m_context.IsEmpty() || !m_wrapper_boilerplates.IsEmpty());
return !m_context.IsEmpty();
}
DOMWindow* V8Proxy::retrieveWindow()
{
// TODO: This seems very fragile. How do we know that the global object
// from the current context is something sensible? Do we need to use the
// last entered here? Who calls this?
return retrieveWindow(v8::Context::GetCurrent());
}
DOMWindow* V8Proxy::retrieveWindow(v8::Handle<v8::Context> context)
{
v8::Handle<v8::Object> global = context->Global();
ASSERT(!global.IsEmpty());
global = LookupDOMWrapper(V8ClassIndex::DOMWINDOW, global);
ASSERT(!global.IsEmpty());
return ToNativeObject<DOMWindow>(V8ClassIndex::DOMWINDOW, global);
}
Frame* V8Proxy::retrieveFrame(v8::Handle<v8::Context> context)
{
return retrieveWindow(context)->frame();
}
Frame* V8Proxy::retrieveFrameForEnteredContext()
{
v8::Handle<v8::Context> context = v8::Context::GetEntered();
if (context.IsEmpty())
return 0;
return retrieveFrame(context);
}
Frame* V8Proxy::retrieveFrameForCurrentContext()
{
v8::Handle<v8::Context> context = v8::Context::GetCurrent();
if (context.IsEmpty())
return 0;
return retrieveFrame(context);
}
Frame* V8Proxy::retrieveFrameForCallingContext()
{
v8::Handle<v8::Context> context = v8::Context::GetCalling();
if (context.IsEmpty())
return 0;
return retrieveFrame(context);
}
Frame* V8Proxy::retrieveFrame()
{
DOMWindow* window = retrieveWindow();
return window ? window->frame() : 0;
}
V8Proxy* V8Proxy::retrieve()
{
DOMWindow* window = retrieveWindow();
ASSERT(window);
return retrieve(window->frame());
}
V8Proxy* V8Proxy::retrieve(Frame* frame)
{
if (!frame)
return 0;
return frame->script()->isEnabled() ? frame->script()->proxy() : 0;
}
V8Proxy* V8Proxy::retrieve(ScriptExecutionContext* context)
{
if (!context->isDocument())
return 0;
return retrieve(static_cast<Document*>(context)->frame());
}
void V8Proxy::disconnectFrame()
{
// disconnect all event listeners
DisconnectEventListeners();
}
bool V8Proxy::isEnabled()
{
Settings* settings = m_frame->settings();
if (!settings)
return false;
// In the common case, JavaScript is enabled and we're done.
if (settings->isJavaScriptEnabled())
return true;
// If JavaScript has been disabled, we need to look at the frame to tell
// whether this script came from the web or the embedder. Scripts from the
// embedder are safe to run, but scripts from the other sources are
// disallowed.
Document* document = m_frame->document();
if (!document)
return false;
SecurityOrigin* origin = document->securityOrigin();
if (origin->protocol().isEmpty())
return false; // Uninitialized document
if (origin->protocol() == "http" || origin->protocol() == "https")
return false; // Web site
// TODO(darin): the following are application decisions, and they should
// not be made at this layer. instead, we should bridge out to the
// embedder to allow them to override policy here.
#if PLATFORM(CHROMIUM)
// TODO(andreip): ChromeBridge->BrowserBridge?
if (origin->protocol() == ChromiumBridge::uiResourceProtocol())
return true; // Embedder's scripts are ok to run
#endif
// If the scheme is ftp: or file:, an empty file name indicates a directory
// listing, which requires JavaScript to function properly.
const char* kDirProtocols[] = { "ftp", "file" };
#if PLATFORM(ANDROID)
// TODO(andreip): port arraysize function to Android. There's one in Gears.
for (size_t i = 0; i < 2; ++i) {
#else
for (size_t i = 0; i < arraysize(kDirProtocols); ++i) {
#endif
if (origin->protocol() == kDirProtocols[i]) {
const KURL& url = document->url();
return url.pathAfterLastSlash() == url.pathEnd();
}
}
return false; // Other protocols fall through to here
}
void V8Proxy::UpdateDocumentWrapper(v8::Handle<v8::Value> wrapper) {
ClearDocumentWrapper();
ASSERT(m_document.IsEmpty());
m_document = v8::Persistent<v8::Value>::New(wrapper);
#ifndef NDEBUG
RegisterGlobalHandle(PROXY, this, m_document);
#endif
}
void V8Proxy::ClearDocumentWrapper()
{
if (!m_document.IsEmpty()) {
#ifndef NDEBUG
UnregisterGlobalHandle(this, m_document);
#endif
m_document.Dispose();
m_document.Clear();
}
}
void V8Proxy::UpdateDocumentWrapperCache()
{
LOCK_V8;
v8::HandleScope handle_scope;
v8::Context::Scope context_scope(GetContext());
v8::Handle<v8::Value> document_wrapper = NodeToV8Object(m_frame->document());
m_context->Global()->ForceSet(v8::String::New("document"),
document_wrapper,
static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontDelete));
}
void V8Proxy::ClearDocumentWrapperCache()
{
ASSERT(!m_context.IsEmpty());
m_context->Global()->ForceDelete(v8::String::New("document"));
}
void V8Proxy::DisposeContextHandles() {
if (!m_context.IsEmpty()) {
m_context.Dispose();
m_context.Clear();
}
if (!m_wrapper_boilerplates.IsEmpty()) {
#ifndef NDEBUG
UnregisterGlobalHandle(this, m_wrapper_boilerplates);
#endif
m_wrapper_boilerplates.Dispose();
m_wrapper_boilerplates.Clear();
}
if (!m_object_prototype.IsEmpty()) {
#ifndef NDEBUG
UnregisterGlobalHandle(this, m_object_prototype);
#endif
m_object_prototype.Dispose();
m_object_prototype.Clear();
}
}
void V8Proxy::clearForClose()
{
if (!m_context.IsEmpty()) {
LOCK_V8;
v8::HandleScope handle_scope;
ClearDocumentWrapper();
DisposeContextHandles();
}
}
void V8Proxy::clearForNavigation()
{
// disconnect all event listeners
DisconnectEventListeners();
if (!m_context.IsEmpty()) {
LOCK_V8;
v8::HandleScope handle;
ClearDocumentWrapper();
v8::Context::Scope context_scope(m_context);
// Clear the document wrapper cache before turning on access checks on
// the old DOMWindow wrapper. This way, access to the document wrapper
// will be protected by the security checks on the DOMWindow wrapper.
ClearDocumentWrapperCache();
// Turn on access check on the old DOMWindow wrapper.
v8::Handle<v8::Object> wrapper =
LookupDOMWrapper(V8ClassIndex::DOMWINDOW, m_global);
ASSERT(!wrapper.IsEmpty());
wrapper->TurnOnAccessCheck();
// Separate the context from its global object.
m_context->DetachGlobal();
DisposeContextHandles();
}
}
void V8Proxy::SetSecurityToken() {
Document* document = m_frame->document();
// Setup security origin and security token
if (!document) {
m_context->UseDefaultSecurityToken();
return;
}
// Ask the document's SecurityOrigin to generate a security token.
// If two tokens are equal, then the SecurityOrigins canAccess each other.
// If two tokens are not equal, then we have to call canAccess.
// Note: we can't use the HTTPOrigin if it was set from the DOM.
SecurityOrigin* origin = document->securityOrigin();
String token;
if (!origin->domainWasSetInDOM())
token = document->securityOrigin()->toString();
// An empty or "null" token means we always have to call
// canAccess. The toString method on securityOrigins returns the
// string "null" for empty security origins and for security
// origins that should only allow access to themselves. In this
// case, we use the global object as the security token to avoid
// calling canAccess when a script accesses its own objects.
if (token.isEmpty() || token == "null") {
m_context->UseDefaultSecurityToken();
return;
}
CString utf8_token = token.utf8();
// NOTE: V8 does identity comparison in fast path, must use a symbol
// as the security token.
m_context->SetSecurityToken(
v8::String::NewSymbol(utf8_token.data(), utf8_token.length()));
}
void V8Proxy::updateDocument()
{
if (!m_frame->document())
return;
if (m_global.IsEmpty())
return;
InitContextIfNeeded();
// We have a new document and we need to update the cache.
UpdateDocumentWrapperCache();
updateSecurityOrigin();
}
void V8Proxy::updateSecurityOrigin()
{
LOCK_V8;
v8::HandleScope scope;
SetSecurityToken();
}
// Same origin policy implementation:
//
// Same origin policy prevents JS code from domain A access JS & DOM objects
// in a different domain B. There are exceptions and several objects are
// accessible by cross-domain code. For example, the window.frames object is
// accessible by code from a different domain, but window.document is not.
//
// The binding code sets security check callbacks on a function template,
// and accessing instances of the template calls the callback function.
// The callback function checks same origin policy.
//
// Callback functions are expensive. V8 uses a security token string to do
// fast access checks for the common case where source and target are in the
// same domain. A security token is a string object that represents
// the protocol/url/port of a domain.
//
// There are special cases where a security token matching is not enough.
// For example, JavaScript can set its domain to a super domain by calling
// document.setDomain(...). In these cases, the binding code can reset
// a context's security token to its global object so that the fast access
// check will always fail.
// Check if the current execution context can access a target frame.
// First it checks same domain policy using the lexical context
//
// This is equivalent to KJS::Window::allowsAccessFrom(ExecState*, String&).
bool V8Proxy::CanAccessPrivate(DOMWindow* target_window)
{
ASSERT(target_window);
String message;
DOMWindow* origin_window = retrieveWindow();
if (origin_window == target_window)
return true;
if (!origin_window)
return false;
const SecurityOrigin* active_security_origin = origin_window->securityOrigin();
const SecurityOrigin* target_security_origin = target_window->securityOrigin();
// We have seen crashes were the security origin of the target has not been
// initialized. Defend against that.
if (!target_security_origin)
return false;
if (active_security_origin->canAccess(target_security_origin))
return true;
// Allow access to a "about:blank" page if the dynamic context is a
// detached context of the same frame as the blank page.
if (target_security_origin->isEmpty() &&
origin_window->frame() == target_window->frame())
return true;
return false;
}
bool V8Proxy::CanAccessFrame(Frame* target, bool report_error)
{
// The subject is detached from a frame, deny accesses.
if (!target)
return false;
if (!CanAccessPrivate(target->domWindow())) {
if (report_error)
ReportUnsafeAccessTo(target, REPORT_NOW);
return false;
}
return true;
}
bool V8Proxy::CheckNodeSecurity(Node* node)
{
if (!node)
return false;
Frame* target = node->document()->frame();
if (!target)
return false;
return CanAccessFrame(target, true);
}
v8::Persistent<v8::Context> V8Proxy::createNewContext(
v8::Handle<v8::Object> global)
{
v8::Persistent<v8::Context> result;
// Create a new environment using an empty template for the shadow
// object. Reuse the global object if one has been created earlier.
v8::Persistent<v8::ObjectTemplate> globalTemplate =
V8DOMWindow::GetShadowObjectTemplate();
if (globalTemplate.IsEmpty())
return result;
// Install a security handler with V8.
globalTemplate->SetAccessCheckCallbacks(
V8Custom::v8DOMWindowNamedSecurityCheck,
V8Custom::v8DOMWindowIndexedSecurityCheck,
v8::Integer::New(V8ClassIndex::DOMWINDOW));
// Dynamically tell v8 about our extensions now.
const char** extensionNames = new const char*[m_extensions.size()];
int index = 0;
for (V8ExtensionList::iterator it = m_extensions.begin();
it != m_extensions.end(); ++it) {
// Note: we check the loader URL here instead of the document URL
// because we might be currently loading an URL into a blank page.
// See http://code.google.com/p/chromium/issues/detail?id=10924
if (it->scheme.length() > 0 &&
(it->scheme != m_frame->loader()->activeDocumentLoader()->url().protocol() ||
it->scheme != m_frame->page()->mainFrame()->loader()->activeDocumentLoader()->url().protocol()))
continue;
extensionNames[index++] = it->extension->name();
}
v8::ExtensionConfiguration extensions(index, extensionNames);
result = v8::Context::New(&extensions, globalTemplate, global);
delete [] extensionNames;
extensionNames = 0;
return result;
}
// Create a new environment and setup the global object.
//
// The global object corresponds to a DOMWindow instance. However, to
// allow properties of the JS DOMWindow instance to be shadowed, we
// use a shadow object as the global object and use the JS DOMWindow
// instance as the prototype for that shadow object. The JS DOMWindow
// instance is undetectable from javascript code because the __proto__
// accessors skip that object.
//
// The shadow object and the DOMWindow instance are seen as one object
// from javascript. The javascript object that corresponds to a
// DOMWindow instance is the shadow object. When mapping a DOMWindow
// instance to a V8 object, we return the shadow object.
//
// To implement split-window, see
// 1) https://bugs.webkit.org/show_bug.cgi?id=17249
// 2) https://wiki.mozilla.org/Gecko:SplitWindow
// 3) https://bugzilla.mozilla.org/show_bug.cgi?id=296639
// we need to split the shadow object further into two objects:
// an outer window and an inner window. The inner window is the hidden
// prototype of the outer window. The inner window is the default
// global object of the context. A variable declared in the global
// scope is a property of the inner window.
//
// The outer window sticks to a Frame, it is exposed to JavaScript
// via window.window, window.self, window.parent, etc. The outer window
// has a security token which is the domain. The outer window cannot
// have its own properties. window.foo = 'x' is delegated to the
// inner window.
//
// When a frame navigates to a new page, the inner window is cut off
// the outer window, and the outer window identify is preserved for
// the frame. However, a new inner window is created for the new page.
// If there are JS code holds a closure to the old inner window,
// it won't be able to reach the outer window via its global object.
void V8Proxy::InitContextIfNeeded()
{
// Bail out if the context has already been initialized.
if (!m_context.IsEmpty())
return;
#ifdef ANDROID_INSTRUMENT
android::TimeCounter::start(android::TimeCounter::JavaScriptInitTimeCounter);
#endif
LOCK_V8;
// Create a handle scope for all local handles.
v8::HandleScope handle_scope;
// Setup the security handlers and message listener. This only has
// to be done once.
static bool v8_initialized = false;
if (!v8_initialized) {
// Tells V8 not to call the default OOM handler, binding code
// will handle it.
v8::V8::IgnoreOutOfMemoryException();
v8::V8::SetFatalErrorHandler(ReportFatalErrorInV8);
v8::V8::SetGlobalGCPrologueCallback(&GCPrologue);
v8::V8::SetGlobalGCEpilogueCallback(&GCEpilogue);
v8::V8::AddMessageListener(HandleConsoleMessage);
v8::V8::SetFailedAccessCheckCallbackFunction(ReportUnsafeJavaScriptAccess);
v8_initialized = true;
}
m_context = createNewContext(m_global);
if (m_context.IsEmpty())
return;
// Starting from now, use local context only.
v8::Local<v8::Context> context = GetContext();
v8::Context::Scope context_scope(context);
// Store the first global object created so we can reuse it.
if (m_global.IsEmpty()) {
m_global = v8::Persistent<v8::Object>::New(context->Global());
// Bail out if allocation of the first global objects fails.
if (m_global.IsEmpty()) {
DisposeContextHandles();
return;
}
#ifndef NDEBUG
RegisterGlobalHandle(PROXY, this, m_global);
#endif
}
// Allocate strings used during initialization.
v8::Handle<v8::String> object_string = v8::String::New("Object");
v8::Handle<v8::String> prototype_string = v8::String::New("prototype");
v8::Handle<v8::String> implicit_proto_string = v8::String::New("__proto__");
// Bail out if allocation failed.
if (object_string.IsEmpty() ||
prototype_string.IsEmpty() ||
implicit_proto_string.IsEmpty()) {
DisposeContextHandles();
return;
}
// Allocate clone cache and pre-allocated objects
v8::Handle<v8::Object> object = v8::Handle<v8::Object>::Cast(
m_global->Get(object_string));
m_object_prototype = v8::Persistent<v8::Value>::New(
object->Get(prototype_string));
m_wrapper_boilerplates = v8::Persistent<v8::Array>::New(
v8::Array::New(V8ClassIndex::WRAPPER_TYPE_COUNT));
// Bail out if allocation failed.
if (m_object_prototype.IsEmpty()) {
DisposeContextHandles();
return;
}
#ifndef NDEBUG
RegisterGlobalHandle(PROXY, this, m_object_prototype);
RegisterGlobalHandle(PROXY, this, m_wrapper_boilerplates);
#endif
// Create a new JS window object and use it as the prototype for the
// shadow global object.
v8::Handle<v8::Function> window_constructor =
GetConstructor(V8ClassIndex::DOMWINDOW);
v8::Local<v8::Object> js_window =
SafeAllocation::NewInstance(window_constructor);
// Bail out if allocation failed.
if (js_window.IsEmpty()) {
DisposeContextHandles();
return;
}
DOMWindow* window = m_frame->domWindow();
// Wrap the window.
SetDOMWrapper(js_window,
V8ClassIndex::ToInt(V8ClassIndex::DOMWINDOW),
window);
window->ref();
V8Proxy::SetJSWrapperForDOMObject(window,
v8::Persistent<v8::Object>::New(js_window));
// Insert the window instance as the prototype of the shadow object.
v8::Handle<v8::Object> v8_global = context->Global();
v8_global->Set(implicit_proto_string, js_window);
updateDocument();
SetSecurityToken();
m_frame->loader()->dispatchWindowObjectAvailable();
#ifdef ANDROID_INSTRUMENT
android::TimeCounter::record(android::TimeCounter::JavaScriptInitTimeCounter, __FUNCTION__);
#endif
}
void V8Proxy::SetDOMException(int exception_code)
{
if (exception_code <= 0)
return;
ExceptionCodeDescription description;
getExceptionCodeDescription(exception_code, description);
v8::Handle<v8::Value> exception;
switch (description.type) {
case DOMExceptionType:
exception = ToV8Object(V8ClassIndex::DOMCOREEXCEPTION,
DOMCoreException::create(description));
break;
case RangeExceptionType:
exception = ToV8Object(V8ClassIndex::RANGEEXCEPTION,
RangeException::create(description));
break;
case EventExceptionType:
exception = ToV8Object(V8ClassIndex::EVENTEXCEPTION,
EventException::create(description));
break;
case XMLHttpRequestExceptionType:
exception = ToV8Object(V8ClassIndex::XMLHTTPREQUESTEXCEPTION,
XMLHttpRequestException::create(description));
break;
#if ENABLE(SVG)
case SVGExceptionType:
exception = ToV8Object(V8ClassIndex::SVGEXCEPTION,
SVGException::create(description));
break;
#endif
#if ENABLE(XPATH)
case XPathExceptionType:
exception = ToV8Object(V8ClassIndex::XPATHEXCEPTION,
XPathException::create(description));
break;
#endif
}
ASSERT(!exception.IsEmpty());
v8::ThrowException(exception);
}
v8::Handle<v8::Value> V8Proxy::ThrowError(ErrorType type, const char* message)
{
switch (type) {
case RANGE_ERROR:
return v8::ThrowException(v8::Exception::RangeError(v8String(message)));
case REFERENCE_ERROR:
return v8::ThrowException(
v8::Exception::ReferenceError(v8String(message)));
case SYNTAX_ERROR:
return v8::ThrowException(v8::Exception::SyntaxError(v8String(message)));
case TYPE_ERROR:
return v8::ThrowException(v8::Exception::TypeError(v8String(message)));
case GENERAL_ERROR:
return v8::ThrowException(v8::Exception::Error(v8String(message)));
default:
ASSERT(false);
return v8::Handle<v8::Value>();
}
}
v8::Local<v8::Context> V8Proxy::GetContext(Frame* frame)
{
V8Proxy* proxy = retrieve(frame);
if (!proxy)
return v8::Local<v8::Context>();
proxy->InitContextIfNeeded();
return proxy->GetContext();
}
v8::Local<v8::Context> V8Proxy::GetCurrentContext()
{
return v8::Context::GetCurrent();
}
v8::Handle<v8::Value> V8Proxy::ToV8Object(V8ClassIndex::V8WrapperType type, void* imp)
{
ASSERT(type != V8ClassIndex::EVENTLISTENER);
ASSERT(type != V8ClassIndex::EVENTTARGET);
ASSERT(type != V8ClassIndex::EVENT);
bool is_active_dom_object = false;
switch (type) {
#define MAKE_CASE(TYPE, NAME) case V8ClassIndex::TYPE:
DOM_NODE_TYPES(MAKE_CASE)
#if ENABLE(SVG)
SVG_NODE_TYPES(MAKE_CASE)
#endif
return NodeToV8Object(static_cast<Node*>(imp));
case V8ClassIndex::CSSVALUE:
return CSSValueToV8Object(static_cast<CSSValue*>(imp));
case V8ClassIndex::CSSRULE:
return CSSRuleToV8Object(static_cast<CSSRule*>(imp));
case V8ClassIndex::STYLESHEET:
return StyleSheetToV8Object(static_cast<StyleSheet*>(imp));
case V8ClassIndex::DOMWINDOW:
return WindowToV8Object(static_cast<DOMWindow*>(imp));
#if ENABLE(SVG)
SVG_NONNODE_TYPES(MAKE_CASE)
if (type == V8ClassIndex::SVGELEMENTINSTANCE)
return SVGElementInstanceToV8Object(static_cast<SVGElementInstance*>(imp));
return SVGObjectWithContextToV8Object(type, imp);
#endif
ACTIVE_DOM_OBJECT_TYPES(MAKE_CASE)
is_active_dom_object = true;
break;
default:
break;
}
#undef MAKE_CASE
if (!imp) return v8::Null();
// Non DOM node
v8::Persistent<v8::Object> result = is_active_dom_object ?
getActiveDOMObjectMap().get(imp) :
getDOMObjectMap().get(imp);
if (result.IsEmpty()) {
v8::Local<v8::Object> v8obj = InstantiateV8Object(type, type, imp);
if (!v8obj.IsEmpty()) {
// Go through big switch statement, it has some duplications
// that were handled by code above (such as CSSVALUE, CSSRULE, etc).
switch (type) {
#define MAKE_CASE(TYPE, NAME) \
case V8ClassIndex::TYPE: static_cast<NAME*>(imp)->ref(); break;
DOM_OBJECT_TYPES(MAKE_CASE)
#undef MAKE_CASE
default:
ASSERT(false);
}
result = v8::Persistent<v8::Object>::New(v8obj);
if (is_active_dom_object)
SetJSWrapperForActiveDOMObject(imp, result);
else
SetJSWrapperForDOMObject(imp, result);
// Special case for non-node objects associated with a
// DOMWindow. Both Safari and FF let the JS wrappers for these
// objects survive GC. To mimic their behavior, V8 creates
// hidden references from the DOMWindow to these wrapper
// objects. These references get cleared when the DOMWindow is
// reused by a new page.
switch (type) {
case V8ClassIndex::CONSOLE:
SetHiddenWindowReference(static_cast<Console*>(imp)->frame(),
V8Custom::kDOMWindowConsoleIndex, result);
break;
case V8ClassIndex::HISTORY:
SetHiddenWindowReference(static_cast<History*>(imp)->frame(),
V8Custom::kDOMWindowHistoryIndex, result);
break;
case V8ClassIndex::NAVIGATOR:
SetHiddenWindowReference(static_cast<Navigator*>(imp)->frame(),
V8Custom::kDOMWindowNavigatorIndex, result);
break;
case V8ClassIndex::SCREEN:
SetHiddenWindowReference(static_cast<Screen*>(imp)->frame(),
V8Custom::kDOMWindowScreenIndex, result);
break;
case V8ClassIndex::LOCATION:
SetHiddenWindowReference(static_cast<Location*>(imp)->frame(),
V8Custom::kDOMWindowLocationIndex, result);
break;
case V8ClassIndex::DOMSELECTION:
SetHiddenWindowReference(static_cast<DOMSelection*>(imp)->frame(),
V8Custom::kDOMWindowDOMSelectionIndex, result);
break;
case V8ClassIndex::BARINFO: {
BarInfo* barinfo = static_cast<BarInfo*>(imp);
Frame* frame = barinfo->frame();
switch (barinfo->type()) {
case BarInfo::Locationbar:
SetHiddenWindowReference(frame, V8Custom::kDOMWindowLocationbarIndex, result);
break;
case BarInfo::Menubar:
SetHiddenWindowReference(frame, V8Custom::kDOMWindowMenubarIndex, result);
break;
case BarInfo::Personalbar:
SetHiddenWindowReference(frame, V8Custom::kDOMWindowPersonalbarIndex, result);
break;
case BarInfo::Scrollbars:
SetHiddenWindowReference(frame, V8Custom::kDOMWindowScrollbarsIndex, result);
break;
case BarInfo::Statusbar:
SetHiddenWindowReference(frame, V8Custom::kDOMWindowStatusbarIndex, result);
break;
case BarInfo::Toolbar:
SetHiddenWindowReference(frame, V8Custom::kDOMWindowToolbarIndex, result);
break;
}
break;
}
default:
break;
}
}
}
return result;
}
void V8Proxy::SetHiddenWindowReference(Frame* frame,
const int internal_index,
v8::Handle<v8::Object> jsobj)
{
// Get DOMWindow
if (!frame) return; // Object might be detached from window
v8::Handle<v8::Context> context = GetContext(frame);
if (context.IsEmpty()) return;
ASSERT(internal_index < V8Custom::kDOMWindowInternalFieldCount);
v8::Handle<v8::Object> global = context->Global();
// Look for real DOM wrapper.
global = LookupDOMWrapper(V8ClassIndex::DOMWINDOW, global);
ASSERT(!global.IsEmpty());
ASSERT(global->GetInternalField(internal_index)->IsUndefined());
global->SetInternalField(internal_index, jsobj);
}
V8ClassIndex::V8WrapperType V8Proxy::GetDOMWrapperType(v8::Handle<v8::Object> object)
{
ASSERT(MaybeDOMWrapper(object));
v8::Handle<v8::Value> type =
object->GetInternalField(V8Custom::kDOMWrapperTypeIndex);
return V8ClassIndex::FromInt(type->Int32Value());
}
void* V8Proxy::ToNativeObjectImpl(V8ClassIndex::V8WrapperType type,
v8::Handle<v8::Value> object)
{
// Native event listener is per frame, it cannot be handled
// by this generic function.
ASSERT(type != V8ClassIndex::EVENTLISTENER);
ASSERT(type != V8ClassIndex::EVENTTARGET);
ASSERT(MaybeDOMWrapper(object));
switch (type) {
#define MAKE_CASE(TYPE, NAME) case V8ClassIndex::TYPE:
DOM_NODE_TYPES(MAKE_CASE)
#if ENABLE(SVG)
SVG_NODE_TYPES(MAKE_CASE)
#endif
ASSERT(false);
return NULL;
case V8ClassIndex::XMLHTTPREQUEST:
return DOMWrapperToNative<XMLHttpRequest>(object);
case V8ClassIndex::EVENT:
return DOMWrapperToNative<Event>(object);
case V8ClassIndex::CSSRULE:
return DOMWrapperToNative<CSSRule>(object);
default:
break;
}
#undef MAKE_CASE
return DOMWrapperToNative<void>(object);
}
void* V8Proxy::ToSVGPODTypeImpl(V8ClassIndex::V8WrapperType type,
v8::Handle<v8::Value> object) {
return IsWrapperOfType(object, type)
? DOMWrapperToNative<void>(object)
: NULL;
}
v8::Handle<v8::Object> V8Proxy::LookupDOMWrapper(
V8ClassIndex::V8WrapperType type, v8::Handle<v8::Value> value)
{
if (value.IsEmpty())
return v8::Handle<v8::Object>();
v8::Handle<v8::FunctionTemplate> desc = V8Proxy::GetTemplate(type);
while (value->IsObject()) {
v8::Handle<v8::Object> object = v8::Handle<v8::Object>::Cast(value);
if (desc->HasInstance(object))
return object;
value = object->GetPrototype();
}
return v8::Handle<v8::Object>();
}
// static
void* V8Proxy::DOMWrapperToNodeHelper(v8::Handle<v8::Value> value) {
ASSERT(MaybeDOMWrapper(value));
v8::Handle<v8::Object> object = v8::Handle<v8::Object>::Cast(value);
ASSERT(GetDOMWrapperType(object) == V8ClassIndex::NODE);
v8::Handle<v8::Value> wrapper =
object->GetInternalField(V8Custom::kDOMWrapperObjectIndex);
return ExtractCPointer<Node>(wrapper);
}
PassRefPtr<NodeFilter> V8Proxy::ToNativeNodeFilter(v8::Handle<v8::Value> filter)
{
// A NodeFilter is used when walking through a DOM tree or iterating tree
// nodes.
// TODO: we may want to cache NodeFilterCondition and NodeFilter
// object, but it is minor.
// NodeFilter is passed to NodeIterator that has a ref counted pointer
// to NodeFilter. NodeFilter has a ref counted pointer to NodeFilterCondition.
// In NodeFilterCondition, filter object is persisted in its constructor,
// and disposed in its destructor.
if (!filter->IsFunction())
return 0;
NodeFilterCondition* cond = new V8NodeFilterCondition(filter);
return NodeFilter::create(cond);
}
v8::Local<v8::Object> V8Proxy::InstantiateV8Object(
V8ClassIndex::V8WrapperType desc_type,
V8ClassIndex::V8WrapperType cptr_type,
void* imp)
{
// Make a special case for document.all
if (desc_type == V8ClassIndex::HTMLCOLLECTION &&
static_cast<HTMLCollection*>(imp)->type() == DocAll) {
desc_type = V8ClassIndex::UNDETECTABLEHTMLCOLLECTION;
}
V8Proxy* proxy = V8Proxy::retrieve();
v8::Local<v8::Object> instance;
if (proxy) {
instance = proxy->CreateWrapperFromCache(desc_type);
} else {
v8::Local<v8::Function> function = GetTemplate(desc_type)->GetFunction();
instance = SafeAllocation::NewInstance(function);
}
if (!instance.IsEmpty()) {
// Avoid setting the DOM wrapper for failed allocations.
SetDOMWrapper(instance, V8ClassIndex::ToInt(cptr_type), imp);
}
return instance;
}
v8::Handle<v8::Value> V8Proxy::CheckNewLegal(const v8::Arguments& args)
{
if (!AllowAllocation::m_current)
return ThrowError(TYPE_ERROR, "Illegal constructor");
return args.This();
}
void V8Proxy::SetDOMWrapper(v8::Handle<v8::Object> obj, int type, void* cptr)
{
ASSERT(obj->InternalFieldCount() >= 2);
obj->SetInternalField(V8Custom::kDOMWrapperObjectIndex, WrapCPointer(cptr));
obj->SetInternalField(V8Custom::kDOMWrapperTypeIndex, v8::Integer::New(type));
}
#ifndef NDEBUG
bool V8Proxy::MaybeDOMWrapper(v8::Handle<v8::Value> value)
{
if (value.IsEmpty() || !value->IsObject()) return false;
v8::Handle<v8::Object> obj = v8::Handle<v8::Object>::Cast(value);
if (obj->InternalFieldCount() == 0) return false;
ASSERT(obj->InternalFieldCount() >=
V8Custom::kDefaultWrapperInternalFieldCount);
v8::Handle<v8::Value> type =
obj->GetInternalField(V8Custom::kDOMWrapperTypeIndex);
ASSERT(type->IsInt32());
ASSERT(V8ClassIndex::INVALID_CLASS_INDEX < type->Int32Value() &&
type->Int32Value() < V8ClassIndex::CLASSINDEX_END);
v8::Handle<v8::Value> wrapper =
obj->GetInternalField(V8Custom::kDOMWrapperObjectIndex);
ASSERT(wrapper->IsNumber() || wrapper->IsExternal());
return true;
}
#endif
bool V8Proxy::IsDOMEventWrapper(v8::Handle<v8::Value> value)
{
// All kinds of events use EVENT as dom type in JS wrappers.
// See EventToV8Object
return IsWrapperOfType(value, V8ClassIndex::EVENT);
}
bool V8Proxy::IsWrapperOfType(v8::Handle<v8::Value> value,
V8ClassIndex::V8WrapperType classType)
{
if (value.IsEmpty() || !value->IsObject()) return false;
v8::Handle<v8::Object> obj = v8::Handle<v8::Object>::Cast(value);
if (obj->InternalFieldCount() == 0) return false;
ASSERT(obj->InternalFieldCount() >=
V8Custom::kDefaultWrapperInternalFieldCount);
v8::Handle<v8::Value> wrapper =
obj->GetInternalField(V8Custom::kDOMWrapperObjectIndex);
ASSERT(wrapper->IsNumber() || wrapper->IsExternal());
v8::Handle<v8::Value> type =
obj->GetInternalField(V8Custom::kDOMWrapperTypeIndex);
ASSERT(type->IsInt32());
ASSERT(V8ClassIndex::INVALID_CLASS_INDEX < type->Int32Value() &&
type->Int32Value() < V8ClassIndex::CLASSINDEX_END);
return V8ClassIndex::FromInt(type->Int32Value()) == classType;
}
#if ENABLE(VIDEO)
#define FOR_EACH_VIDEO_TAG(macro) \
macro(audio, AUDIO) \
macro(source, SOURCE) \
macro(video, VIDEO)
#else
#define FOR_EACH_VIDEO_TAG(macro)
#endif
#define FOR_EACH_TAG(macro) \
macro(a, ANCHOR) \
macro(applet, APPLET) \
macro(area, AREA) \
macro(base, BASE) \
macro(basefont, BASEFONT) \
macro(blockquote, BLOCKQUOTE) \
macro(body, BODY) \
macro(br, BR) \
macro(button, BUTTON) \
macro(caption, TABLECAPTION) \
macro(col, TABLECOL) \
macro(colgroup, TABLECOL) \
macro(del, MOD) \
macro(canvas, CANVAS) \
macro(dir, DIRECTORY) \
macro(div, DIV) \
macro(dl, DLIST) \
macro(embed, EMBED) \
macro(fieldset, FIELDSET) \
macro(font, FONT) \
macro(form, FORM) \
macro(frame, FRAME) \
macro(frameset, FRAMESET) \
macro(h1, HEADING) \
macro(h2, HEADING) \
macro(h3, HEADING) \
macro(h4, HEADING) \
macro(h5, HEADING) \
macro(h6, HEADING) \
macro(head, HEAD) \
macro(hr, HR) \
macro(html, HTML) \
macro(img, IMAGE) \
macro(iframe, IFRAME) \
macro(image, IMAGE) \
macro(input, INPUT) \
macro(ins, MOD) \
macro(isindex, ISINDEX) \
macro(keygen, SELECT) \
macro(label, LABEL) \
macro(legend, LEGEND) \
macro(li, LI) \
macro(link, LINK) \
macro(listing, PRE) \
macro(map, MAP) \
macro(marquee, MARQUEE) \
macro(menu, MENU) \
macro(meta, META) \
macro(object, OBJECT) \
macro(ol, OLIST) \
macro(optgroup, OPTGROUP) \
macro(option, OPTION) \
macro(p, PARAGRAPH) \
macro(param, PARAM) \
macro(pre, PRE) \
macro(q, QUOTE) \
macro(script, SCRIPT) \
macro(select, SELECT) \
macro(style, STYLE) \
macro(table, TABLE) \
macro(thead, TABLESECTION) \
macro(tbody, TABLESECTION) \
macro(tfoot, TABLESECTION) \
macro(td, TABLECELL) \
macro(th, TABLECELL) \
macro(tr, TABLEROW) \
macro(textarea, TEXTAREA) \
macro(title, TITLE) \
macro(ul, ULIST) \
macro(xmp, PRE)
V8ClassIndex::V8WrapperType V8Proxy::GetHTMLElementType(HTMLElement* element)
{
static HashMap<String, V8ClassIndex::V8WrapperType> map;
if (map.isEmpty()) {
#define ADD_TO_HASH_MAP(tag, name) \
map.set(#tag, V8ClassIndex::HTML##name##ELEMENT);
FOR_EACH_TAG(ADD_TO_HASH_MAP)
#if ENABLE(VIDEO)
if (MediaPlayer::isAvailable()) {
FOR_EACH_VIDEO_TAG(ADD_TO_HASH_MAP)
}
#endif
#undef ADD_TO_HASH_MAP
}
V8ClassIndex::V8WrapperType t = map.get(element->localName().impl());
if (t == 0)
return V8ClassIndex::HTMLELEMENT;
return t;
}
#undef FOR_EACH_TAG
#if ENABLE(SVG)
#if ENABLE(SVG_ANIMATION)
#define FOR_EACH_ANIMATION_TAG(macro) \
macro(animateColor, ANIMATECOLOR) \
macro(animate, ANIMATE) \
macro(animateTransform, ANIMATETRANSFORM) \
macro(set, SET)
#else
#define FOR_EACH_ANIMATION_TAG(macro)
#endif
#if ENABLE(SVG_FILTERS)
#define FOR_EACH_FILTERS_TAG(macro) \
macro(feBlend, FEBLEND) \
macro(feColorMatrix, FECOLORMATRIX) \
macro(feComponentTransfer, FECOMPONENTTRANSFER) \
macro(feComposite, FECOMPOSITE) \
macro(feDiffuseLighting, FEDIFFUSELIGHTING) \
macro(feDisplacementMap, FEDISPLACEMENTMAP) \
macro(feDistantLight, FEDISTANTLIGHT) \
macro(feFlood, FEFLOOD) \
macro(feFuncA, FEFUNCA) \
macro(feFuncB, FEFUNCB) \
macro(feFuncG, FEFUNCG) \
macro(feFuncR, FEFUNCR) \
macro(feGaussianBlur, FEGAUSSIANBLUR) \
macro(feImage, FEIMAGE) \
macro(feMerge, FEMERGE) \
macro(feMergeNode, FEMERGENODE) \
macro(feOffset, FEOFFSET) \
macro(fePointLight, FEPOINTLIGHT) \
macro(feSpecularLighting, FESPECULARLIGHTING) \
macro(feSpotLight, FESPOTLIGHT) \
macro(feTile, FETILE) \
macro(feTurbulence, FETURBULENCE) \
macro(filter, FILTER)
#else
#define FOR_EACH_FILTERS_TAG(macro)
#endif
#if ENABLE(SVG_FONTS)
#define FOR_EACH_FONTS_TAG(macro) \
macro(definition-src, DEFINITIONSRC) \
macro(font-face, FONTFACE) \
macro(font-face-format, FONTFACEFORMAT) \
macro(font-face-name, FONTFACENAME) \
macro(font-face-src, FONTFACESRC) \
macro(font-face-uri, FONTFACEURI)
#else
#define FOR_EACH_FONTS_TAG(marco)
#endif
#if ENABLE(SVG_FOREIGN_OBJECT)
#define FOR_EACH_FOREIGN_OBJECT_TAG(macro) \
macro(foreignObject, FOREIGNOBJECT)
#else
#define FOR_EACH_FOREIGN_OBJECT_TAG(macro)
#endif
#if ENABLE(SVG_USE)
#define FOR_EACH_USE_TAG(macro) \
macro(use, USE)
#else
#define FOR_EACH_USE_TAG(macro)
#endif
#define FOR_EACH_TAG(macro) \
FOR_EACH_ANIMATION_TAG(macro) \
FOR_EACH_FILTERS_TAG(macro) \
FOR_EACH_FONTS_TAG(macro) \
FOR_EACH_FOREIGN_OBJECT_TAG(macro) \
FOR_EACH_USE_TAG(macro) \
macro(a, A) \
macro(altGlyph, ALTGLYPH) \
macro(circle, CIRCLE) \
macro(clipPath, CLIPPATH) \
macro(cursor, CURSOR) \
macro(defs, DEFS) \
macro(desc, DESC) \
macro(ellipse, ELLIPSE) \
macro(g, G) \
macro(glyph, GLYPH) \
macro(image, IMAGE) \
macro(linearGradient, LINEARGRADIENT) \
macro(line, LINE) \
macro(marker, MARKER) \
macro(mask, MASK) \
macro(metadata, METADATA) \
macro(path, PATH) \
macro(pattern, PATTERN) \
macro(polyline, POLYLINE) \
macro(polygon, POLYGON) \
macro(radialGradient, RADIALGRADIENT) \
macro(rect, RECT) \
macro(script, SCRIPT) \
macro(stop, STOP) \
macro(style, STYLE) \
macro(svg, SVG) \
macro(switch, SWITCH) \
macro(symbol, SYMBOL) \
macro(text, TEXT) \
macro(textPath, TEXTPATH) \
macro(title, TITLE) \
macro(tref, TREF) \
macro(tspan, TSPAN) \
macro(view, VIEW) \
// end of macro
V8ClassIndex::V8WrapperType V8Proxy::GetSVGElementType(SVGElement* element)
{
static HashMap<String, V8ClassIndex::V8WrapperType> map;
if (map.isEmpty()) {
#define ADD_TO_HASH_MAP(tag, name) \
map.set(#tag, V8ClassIndex::SVG##name##ELEMENT);
FOR_EACH_TAG(ADD_TO_HASH_MAP)
#undef ADD_TO_HASH_MAP
}
V8ClassIndex::V8WrapperType t = map.get(element->localName().impl());
if (t == 0) return V8ClassIndex::SVGELEMENT;
return t;
}
#undef FOR_EACH_TAG
#endif // ENABLE(SVG)
v8::Handle<v8::Value> V8Proxy::EventToV8Object(Event* event)
{
if (!event)
return v8::Null();
v8::Handle<v8::Object> wrapper = getDOMObjectMap().get(event);
if (!wrapper.IsEmpty())
return wrapper;
V8ClassIndex::V8WrapperType type = V8ClassIndex::EVENT;
if (event->isUIEvent()) {
if (event->isKeyboardEvent())
type = V8ClassIndex::KEYBOARDEVENT;
else if (event->isTextEvent())
type = V8ClassIndex::TEXTEVENT;
else if (event->isMouseEvent())
type = V8ClassIndex::MOUSEEVENT;
else if (event->isWheelEvent())
type = V8ClassIndex::WHEELEVENT;
#if ENABLE(TOUCH_EVENTS)
else if (event->isTouchEvent())
type = V8ClassIndex::TOUCHEVENT;
#endif
#if ENABLE(SVG)
else if (event->isSVGZoomEvent())
type = V8ClassIndex::SVGZOOMEVENT;
#endif
else
type = V8ClassIndex::UIEVENT;
} else if (event->isMutationEvent())
type = V8ClassIndex::MUTATIONEVENT;
else if (event->isOverflowEvent())
type = V8ClassIndex::OVERFLOWEVENT;
else if (event->isMessageEvent())
type = V8ClassIndex::MESSAGEEVENT;
else if (event->isProgressEvent()) {
if (event->isXMLHttpRequestProgressEvent())
type = V8ClassIndex::XMLHTTPREQUESTPROGRESSEVENT;
else
type = V8ClassIndex::PROGRESSEVENT;
} else if (event->isWebKitAnimationEvent())
type = V8ClassIndex::WEBKITANIMATIONEVENT;
else if (event->isWebKitTransitionEvent())
type = V8ClassIndex::WEBKITTRANSITIONEVENT;
v8::Handle<v8::Object> result =
InstantiateV8Object(type, V8ClassIndex::EVENT, event);
if (result.IsEmpty()) {
// Instantiation failed. Avoid updating the DOM object map and
// return null which is already handled by callers of this function
// in case the event is NULL.
return v8::Null();
}
event->ref(); // fast ref
SetJSWrapperForDOMObject(event, v8::Persistent<v8::Object>::New(result));
return result;
}
// Caller checks node is not null.
v8::Handle<v8::Value> V8Proxy::NodeToV8Object(Node* node)
{
if (!node) return v8::Null();
// Find the context to which the node belongs and create the wrapper
// in that context. If the node is not in a document, the current
// context is used.
//
// Getting the context might initialize the context which can instantiate
// a document wrapper. Therefore, we get the context before checking if
// the node already has a wrapper.
v8::Local<v8::Context> context;
Document* doc = node->document();
if (doc) {
context = V8Proxy::GetContext(doc->frame());
}
v8::Handle<v8::Object> wrapper = getDOMNodeMap().get(node);
if (!wrapper.IsEmpty())
return wrapper;
bool is_document = false; // document type node has special handling
V8ClassIndex::V8WrapperType type;
switch (node->nodeType()) {
case Node::ELEMENT_NODE:
if (node->isHTMLElement())
type = GetHTMLElementType(static_cast<HTMLElement*>(node));
#if ENABLE(SVG)
else if (node->isSVGElement())
type = GetSVGElementType(static_cast<SVGElement*>(node));
#endif
else
type = V8ClassIndex::ELEMENT;
break;
case Node::ATTRIBUTE_NODE:
type = V8ClassIndex::ATTR;
break;
case Node::TEXT_NODE:
type = V8ClassIndex::TEXT;
break;
case Node::CDATA_SECTION_NODE:
type = V8ClassIndex::CDATASECTION;
break;
case Node::ENTITY_NODE:
type = V8ClassIndex::ENTITY;
break;
case Node::PROCESSING_INSTRUCTION_NODE:
type = V8ClassIndex::PROCESSINGINSTRUCTION;
break;
case Node::COMMENT_NODE:
type = V8ClassIndex::COMMENT;
break;
case Node::DOCUMENT_NODE: {
is_document = true;
Document* doc = static_cast<Document*>(node);
if (doc->isHTMLDocument())
type = V8ClassIndex::HTMLDOCUMENT;
#if ENABLE(SVG)
else if (doc->isSVGDocument())
type = V8ClassIndex::SVGDOCUMENT;
#endif
else
type = V8ClassIndex::DOCUMENT;
break;
}
case Node::DOCUMENT_TYPE_NODE:
type = V8ClassIndex::DOCUMENTTYPE;
break;
case Node::NOTATION_NODE:
type = V8ClassIndex::NOTATION;
break;
case Node::DOCUMENT_FRAGMENT_NODE:
type = V8ClassIndex::DOCUMENTFRAGMENT;
break;
case Node::ENTITY_REFERENCE_NODE:
type = V8ClassIndex::ENTITYREFERENCE;
break;
default:
type = V8ClassIndex::NODE;
}
// Enter the node's context and create the wrapper in that context.
if (!context.IsEmpty()) {
context->Enter();
}
v8::Local<v8::Object> result =
InstantiateV8Object(type, V8ClassIndex::NODE, node);
// Exit the node's context if it was entered.
if (!context.IsEmpty()) {
context->Exit();
}
if (result.IsEmpty()) {
// If instantiation failed it's important not to add the result
// to the DOM node map. Instead we return an empty handle, which
// should already be handled by callers of this function in case
// the node is NULL.
return result;
}
node->ref();
SetJSWrapperForDOMNode(node, v8::Persistent<v8::Object>::New(result));
if (is_document) {
Document* doc = static_cast<Document*>(node);
V8Proxy* proxy = V8Proxy::retrieve(doc->frame());
if (proxy)
proxy->UpdateDocumentWrapper(result);
if (type == V8ClassIndex::HTMLDOCUMENT) {
// Create marker object and insert it in two internal fields.
// This is used to implement temporary shadowing of
// document.all.
ASSERT(result->InternalFieldCount() ==
V8Custom::kHTMLDocumentInternalFieldCount);
v8::Local<v8::Object> marker = v8::Object::New();
result->SetInternalField(V8Custom::kHTMLDocumentMarkerIndex, marker);
result->SetInternalField(V8Custom::kHTMLDocumentShadowIndex, marker);
}
}
return result;
}
// A JS object of type EventTarget can only be the following possible types:
// 1) EventTargetNode; 2) DOMWindow 3) XMLHttpRequest; 4) MessagePort;
// 5) XMLHttpRequestUpload
// check EventTarget.h for new type conversion methods
v8::Handle<v8::Value> V8Proxy::EventTargetToV8Object(EventTarget* target)
{
if (!target)
return v8::Null();
#if ENABLE(SVG)
SVGElementInstance* instance = target->toSVGElementInstance();
if (instance)
return ToV8Object(V8ClassIndex::SVGELEMENTINSTANCE, instance);
#endif
#if ENABLE(WORKERS)
Worker* worker = target->toWorker();
if (worker)
return ToV8Object(V8ClassIndex::WORKER, worker);
#endif // WORKERS
#if ENABLE(OFFLINE_WEB_APPLICATIONS)
DOMApplicationCache* app_cache = target->toDOMApplicationCache();
if (app_cache)
return ToV8Object(V8ClassIndex::DOMAPPLICATIONCACHE, app_cache);
#endif
Node* node = target->toNode();
if (node)
return NodeToV8Object(node);
if (DOMWindow* domWindow = target->toDOMWindow())
return ToV8Object(V8ClassIndex::DOMWINDOW, domWindow);
// XMLHttpRequest is created within its JS counterpart.
XMLHttpRequest* xhr = target->toXMLHttpRequest();
if (xhr) {
v8::Handle<v8::Object> wrapper = getActiveDOMObjectMap().get(xhr);
ASSERT(!wrapper.IsEmpty());
return wrapper;
}
// MessagePort is created within its JS counterpart
MessagePort* port = target->toMessagePort();
if (port) {
v8::Handle<v8::Object> wrapper = getActiveDOMObjectMap().get(port);
ASSERT(!wrapper.IsEmpty());
return wrapper;
}
XMLHttpRequestUpload* upload = target->toXMLHttpRequestUpload();
if (upload) {
v8::Handle<v8::Object> wrapper = getDOMObjectMap().get(upload);
ASSERT(!wrapper.IsEmpty());
return wrapper;
}
ASSERT(0);
return v8::Handle<v8::Value>();
}
v8::Handle<v8::Value> V8Proxy::EventListenerToV8Object(
EventListener* listener)
{
if (listener == 0) return v8::Null();
// TODO(fqian): can a user take a lazy event listener and set to other places?
V8AbstractEventListener* v8listener =
static_cast<V8AbstractEventListener*>(listener);
return v8listener->getListenerObject();
}
v8::Handle<v8::Value> V8Proxy::DOMImplementationToV8Object(
DOMImplementation* impl)
{
v8::Handle<v8::Object> result =
InstantiateV8Object(V8ClassIndex::DOMIMPLEMENTATION,
V8ClassIndex::DOMIMPLEMENTATION,
impl);
if (result.IsEmpty()) {
// If the instantiation failed, we ignore it and return null instead
// of returning an empty handle.
return v8::Null();
}
return result;
}
v8::Handle<v8::Value> V8Proxy::StyleSheetToV8Object(StyleSheet* sheet)
{
if (!sheet) return v8::Null();
v8::Handle<v8::Object> wrapper = getDOMObjectMap().get(sheet);
if (!wrapper.IsEmpty())
return wrapper;
V8ClassIndex::V8WrapperType type = V8ClassIndex::STYLESHEET;
if (sheet->isCSSStyleSheet())
type = V8ClassIndex::CSSSTYLESHEET;
v8::Handle<v8::Object> result =
InstantiateV8Object(type, V8ClassIndex::STYLESHEET, sheet);
if (!result.IsEmpty()) {
// Only update the DOM object map if the result is non-empty.
sheet->ref();
SetJSWrapperForDOMObject(sheet, v8::Persistent<v8::Object>::New(result));
}
// Add a hidden reference from stylesheet object to its owner node.
Node* owner_node = sheet->ownerNode();
if (owner_node) {
v8::Handle<v8::Object> owner =
v8::Handle<v8::Object>::Cast(NodeToV8Object(owner_node));
result->SetInternalField(V8Custom::kStyleSheetOwnerNodeIndex, owner);
}
return result;
}
v8::Handle<v8::Value> V8Proxy::CSSValueToV8Object(CSSValue* value)
{
if (!value) return v8::Null();
v8::Handle<v8::Object> wrapper = getDOMObjectMap().get(value);
if (!wrapper.IsEmpty())
return wrapper;
V8ClassIndex::V8WrapperType type;
if (value->isWebKitCSSTransformValue())
type = V8ClassIndex::WEBKITCSSTRANSFORMVALUE;
else if (value->isValueList())
type = V8ClassIndex::CSSVALUELIST;
else if (value->isPrimitiveValue())
type = V8ClassIndex::CSSPRIMITIVEVALUE;
#if ENABLE(SVG)
else if (value->isSVGPaint())
type = V8ClassIndex::SVGPAINT;
else if (value->isSVGColor())
type = V8ClassIndex::SVGCOLOR;
#endif
else
type = V8ClassIndex::CSSVALUE;
v8::Handle<v8::Object> result =
InstantiateV8Object(type, V8ClassIndex::CSSVALUE, value);
if (!result.IsEmpty()) {
// Only update the DOM object map if the result is non-empty.
value->ref();
SetJSWrapperForDOMObject(value, v8::Persistent<v8::Object>::New(result));
}
return result;
}
v8::Handle<v8::Value> V8Proxy::CSSRuleToV8Object(CSSRule* rule)
{
if (!rule) return v8::Null();
v8::Handle<v8::Object> wrapper = getDOMObjectMap().get(rule);
if (!wrapper.IsEmpty())
return wrapper;
V8ClassIndex::V8WrapperType type;
switch (rule->type()) {
case CSSRule::STYLE_RULE:
type = V8ClassIndex::CSSSTYLERULE;
break;
case CSSRule::CHARSET_RULE:
type = V8ClassIndex::CSSCHARSETRULE;
break;
case CSSRule::IMPORT_RULE:
type = V8ClassIndex::CSSIMPORTRULE;
break;
case CSSRule::MEDIA_RULE:
type = V8ClassIndex::CSSMEDIARULE;
break;
case CSSRule::FONT_FACE_RULE:
type = V8ClassIndex::CSSFONTFACERULE;
break;
case CSSRule::PAGE_RULE:
type = V8ClassIndex::CSSPAGERULE;
break;
case CSSRule::VARIABLES_RULE:
type = V8ClassIndex::CSSVARIABLESRULE;
break;
case CSSRule::WEBKIT_KEYFRAME_RULE:
type = V8ClassIndex::WEBKITCSSKEYFRAMERULE;
break;
case CSSRule::WEBKIT_KEYFRAMES_RULE:
type = V8ClassIndex::WEBKITCSSKEYFRAMESRULE;
break;
default: // CSSRule::UNKNOWN_RULE
type = V8ClassIndex::CSSRULE;
break;
}
v8::Handle<v8::Object> result =
InstantiateV8Object(type, V8ClassIndex::CSSRULE, rule);
if (!result.IsEmpty()) {
// Only update the DOM object map if the result is non-empty.
rule->ref();
SetJSWrapperForDOMObject(rule, v8::Persistent<v8::Object>::New(result));
}
return result;
}
v8::Handle<v8::Value> V8Proxy::WindowToV8Object(DOMWindow* window)
{
if (!window) return v8::Null();
// Initializes environment of a frame, and return the global object
// of the frame.
Frame* frame = window->frame();
if (!frame)
return v8::Handle<v8::Object>();
// Special case: Because of evaluateInNewContext() one DOMWindow can have
// multiple contexts and multiple global objects associated with it. When
// code running in one of those contexts accesses the window object, we
// want to return the global object associated with that context, not
// necessarily the first global object associated with that DOMWindow.
v8::Handle<v8::Context> current_context = v8::Context::GetCurrent();
v8::Handle<v8::Object> current_global = current_context->Global();
v8::Handle<v8::Object> windowWrapper =
LookupDOMWrapper(V8ClassIndex::DOMWINDOW, current_global);
if (!windowWrapper.IsEmpty())
if (DOMWrapperToNative<DOMWindow>(windowWrapper) == window)
return current_global;
// Otherwise, return the global object associated with this frame.
v8::Handle<v8::Context> context = GetContext(frame);
if (context.IsEmpty())
return v8::Handle<v8::Object>();
v8::Handle<v8::Object> global = context->Global();
ASSERT(!global.IsEmpty());
return global;
}
void V8Proxy::BindJSObjectToWindow(Frame* frame,
const char* name,
int type,
v8::Handle<v8::FunctionTemplate> desc,
void* imp)
{
// Get environment.
v8::Handle<v8::Context> context = V8Proxy::GetContext(frame);
if (context.IsEmpty())
return; // JS not enabled.
v8::Context::Scope scope(context);
v8::Handle<v8::Object> instance = desc->GetFunction();
SetDOMWrapper(instance, type, imp);
v8::Handle<v8::Object> global = context->Global();
global->Set(v8::String::New(name), instance);
}
void V8Proxy::ProcessConsoleMessages()
{
ConsoleMessageManager::ProcessDelayedMessages();
}
// Create the utility context for holding JavaScript functions used internally
// which are not visible to JavaScript executing on the page.
void V8Proxy::CreateUtilityContext() {
ASSERT(m_utilityContext.IsEmpty());
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> global_template = v8::ObjectTemplate::New();
m_utilityContext = v8::Context::New(NULL, global_template);
v8::Context::Scope context_scope(m_utilityContext);
// Compile JavaScript function for retrieving the source line of the top
// JavaScript stack frame.
static const char* frame_source_line_source =
"function frame_source_line(exec_state) {"
" return exec_state.frame(0).sourceLine();"
"}";
v8::Script::Compile(v8::String::New(frame_source_line_source))->Run();
// Compile JavaScript function for retrieving the source name of the top
// JavaScript stack frame.
static const char* frame_source_name_source =
"function frame_source_name(exec_state) {"
" var frame = exec_state.frame(0);"
" if (frame.func().resolved() && "
" frame.func().script() && "
" frame.func().script().name()) {"
" return frame.func().script().name();"
" }"
"}";
v8::Script::Compile(v8::String::New(frame_source_name_source))->Run();
}
int V8Proxy::GetSourceLineNumber() {
#if PLATFORM(ANDROID)
// TODO(andreip): consider V8's DEBUG flag here, rather than PLATFORM(ANDROID)
// or, even better, the WEBKIT inspector flag.
return 0;
#else
v8::HandleScope scope;
v8::Handle<v8::Context> utility_context = V8Proxy::GetUtilityContext();
if (utility_context.IsEmpty()) {
return 0;
}
v8::Context::Scope context_scope(utility_context);
v8::Handle<v8::Function> frame_source_line;
frame_source_line = v8::Local<v8::Function>::Cast(
utility_context->Global()->Get(v8::String::New("frame_source_line")));
if (frame_source_line.IsEmpty()) {
return 0;
}
v8::Handle<v8::Value> result = v8::Debug::Call(frame_source_line);
if (result.IsEmpty()) {
return 0;
}
return result->Int32Value();
#endif
}
String V8Proxy::GetSourceName() {
#if PLATFORM(ANDROID)
return String();
#else
v8::HandleScope scope;
v8::Handle<v8::Context> utility_context = GetUtilityContext();
if (utility_context.IsEmpty()) {
return String();
}
v8::Context::Scope context_scope(utility_context);
v8::Handle<v8::Function> frame_source_name;
frame_source_name = v8::Local<v8::Function>::Cast(
utility_context->Global()->Get(v8::String::New("frame_source_name")));
if (frame_source_name.IsEmpty()) {
return String();
}
return ToWebCoreString(v8::Debug::Call(frame_source_name));
#endif
}
void V8Proxy::RegisterExtension(v8::Extension* extension,
const String& schemeRestriction) {
v8::RegisterExtension(extension);
V8ExtensionInfo info = {schemeRestriction, extension};
m_extensions.append(info);
}
} // namespace WebCore