зеркало из https://github.com/mozilla/gecko-dev.git
596 строки
16 KiB
C
596 строки
16 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
*
|
|
* The contents of this file are subject to the Netscape Public License
|
|
* Version 1.0 (the "NPL"); you may not use this file except in
|
|
* compliance with the NPL. You may obtain a copy of the NPL at
|
|
* http://www.mozilla.org/NPL/
|
|
*
|
|
* Software distributed under the NPL is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
|
|
* for the specific language governing rights and limitations under the
|
|
* NPL.
|
|
*
|
|
* The Initial Developer of this code under the NPL is Netscape
|
|
* Communications Corporation. Portions created by Netscape are
|
|
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
|
|
* Reserved.
|
|
*/
|
|
|
|
/*
|
|
* DOM Node, NodeList, NamedNodeMap implementation.
|
|
*/
|
|
|
|
#include "dom_priv.h"
|
|
|
|
#ifdef DEBUG_shaver
|
|
int DOM_node_indent = 0;
|
|
#endif
|
|
|
|
DOM_Node *
|
|
DOM_PopNode(DOM_Node *node)
|
|
{
|
|
return node->parent;
|
|
}
|
|
|
|
JSBool
|
|
DOM_PushNode(DOM_Node *node, DOM_Node *parent)
|
|
{
|
|
DOM_Node *iter;
|
|
node->parent = parent;
|
|
node->sibling = NULL;
|
|
node->prev_sibling = NULL;
|
|
node->child = NULL;
|
|
|
|
/* First child */
|
|
if (!parent->child) {
|
|
parent->child = node;
|
|
return JS_TRUE;
|
|
}
|
|
/*
|
|
* XXX optimize by using parent->mocha_object to cache last child, and
|
|
* XXX NULLing parent->mocha_object on PopNode?
|
|
*/
|
|
for (iter = parent->child; iter->sibling; iter = iter->sibling)
|
|
; /* empty */
|
|
XP_ASSERT(iter);
|
|
iter->sibling = node;
|
|
node->prev_sibling = iter;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
JSObject *
|
|
DOM_ObjectForNode(JSContext *cx, DOM_Node *node)
|
|
{
|
|
if (!node)
|
|
return NULL;
|
|
if (node->mocha_object)
|
|
return node->mocha_object;
|
|
|
|
return DOM_NewNodeObject(cx, node);
|
|
}
|
|
|
|
JSBool
|
|
dom_node_getter(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
|
|
{
|
|
intN slot;
|
|
DOM_Node *node;
|
|
JSString *str;
|
|
|
|
if (!JSVAL_IS_INT(id))
|
|
return JS_TRUE;
|
|
|
|
node = (DOM_Node *)JS_GetPrivate(cx, obj);
|
|
if (!node)
|
|
return JS_TRUE;
|
|
|
|
slot = JSVAL_TO_INT(id);
|
|
|
|
switch(slot) {
|
|
case DOM_NODE_NODENAME:
|
|
if (!node->name) {
|
|
*vp = JSVAL_NULL; /* XXX '#nameless' or some such? */
|
|
return JS_TRUE;
|
|
}
|
|
#ifdef DEBUG_shaver_0
|
|
str = JS_InternString(cx, node->name);
|
|
#else
|
|
str = JS_NewStringCopyZ(cx, node->name);
|
|
#endif
|
|
if (!str)
|
|
return JS_FALSE;
|
|
*vp = STRING_TO_JSVAL(str);
|
|
return JS_TRUE;
|
|
case DOM_NODE_NODETYPE:
|
|
*vp = INT_TO_JSVAL(node->type);
|
|
return JS_TRUE;
|
|
case DOM_NODE_FIRSTCHILD:
|
|
*vp = OBJECT_TO_JSVAL(DOM_ObjectForNodeDowncast(cx, node->child));
|
|
return JS_TRUE;
|
|
case DOM_NODE_NEXTSIBLING:
|
|
*vp = OBJECT_TO_JSVAL(DOM_ObjectForNodeDowncast(cx, node->sibling));
|
|
return JS_TRUE;
|
|
case DOM_NODE_PREVIOUSSIBLING:
|
|
*vp = OBJECT_TO_JSVAL(DOM_ObjectForNodeDowncast(cx,
|
|
node->prev_sibling));
|
|
return JS_TRUE;
|
|
case DOM_NODE_PARENTNODE:
|
|
*vp = OBJECT_TO_JSVAL(DOM_ObjectForNodeDowncast(cx, node->parent));
|
|
return JS_TRUE;
|
|
case DOM_NODE_HASCHILDNODES:
|
|
*vp = BOOLEAN_TO_JSVAL((JSBool)(node->child != NULL));
|
|
return JS_TRUE;
|
|
case DOM_NODE_ATTRIBUTES:
|
|
/* only elements have non-null attributes */
|
|
*vp = JSVAL_NULL;
|
|
return JS_TRUE;
|
|
default:
|
|
XP_ASSERT(0);
|
|
break;
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
JSBool
|
|
dom_node_setter(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
|
|
{
|
|
return JS_TRUE;
|
|
}
|
|
|
|
void
|
|
dom_node_finalize(JSContext *cx, JSObject *obj)
|
|
{
|
|
DOM_Node *priv = (DOM_Node *)JS_GetPrivate(cx, obj);
|
|
if (!priv)
|
|
return;
|
|
priv->mocha_object = NULL;
|
|
DOM_DestroyTree(cx, priv);
|
|
}
|
|
|
|
static JSClass DOM_NodeClass = {
|
|
"Node", JSCLASS_HAS_PRIVATE,
|
|
JS_PropertyStub, JS_PropertyStub, dom_node_getter, dom_node_setter,
|
|
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, dom_node_finalize
|
|
};
|
|
|
|
JSObject *
|
|
DOM_NewNodeObject(JSContext *cx, DOM_Node *node)
|
|
{
|
|
JSObject *obj;
|
|
|
|
obj = JS_ConstructObject(cx, &DOM_NodeClass, NULL, NULL);
|
|
if (!obj)
|
|
return NULL;
|
|
|
|
if (!JS_SetPrivate(cx, obj, node)) {
|
|
return NULL;
|
|
}
|
|
|
|
node->mocha_object = obj;
|
|
|
|
return obj;
|
|
}
|
|
|
|
void
|
|
DOM_DestroyNode(JSContext *cx, DOM_Node *node)
|
|
{
|
|
XP_ASSERT(!node->mocha_object);
|
|
if (node->ops && node->ops->destroyNode)
|
|
node->ops->destroyNode(cx, node);
|
|
if (node->name && node->type != NODE_TYPE_TEXT)
|
|
JS_free(cx, node->name);
|
|
JS_free(cx, node);
|
|
}
|
|
|
|
#define REMOVE_FROM_TREE(node) \
|
|
PR_BEGIN_MACRO \
|
|
if (node->prev_sibling) \
|
|
node->prev_sibling->sibling = node->sibling; \
|
|
if (node->sibling) \
|
|
node->sibling->prev_sibling = node->prev_sibling; \
|
|
node->sibling = node->prev_sibling = node->parent = NULL; \
|
|
PR_END_MACRO
|
|
|
|
#define FAIL_UNLESS_CHILD(node, refNode) \
|
|
PR_BEGIN_MACRO \
|
|
if (refNode->parent != node) { \
|
|
/* XXX walk the tree looking for it? */ \
|
|
DOM_SignalException(cx, DOM_NOT_FOUND_ERR); \
|
|
return JS_FALSE; \
|
|
} \
|
|
PR_END_MACRO
|
|
|
|
static JSBool
|
|
IsLegalChild(DOM_Node *node, DOM_Node *child)
|
|
{
|
|
switch(node->type) {
|
|
case NODE_TYPE_ATTRIBUTE:
|
|
if (child->type == NODE_TYPE_TEXT ||
|
|
child->type == NODE_TYPE_ENTITY_REF)
|
|
return JS_TRUE;
|
|
return JS_FALSE;
|
|
if (child->type == NODE_TYPE_ELEMENT ||
|
|
child->type == NODE_TYPE_COMMENT ||
|
|
child->type == NODE_TYPE_TEXT ||
|
|
child->type == NODE_TYPE_PI ||
|
|
child->type == NODE_TYPE_CDATA ||
|
|
child->type == NODE_TYPE_ENTITY_REF)
|
|
return JS_TRUE;
|
|
return JS_FALSE;
|
|
case NODE_TYPE_ELEMENT:
|
|
case NODE_TYPE_DOCFRAGMENT:
|
|
case NODE_TYPE_ENTITY_REF:
|
|
if (child->type == NODE_TYPE_ELEMENT ||
|
|
child->type == NODE_TYPE_PI ||
|
|
child->type == NODE_TYPE_COMMENT ||
|
|
child->type == NODE_TYPE_TEXT ||
|
|
child->type == NODE_TYPE_CDATA ||
|
|
child->type == NODE_TYPE_ENTITY_REF)
|
|
return JS_TRUE;
|
|
return JS_FALSE;
|
|
case NODE_TYPE_DOCUMENT:
|
|
if(child->type == NODE_TYPE_PI ||
|
|
child->type == NODE_TYPE_COMMENT ||
|
|
child->type == NODE_TYPE_DOCTYPE)
|
|
return JS_TRUE;
|
|
if (child->type == NODE_TYPE_ELEMENT)
|
|
/* XXX check to make sure it's the only one */
|
|
return JS_TRUE;
|
|
return JS_FALSE;
|
|
case NODE_TYPE_DOCTYPE:
|
|
if (child->type == NODE_TYPE_NOTATION ||
|
|
child->type == NODE_TYPE_ENTITY)
|
|
return JS_TRUE;
|
|
return JS_FALSE;
|
|
default: /* PI, COMMENT, TEXT, CDATA, ENTITY, NOTATION */
|
|
return JS_FALSE;
|
|
}
|
|
}
|
|
|
|
#define CHECK_LEGAL_CHILD(node, child) \
|
|
PR_BEGIN_MACRO \
|
|
if (!IsLegalChild(node, child)) { \
|
|
DOM_SignalException(cx, DOM_HIERARCHY_REQUEST_ERR); \
|
|
return JS_FALSE; \
|
|
} \
|
|
PR_END_MACRO
|
|
|
|
static JSBool
|
|
node_insertBefore(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *vp)
|
|
{
|
|
JSObject *newChild, *refChild;
|
|
DOM_Node *newNode, *refNode, *node;
|
|
|
|
if (!JS_ConvertArguments(cx, argc, argv, "oo", &newChild, &refChild))
|
|
return JS_FALSE;
|
|
|
|
node = (DOM_Node *)JS_GetPrivate(cx, obj);
|
|
if (!node)
|
|
return JS_TRUE;
|
|
newNode = (DOM_Node *)JS_GetPrivate(cx, newChild);
|
|
refNode = (DOM_Node *)JS_GetPrivate(cx, refChild);
|
|
|
|
*vp = argv[0]; /* newChild */
|
|
if (!newNode || !refNode) {
|
|
return JS_TRUE;
|
|
}
|
|
|
|
CHECK_LEGAL_CHILD(node, newNode);
|
|
FAIL_UNLESS_CHILD(node, refNode);
|
|
if (!node->ops->insertBefore(cx, node, newNode, refNode, JS_TRUE))
|
|
return JS_FALSE;
|
|
REMOVE_FROM_TREE(newNode);
|
|
|
|
newNode->parent = node;
|
|
|
|
newNode->sibling = refNode;
|
|
newNode->prev_sibling = refNode->prev_sibling;
|
|
|
|
refNode->prev_sibling = newNode;
|
|
|
|
return node->ops->insertBefore(cx, node, newNode, refNode, JS_TRUE);
|
|
}
|
|
|
|
static JSBool
|
|
node_replaceChild(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *vp)
|
|
{
|
|
JSObject *oldChild, *newChild;
|
|
DOM_Node *oldNode, *newNode, *node;
|
|
|
|
if (!JS_ConvertArguments(cx, argc, argv, "oo", &newChild, &oldChild))
|
|
return JS_FALSE;
|
|
|
|
node = (DOM_Node *)JS_GetPrivate(cx, obj);
|
|
if (!node)
|
|
return JS_TRUE;
|
|
newNode = (DOM_Node *)JS_GetPrivate(cx, newChild);
|
|
oldNode = (DOM_Node *)JS_GetPrivate(cx, oldChild);
|
|
|
|
*vp = argv[1]; /* oldChild */
|
|
if (newNode == oldNode ||
|
|
!newNode || !oldNode)
|
|
return JS_TRUE;
|
|
|
|
CHECK_LEGAL_CHILD(node, newNode);
|
|
FAIL_UNLESS_CHILD(node, oldNode);
|
|
if (!node->ops->replaceChild(cx, node, newNode, oldNode, JS_TRUE))
|
|
return JS_FALSE;
|
|
REMOVE_FROM_TREE(newNode);
|
|
|
|
newNode->parent = node;
|
|
oldNode->sibling->prev_sibling = newNode;
|
|
oldNode->prev_sibling->sibling = newNode;
|
|
|
|
oldNode->parent = oldNode->sibling = oldNode->prev_sibling = NULL;
|
|
|
|
return node->ops->replaceChild(cx, node, newNode, oldNode, JS_FALSE);
|
|
}
|
|
|
|
static JSBool
|
|
node_removeChild(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *vp)
|
|
{
|
|
JSObject *deadChild;
|
|
DOM_Node *deadNode, *node;
|
|
|
|
if (!JS_ConvertArguments(cx, argc, argv, "o", &deadChild))
|
|
return JS_FALSE;
|
|
|
|
*vp = argv[0]; /* deadChild */
|
|
node = (DOM_Node *)JS_GetPrivate(cx, obj);
|
|
deadNode = (DOM_Node *)JS_GetPrivate(cx, deadChild);
|
|
if (!deadNode || !node)
|
|
return JS_TRUE;
|
|
|
|
FAIL_UNLESS_CHILD(node, deadNode);
|
|
if (!node->ops->removeChild(cx, node, deadNode, JS_TRUE))
|
|
return JS_FALSE;
|
|
|
|
REMOVE_FROM_TREE(deadNode);
|
|
|
|
return node->ops->removeChild(cx, node, deadNode, JS_FALSE);
|
|
}
|
|
|
|
static JSBool
|
|
node_appendChild(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *vp)
|
|
{
|
|
JSObject *newChild;
|
|
DOM_Node *newNode, *node, *iter;
|
|
|
|
if (!JS_ConvertArguments(cx, argc, argv, "o", &newChild))
|
|
return JS_FALSE;
|
|
|
|
*vp = argv[0]; /* newChild */
|
|
node = (DOM_Node *)JS_GetPrivate(cx, obj);
|
|
newNode = (DOM_Node *)JS_GetPrivate(cx, obj);
|
|
if (!node || !newNode)
|
|
return JS_TRUE;
|
|
|
|
CHECK_LEGAL_CHILD(node, newNode);
|
|
if (node->ops->appendChild(cx, node, newNode, JS_TRUE))
|
|
REMOVE_FROM_TREE(newNode);
|
|
|
|
newNode->parent = node;
|
|
|
|
iter = node->child;
|
|
if (iter) {
|
|
while (iter->sibling)
|
|
iter = iter->sibling;
|
|
iter->sibling = newNode;
|
|
newNode->prev_sibling = iter;
|
|
} else {
|
|
node->child = newNode;
|
|
}
|
|
|
|
return node->ops->appendChild(cx, node, newNode, JS_FALSE);
|
|
}
|
|
|
|
static JSBool
|
|
node_cloneNode(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *vp)
|
|
{
|
|
JSBool deep;
|
|
if (!JS_ConvertArguments(cx, argc, argv, "b", &deep))
|
|
return JS_FALSE;
|
|
|
|
DOM_SignalException(cx, DOM_NOT_SUPPORTED_ERR);
|
|
/* clone */
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
node_equals(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *vp)
|
|
{
|
|
JSObject *node;
|
|
DOM_Node *node1, *node2;
|
|
if (!JS_ConvertArguments(cx, argc, argv, "o", &node))
|
|
return JS_FALSE;
|
|
|
|
node1 = (DOM_Node *)JS_GetPrivate(cx, obj);
|
|
node2 = (DOM_Node *)JS_GetPrivate(cx, node);
|
|
*vp = (JSBool)(node1 == node2);
|
|
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSFunctionSpec node_methods[] = {
|
|
{"insertBefore", node_insertBefore, 2},
|
|
{"replaceChild", node_replaceChild, 2},
|
|
{"removeChild", node_removeChild, 1},
|
|
{"appendChild", node_appendChild, 1},
|
|
{"cloneNode", node_cloneNode, 1},
|
|
{"equals", node_equals, 2},
|
|
{0}
|
|
};
|
|
|
|
JSPropertySpec dom_node_props[] = {
|
|
{"nodeName", DOM_NODE_NODENAME},
|
|
{"nodeValue", DOM_NODE_NODEVALUE},
|
|
{"nodeType", DOM_NODE_NODETYPE},
|
|
{"parentNode", DOM_NODE_PARENTNODE},
|
|
{"childNodes", DOM_NODE_CHILDNODES},
|
|
{"firstChild", DOM_NODE_FIRSTCHILD},
|
|
{"lastChild", DOM_NODE_LASTCHILD},
|
|
{"previousSibling", DOM_NODE_PREVIOUSSIBLING},
|
|
{"nextSibling", DOM_NODE_NEXTSIBLING},
|
|
{"attributes", DOM_NODE_ATTRIBUTES},
|
|
{"hasChildNodes", DOM_NODE_HASCHILDNODES},
|
|
{0}
|
|
};
|
|
|
|
static JSConstDoubleSpec node_static_props[] = {
|
|
{NODE_TYPE_ELEMENT, "ELEMENT"},
|
|
{NODE_TYPE_ATTRIBUTE, "ATTRIBUTE"},
|
|
{NODE_TYPE_PI, "PROCESSING_INSTRUCTION"},
|
|
{NODE_TYPE_CDATA, "CDATA_SECTION"},
|
|
{NODE_TYPE_TEXT, "TEXT"},
|
|
{NODE_TYPE_ENTITY_REF, "ENTITY_REFERENCE"},
|
|
{NODE_TYPE_ENTITY, "ENTITY"},
|
|
{NODE_TYPE_COMMENT, "COMMENT"},
|
|
{NODE_TYPE_DOCUMENT, "DOCUMENT"},
|
|
{NODE_TYPE_DOCTYPE, "DOCUMENT_TYPE"},
|
|
{NODE_TYPE_DOCFRAGMENT, "DOCUMENT_FRAGMENT"},
|
|
{NODE_TYPE_NOTATION, "NOTATION"},
|
|
{0}
|
|
};
|
|
|
|
static JSBool
|
|
Node(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *vp)
|
|
{
|
|
return JS_TRUE;
|
|
}
|
|
|
|
JSObject *
|
|
dom_NodeInit(JSContext *cx, JSObject *scope)
|
|
{
|
|
JSObject *proto, *ctor;
|
|
proto = JS_InitClass(cx, scope, NULL, &DOM_NodeClass,
|
|
Node, 0,
|
|
dom_node_props, node_methods,
|
|
NULL, NULL);
|
|
if (!proto || !(ctor = JS_GetConstructor(cx, proto)))
|
|
return NULL;
|
|
if (!JS_DefineConstDoubles(cx, ctor, node_static_props))
|
|
return NULL;
|
|
return proto;
|
|
}
|
|
|
|
/*
|
|
* NodeList
|
|
*
|
|
* Basically an Array with magic methods.
|
|
*/
|
|
|
|
#if 0
|
|
|
|
static JSClass DOM_NodeListClass = {
|
|
"NodeList", 0,
|
|
JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
|
|
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub
|
|
};
|
|
|
|
static JSBool
|
|
nodelist_item(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
uint32 index;
|
|
if (!JS_ConvertArguments(cx, argc, argv, "j", &index))
|
|
return JS_TRUE;
|
|
/* JS_GetElement */
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSFunctionSpec nodelist_methods[] = {
|
|
{"item", nodelist_item, 1},
|
|
{0},
|
|
};
|
|
|
|
static JSBool
|
|
nodelist_size_get(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
|
|
{
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSPropertySpec nodelist_props[] = {
|
|
{"size", -1, 0, nodelist_size_get},
|
|
{0}
|
|
};
|
|
|
|
/*
|
|
* NamedNodeMap
|
|
*
|
|
* Basically an Object with magic resolve and helper methods.
|
|
*/
|
|
|
|
static JSBool
|
|
nnm_resolve(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
|
|
{
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSClass DOM_NamedNodeMapClass = {
|
|
"NamedNodeMap", 0,
|
|
JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
|
|
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub
|
|
};
|
|
|
|
static JSBool
|
|
nnm_getNamedItem(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
/* JS_GetProperty */
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
nnm_setNamedItem(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
return JS_TRUE;
|
|
/* JS_SetProperty */
|
|
}
|
|
|
|
static JSBool
|
|
nnm_removeNamedItem(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
/* JS_DeleteProperty */
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
nnm_item(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
/* return given slot -- OBJ_GET_SLOT? */
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSFunctionSpec nnm_methods[] = {
|
|
{"getNamedItem", nnm_getNamedItem, 1},
|
|
{"setNamedItem", nnm_setNamedItem, 1},
|
|
{"removeNamedItem", nnm_removeNamedItem,1},
|
|
{"item", nnm_item, 1},
|
|
{0}
|
|
};
|
|
|
|
static JSBool
|
|
nnm_get_size(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
|
|
{
|
|
/* return __count__ */
|
|
return JS_FALSE;
|
|
}
|
|
|
|
enum {
|
|
NNM_SIZE = -1
|
|
};
|
|
|
|
static JSPropertySpec nnm_props[] = {
|
|
{"size", NNM_SIZE, 0, nnm_get_size},
|
|
{0}
|
|
};
|
|
|
|
#endif /* 0 */
|