kjs_binding.cpp   [plain text]


/*
 *  This file is part of the KDE libraries
 *  Copyright (C) 1999-2001 Harri Porten (porten@kde.org)
 *  Copyright (C) 2004, 2005, 2006 Apple Computer, Inc.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

// gcc 3.x can't handle including the HashMap pointer specialization in this file
#ifndef __GLIBCXX__ // less than gcc 3.4
#define HASH_MAP_PTR_SPEC_WORKAROUND 1
#endif

#include "config.h"
#include "kjs_binding.h"

#include "Event.h"
#include "EventNames.h"
#include "Frame.h"
#include "PlatformString.h"
#include "Range.h"
#include "kjs_dom.h"
#include "kjs_window.h"
#include <kjs/collector.h>
#include <wtf/HashMap.h>
#include "WebCoreThread.h"

using namespace WebCore;
using namespace EventNames;

namespace KJS {

UString DOMObject::toString(ExecState *) const
{
  return "[object " + className() + "]";
}

typedef HashMap<void*, DOMObject*> DOMObjectMap;
typedef HashMap<Node*, DOMNode*> NodeMap;
typedef HashMap<Document*, NodeMap*> NodePerDocMap;

static DOMObjectMap *domObjects()
{ 
  static DOMObjectMap* staticDomObjects = new DOMObjectMap();
  return staticDomObjects;
}

static NodePerDocMap *domNodesPerDocument()
{
  static NodePerDocMap *staticDOMNodesPerDocument = new NodePerDocMap();
  return staticDOMNodesPerDocument;
}


ScriptInterpreter::ScriptInterpreter( JSObject *global, Frame *frame )
  : Interpreter( global ), m_frame(frame),
    m_evt( 0L ), m_inlineCode(false), m_timerCallback(false)
{
    // Time in milliseconds before the script timeout handler kicks in
    const unsigned ScriptTimeoutTimeMS = 5000;
        
    setTimeoutTime(ScriptTimeoutTimeMS);
}

DOMObject* ScriptInterpreter::getDOMObject(void* objectHandle) 
{
    return domObjects()->get(objectHandle);
}

void ScriptInterpreter::putDOMObject(void* objectHandle, DOMObject* obj) 
{
    domObjects()->set(objectHandle, obj);
}

void ScriptInterpreter::forgetDOMObject(void* objectHandle)
{
    domObjects()->remove(objectHandle);
}

DOMNode *ScriptInterpreter::getDOMNodeForDocument(Document *document, Node *node)
{
    if (!document)
        return static_cast<DOMNode *>(domObjects()->get(node));
    NodeMap *documentDict = domNodesPerDocument()->get(document);
    if (documentDict)
        return documentDict->get(node);
    return NULL;
}

void ScriptInterpreter::forgetDOMNodeForDocument(Document *document, Node *node)
{
    if (!document) {
        domObjects()->remove(node);
        return;
    }
    NodeMap *documentDict = domNodesPerDocument()->get(document);
    if (documentDict)
        documentDict->remove(node);
}

void ScriptInterpreter::putDOMNodeForDocument(Document *document, Node *nodeHandle, DOMNode *nodeWrapper)
{
    if (!document) {
        domObjects()->set(nodeHandle, nodeWrapper);
        return;
    }
    NodeMap *documentDict = domNodesPerDocument()->get(document);
    if (!documentDict) {
        documentDict = new NodeMap();
        domNodesPerDocument()->set(document, documentDict);
    }
    documentDict->set(nodeHandle, nodeWrapper);
}

void ScriptInterpreter::forgetAllDOMNodesForDocument(Document *document)
{
    assert(document);
    NodePerDocMap::iterator it = domNodesPerDocument()->find(document);
    if (it != domNodesPerDocument()->end()) {
        delete it->second;
        domNodesPerDocument()->remove(it);
    }
}

void ScriptInterpreter::markDOMNodesForDocument(Document* doc)
{
    NodePerDocMap::iterator dictIt = domNodesPerDocument()->find(doc);
    if (dictIt != domNodesPerDocument()->end()) {
      NodeMap *nodeDict = dictIt->second;
      NodeMap::iterator nodeEnd = nodeDict->end();
      for (NodeMap::iterator nodeIt = nodeDict->begin();
           nodeIt != nodeEnd;
           ++nodeIt) {

        DOMNode *node = nodeIt->second;
        // don't mark wrappers for nodes that are no longer in the
        // document - they should not be saved if the node is not
        // otherwise reachable from JS.
        if (node->impl()->inDocument() && !node->marked())
            node->mark();
      }
  }
}

void ScriptInterpreter::mark(bool currentThreadIsMainThread)
{
  if (!currentThreadIsMainThread) {
      // On alternate threads, DOMObjects remain in the cache because they're not collected.
      // So, they need an opportunity to mark their children.
      DOMObjectMap::iterator objectEnd = domObjects()->end();
      for (DOMObjectMap::iterator objectIt = domObjects()->begin();
           objectIt != objectEnd;
           ++objectIt) {
          DOMObject* object = objectIt->second;
          if (!object->marked())
              object->mark();
      }
  }
  
  Interpreter::mark(currentThreadIsMainThread);
}

ExecState *ScriptInterpreter::globalExec()
{
    // we need to make sure that any script execution happening in this
    // frame does not destroy it
    m_frame->keepAlive();
    return Interpreter::globalExec();
}

void ScriptInterpreter::updateDOMNodeDocument(Node *node, Document *oldDoc, Document *newDoc)
{
  DOMNode *cachedObject = getDOMNodeForDocument(oldDoc, node);
  if (cachedObject) {
    putDOMNodeForDocument(newDoc, node, cachedObject);
    forgetDOMNodeForDocument(oldDoc, node);
  }
}

bool ScriptInterpreter::wasRunByUserGesture() const
{
  if ( m_evt )
  {
    const AtomicString &type = m_evt->type();
    bool eventOk = ( // mouse events
      type == clickEvent || type == mousedownEvent ||
      type == mouseupEvent || type == dblclickEvent ||
      // keyboard events
      type == keydownEvent || type == keypressEvent ||
      type == keyupEvent ||
      // other accepted events
      type == selectEvent || type == changeEvent ||
      type == focusEvent || type == blurEvent ||
      type == submitEvent );
    if (eventOk)
      return true;
  } else { // no event
    if (m_inlineCode  && !m_timerCallback)
      // This is the <a href="javascript:window.open('...')> case -> we let it through
      return true;
    // This is the <script>window.open(...)</script> case or a timer callback -> block it
  }
  return false;
}

bool ScriptInterpreter::isGlobalObject(JSValue *v)
{
    return v->isObject(&Window::info);
}

bool ScriptInterpreter::isSafeScript(const Interpreter* target)
{
    return Window::isSafeScript(this, static_cast<const ScriptInterpreter*>(target));
}

Interpreter* ScriptInterpreter::interpreterForGlobalObject(const JSValue* imp)
{
    const Window* win = static_cast<const Window*>(imp);
    return win->interpreter();
}

void *ScriptInterpreter::createLanguageInstanceForValue (ExecState *exec, int language, JSObject *value, const Bindings::RootObject *origin, const Bindings::RootObject *current)
{
    void *result = 0;
    
#if __APPLE__
    // FIXME: Need to implement bindings support.
    if (language == Bindings::Instance::ObjectiveCLanguage)
        result = createObjcInstanceForValue (exec, value, origin, current);
    
    if (!result)
        result = Interpreter::createLanguageInstanceForValue (exec, language, value, origin, current);
#endif
    return result;
}

bool ScriptInterpreter::checkTimeout()
{
    if (!m_frame)
        return false;
    char *windowState = m_frame->windowState();
    if (!windowState)
        return false;
    if (WebThreadStateBitIsSet(windowState, WebThreadStateBitIsStopping))
        return true;
    return Interpreter::checkTimeout();
}

bool ScriptInterpreter::shouldInterruptScript() const
{
    return m_frame->shouldInterruptJavaScript();
}
    
//////

JSValue *jsStringOrNull(const String &s)
{
    if (s.isNull())
        return jsNull();
    return jsString(s);
}

JSValue *jsStringOrUndefined(const String &s)
{
    if (s.isNull())
        return jsUndefined();
    return jsString(s);
}

JSValue *jsStringOrFalse(const String &s)
{
    if (s.isNull())
        return jsBoolean(false);
    return jsString(s);
}

String valueToStringWithNullCheck(ExecState* exec, JSValue* val)
{
    if (val->isNull())
        return String();
    return val->toString(exec);
}

bool valueToBooleanTreatUndefinedAsTrue(ExecState* exec, JSValue* val)
{
    if (val->isUndefined())
        return true;
    return val->toBoolean(exec);
}

static const char * const exceptionNames[] = {
    0,
    "INDEX_SIZE_ERR",
    "DOMSTRING_SIZE_ERR",
    "HIERARCHY_REQUEST_ERR",
    "WRONG_DOCUMENT_ERR",
    "INVALID_CHARACTER_ERR",
    "NO_DATA_ALLOWED_ERR",
    "NO_MODIFICATION_ALLOWED_ERR",
    "NOT_FOUND_ERR",
    "NOT_SUPPORTED_ERR",
    "INUSE_ATTRIBUTE_ERR",
    "INVALID_STATE_ERR",
    "SYNTAX_ERR",
    "INVALID_MODIFICATION_ERR",
    "NAMESPACE_ERR",
    "INVALID_ACCESS_ERR",
    "VALIDATION_ERR",
    "TYPE_MISMATCH_ERR",
};

static const char * const rangeExceptionNames[] = {
    0, "BAD_BOUNDARYPOINTS_ERR", "INVALID_NODE_TYPE_ERR"
};

static const char * const eventExceptionNames[] = {
    "UNSPECIFIED_EVENT_TYPE_ERR"
};

#if XPATH_SUPPORT
static const char * const xpathExceptionNames[] = {
    "INVALID_EXPRESSION_ERR",
    "TYPE_ERR"
};
#endif

void setDOMException(ExecState* exec, ExceptionCode ec)
{
  if (ec == 0 || exec->hadException())
    return;

  const char* type = "DOM";
  int code = ec;

  const char * const * nameTable;
  
  int nameTableSize;
  int nameIndex;
  if (code >= RangeExceptionOffset && code <= RangeExceptionMax) {
    type = "DOM Range";
    code -= RangeExceptionOffset;
    nameIndex = code;
    nameTable = rangeExceptionNames;
    nameTableSize = sizeof(rangeExceptionNames) / sizeof(rangeExceptionNames[0]);
  } else if (code >= EventExceptionOffset && code <= EventExceptionMax) {
    type = "DOM Events";
    code -= EventExceptionOffset;
    nameIndex = code;
    nameTable = eventExceptionNames;
    nameTableSize = sizeof(eventExceptionNames) / sizeof(eventExceptionNames[0]);
#if XPATH_SUPPORT
  } else if (code >= XPathExceptionOffset && code <= XPathExceptionMax) {
    type = "DOM XPath";
    // XPath exception codes start with 51 and we don't want 51 empty elements in the name array
    nameIndex = code - INVALID_EXPRESSION_ERR;
    code -= XPathExceptionOffset;
    nameTable = xpathExceptionNames;
    nameTableSize = sizeof(xpathExceptionNames) / sizeof(xpathExceptionNames[0]);
#endif
  } else {
    nameIndex = code;
    nameTable = exceptionNames;
    nameTableSize = sizeof(exceptionNames) / sizeof(exceptionNames[0]);
  }

  const char* name = nameIndex < nameTableSize ? nameTable[nameIndex] : 0;

  // 100 characters is a big enough buffer, because there are:
  //   13 characters in the message
  //   10 characters in the longest type, "DOM Events"
  //   27 characters in the longest name, "NO_MODIFICATION_ALLOWED_ERR"
  //   20 or so digits in the longest integer's ASCII form (even if int is 64-bit)
  //   1 byte for a null character
  // That adds up to about 70 bytes.
  char buffer[100];

  if (name)
    sprintf(buffer, "%s: %s Exception %d", name, type, code);
  else
    sprintf(buffer, "%s Exception %d", type, code);

  JSObject* errorObject = throwError(exec, GeneralError, buffer);
  errorObject->put(exec, "code", jsNumber(code));
}

}