/* -*- 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 */