blob: 8a71bb3c88b3b1bfec893c5a67b9719c99f832b9 [file] [edit]
/*
* GPAC - Multimedia Framework C SDK
*
* Authors: Jean Le Feuvre
* Copyright (c) Telecom ParisTech 2007-2025
* All rights reserved
*
* This file is part of GPAC / Scene Graph sub-project
*
* GPAC 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, or (at your option)
* any later version.
*
* GPAC 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; see the file COPYING. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#include <gpac/setup.h>
#include <gpac/internal/scenegraph_dev.h>
#include "qjs_common.h"
/*base SVG type*/
#include <gpac/nodes_svg.h>
/*dom events*/
#include <gpac/events.h>
/*dom text event*/
#include <gpac/utf.h>
#include <gpac/download.h>
#include <gpac/network.h>
#include <gpac/xml.h>
#ifndef GPAC_DISABLE_SVG
#ifdef GPAC_HAS_QJS
#include <gpac/html5_mse.h>
#ifdef GPAC_CONFIG_ANDROID
#ifndef XP_UNIX
#define XP_UNIX
#endif /* XP_UNIX */
#endif
#include <gpac/html5_media.h>
/************************************************************
*
* DOM3 core implementation
*
* This is NOT a full dom implementation :)
*
*************************************************************/
GF_JSClass domDocumentClass;
GF_JSClass domNodeClass;
GF_JSClass domElementClass;
GF_JSClass domTextClass;
GF_JSClass domNodeListClass;
GF_JSClass domEventClass;
GF_JSClass htmlMediaElementClass;
typedef struct
{
u32 nb_inst;
GF_List *handlers;
} GF_DOMRuntime;
static GF_DOMRuntime *dom_rt = NULL;
typedef enum {
NODE_JSPROPERTY_NODENAME = -1,
NODE_JSPROPERTY_NODEVALUE = -2,
NODE_JSPROPERTY_NODETYPE = -3,
NODE_JSPROPERTY_PARENTNODE = -4,
NODE_JSPROPERTY_CHILDNODES = -5,
NODE_JSPROPERTY_FIRSTCHILD = -6,
NODE_JSPROPERTY_LASTCHILD = -7,
NODE_JSPROPERTY_PREVIOUSSIBLING = -8,
NODE_JSPROPERTY_NEXTSIBLING = -9,
NODE_JSPROPERTY_ATTRIBUTES = -10,
NODE_JSPROPERTY_OWNERDOCUMENT = -11,
NODE_JSPROPERTY_NAMESPACEURI = -12,
NODE_JSPROPERTY_PREFIX = -13,
NODE_JSPROPERTY_LOCALNAME = -14,
NODE_JSPROPERTY_BASEURI = -15,
NODE_JSPROPERTY_TEXTCONTENT = -16,
NODE_JSPROPERTY_FIRSTELEMENTCHILD = -17,
NODE_JSPROPERTY_LASTELEMENTCHILD = -18,
NODE_JSPROPERTY_PREVIOUSELEMENTSIBLING = -19,
NODE_JSPROPERTY_NEXTELEMENTSIBLING = -20
} GF_DOMNodeJSProperty;
typedef enum {
DOCUMENT_JSPROPERTY_DOCTYPE = -1,
DOCUMENT_JSPROPERTY_IMPLEMENTATION = -2,
DOCUMENT_JSPROPERTY_DOCUMENTELEMENT = -3,
DOCUMENT_JSPROPERTY_INPUTENCODING = -4,
DOCUMENT_JSPROPERTY_XMLENCODING = -5,
DOCUMENT_JSPROPERTY_XMLSTANDALONE = -6,
DOCUMENT_JSPROPERTY_XMLVERSION = -7,
DOCUMENT_JSPROPERTY_STRICTERRORCHECKING = -8,
DOCUMENT_JSPROPERTY_DOCUMENTURI = -9,
DOCUMENT_JSPROPERTY_LOCATION = -10,
DOCUMENT_JSPROPERTY_DOMCONFIG = -11,
DOCUMENT_JSPROPERTY_GLOBAL = -12
} GF_DOMDocumentJSProperty;
typedef enum {
ELEMENT_JSPROPERTY_TAGNAME = -1,
ELEMENT_JSPROPERTY_SCHEMATYPEINFO = -2,
} GF_DOMElementJSProperty;
typedef enum {
DCCI_JSPROPERTY_VALUE = -1,
DCCI_JSPROPERTY_VALUETYPE = -2,
DCCI_JSPROPERTY_PROPERTYTYPE = -3,
DCCI_JSPROPERTY_READONLY = -4,
DCCI_JSPROPERTY_DCCIMETADATAINTERFACETYPE = -5,
DCCI_JSPROPERTY_DCCIMETADATAINTERFACE = -6,
DCCI_JSPROPERTY_VERSION = -7,
} GF_DCCIJSProperty;
typedef enum {
NODELIST_JSPROPERTY_LENGTH = -1,
} GF_DOMNodeListJSProperty;
typedef enum {
EVENT_JSPROPERTY_TYPE = -1,
EVENT_JSPROPERTY_TARGET = -2,
EVENT_JSPROPERTY_CURRENTTARGET = -3,
EVENT_JSPROPERTY_EVENTPHASE = -4,
EVENT_JSPROPERTY_BUBBLES = -5,
EVENT_JSPROPERTY_CANCELABLE = -6,
EVENT_JSPROPERTY_TIMESTAMP = -7,
EVENT_JSPROPERTY_NAMESPACEURI = -8,
EVENT_JSPROPERTY_DEFAULTPREVENTED = -9,
EVENT_JSPROPERTY_DETAIL = -10,
EVENT_JSPROPERTY_DATA = -11,
EVENT_JSPROPERTY_SCREENX = -12,
EVENT_JSPROPERTY_SCREENY = -13,
EVENT_JSPROPERTY_CLIENTX = -14,
EVENT_JSPROPERTY_CLIENTY = -15,
EVENT_JSPROPERTY_BUTTON = -16,
EVENT_JSPROPERTY_RELATEDTARGET = -17,
EVENT_JSPROPERTY_WHEELDELTA = -18,
EVENT_JSPROPERTY_KEYIDENTIFIER = -19,
EVENT_JSPROPERTY_KEYCHAR = -20,
EVENT_JSPROPERTY_CHARCODE = -21,
EVENT_JSPROPERTY_LENGTHCOMPUTABLE = -22,
EVENT_JSPROPERTY_TYPEARG = -23,
EVENT_JSPROPERTY_LOADED = -24,
EVENT_JSPROPERTY_TOTAL = -25,
EVENT_JSPROPERTY_BUFFER_ON = -26,
EVENT_JSPROPERTY_BUFFERLEVEL = -27,
EVENT_JSPROPERTY_BUFFERREMAININGTIME = -28,
EVENT_JSPROPERTY_STATUS = -29,
EVENT_JSPROPERTY_WIDTH = -30,
EVENT_JSPROPERTY_HEIGHT = -31,
EVENT_JSPROPERTY_OFFSETX = -32,
EVENT_JSPROPERTY_OFFSETY = -33,
EVENT_JSPROPERTY_VPWIDTH = -34,
EVENT_JSPROPERTY_VPHEIGHT = -35,
EVENT_JSPROPERTY_TRANSLATIONX = -36,
EVENT_JSPROPERTY_TRANSLATIONY = -37,
EVENT_JSPROPERTY_TYPE3D = -38,
EVENT_JSPROPERTY_ERROR = -39,
EVENT_JSPROPERTY_DYNAMIC_SCENE = -40,
EVENT_JSPROPERTY_URL = -41,
} GF_DOMEventJSProperty;
typedef enum {
TEXT_JSPROPERTY_DATA = -1,
TEXT_JSPROPERTY_LENGTH = -2,
TEXT_JSPROPERTY_ISELEMENTCONTENTWHITESPACE = -3,
TEXT_JSPROPERTY_WHOLETEXT = -4,
} GF_DOMTextJSProperty;
JSValue xml_dom3_not_implemented(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_throw_err(ctx, GF_DOM_EXC_NOT_SUPPORTED_ERR);
}
#define JS_DOM3_uDOM_FIRST_PROP 18
void dom_node_changed(GF_Node *n, Bool child_modif, GF_FieldInfo *info)
{
if (info) {
if (child_modif) {
gf_node_dirty_set(n, GF_SG_CHILD_DIRTY, GF_FALSE);
}
#ifndef GPAC_DISABLE_SVG
else {
u32 flag = gf_svg_get_modification_flags((SVG_Element *)n, info);
gf_node_dirty_set(n, flag, GF_FALSE);
}
#endif
}
gf_node_changed(n, info);
}
static void define_dom_exception(JSContext *c, JSValue global)
{
#define DEF_EXC(_val) \
JS_SetPropertyStr(c, obj, #_val, JS_NewInt32(c, GF_DOM_EXC_##_val));
JSValue obj = JS_NewObject(c);
JS_SetPropertyStr(c, global, "DOMException", obj);
DEF_EXC(INDEX_SIZE_ERR);
DEF_EXC(DOMSTRING_SIZE_ERR);
DEF_EXC(HIERARCHY_REQUEST_ERR);
DEF_EXC(WRONG_DOCUMENT_ERR);
DEF_EXC(INVALID_CHARACTER_ERR);
DEF_EXC(NO_DATA_ALLOWED_ERR);
DEF_EXC(NO_MODIFICATION_ALLOWED_ERR);
DEF_EXC(NOT_FOUND_ERR);
DEF_EXC(NOT_SUPPORTED_ERR);
DEF_EXC(INUSE_ATTRIBUTE_ERR);
DEF_EXC(INVALID_STATE_ERR);
DEF_EXC(SYNTAX_ERR);
DEF_EXC(INVALID_MODIFICATION_ERR);
DEF_EXC(NAMESPACE_ERR);
DEF_EXC(INVALID_ACCESS_ERR);
DEF_EXC(VALIDATION_ERR);
DEF_EXC(TYPE_MISMATCH_ERR);
DEF_EXC(SECURITY_ERR);
DEF_EXC(NETWORK_ERR);
DEF_EXC(ABORT_ERR);
DEF_EXC(URL_MISMATCH_ERR);
DEF_EXC(QUOTA_EXCEEDED_ERR);
DEF_EXC(TIMEOUT_ERR);
DEF_EXC(INVALID_NODE_TYPE_ERR);
DEF_EXC(DATA_CLONE_ERR);
DEF_EXC(SECURITY_ERR);
#undef DEF_EXC
//JS_AliasProperty(c, global, "DOMException", "e");
obj = JS_NewObject(c);
JS_SetPropertyStr(c, global, "EventException", obj);
JS_SetPropertyStr(c, obj, "UNSPECIFIED_EVENT_TYPE_ERR", JS_NewInt32(c, 0));
JS_SetPropertyStr(c, obj, "DISPATCH_REQUEST_ERR", JS_NewInt32(c, 1));
}
GF_Node *dom_get_node(JSValue obj)
{
JSClassID _classID;
GF_Node *n = (GF_Node *) JS_GetAnyOpaque(obj, &_classID);
if (n && n->sgprivate) return n;
return NULL;
}
GF_Node *dom_get_element(JSContext *c, JSValue obj)
{
JSClassID _classID;
GF_Node *n = (GF_Node *) JS_GetAnyOpaque(obj, &_classID);
if (!n || !n->sgprivate) return NULL;
if (n->sgprivate->tag==TAG_DOMText) return NULL;
return n;
}
GF_SceneGraph *dom_get_doc(JSContext *c, JSValue obj)
{
JSClassID _classID;
GF_SceneGraph *sg = (GF_SceneGraph *) JS_GetAnyOpaque(obj, &_classID);
if (sg && !sg->__reserved_null) return sg;
return NULL;
}
typedef struct _dom_js_data
{
JSValue document;
} GF_DOMJSData;
static void dom_js_define_document_ex(JSContext *c, JSValue global, GF_SceneGraph *doc, const char *name)
{
JSClassID __class = 0;
JSValue obj;
GF_SceneGraph *par_sg;
if (!doc || !doc->RootNode) return;
if (doc->reference_count)
doc->reference_count++;
//no need to register root once more, it is already registered with graph
// gf_node_register(doc->RootNode, NULL);
par_sg = doc;
while (par_sg && !par_sg->get_document_class)
par_sg = par_sg->parent_scene;
if (par_sg && par_sg->get_document_class)
__class = par_sg->get_document_class(doc);
if (!__class)
__class = domDocumentClass.class_id;
obj = JS_NewObjectClass(c, __class);
JS_SetOpaque(obj, doc);
GF_SAFEALLOC(doc->js_data, GF_DOMJSData);
if (doc->js_data)
doc->js_data->document = JS_DupValue(c, obj);
JS_SetPropertyStr(c, global, name, obj);
}
void dom_js_define_document(JSContext *c, JSValue global, GF_SceneGraph *doc)
{
dom_js_define_document_ex(c, global, doc, "document");
}
/* Constructs a new document based on the given context and scene graph, used for documents in XHR */
JSValue dom_document_construct(JSContext *c, GF_SceneGraph *sg)
{
JSClassID __class=0;
JSValue new_obj;
GF_SceneGraph *par_sg = sg;
if (!dom_rt) return GF_JS_EXCEPTION(c);
if (sg->js_data) return JS_DupValue(c, sg->js_data->document);
if (sg->reference_count)
sg->reference_count++;
//no need to register root once more, it is already registered with graph
// gf_node_register(sg->RootNode, NULL);
while (par_sg && !par_sg->get_element_class) {
par_sg = par_sg->parent_scene;
}
if (par_sg && par_sg->get_document_class)
__class = par_sg->get_document_class(sg);
if (!__class)
__class = domDocumentClass.class_id;
new_obj = JS_NewObjectClass(c, __class);
JS_SetOpaque(new_obj, sg);
GF_SAFEALLOC(sg->js_data, GF_DOMJSData);
if (sg->js_data)
sg->js_data->document = JS_DupValue(c, new_obj);
return new_obj;
}
JSValue dom_document_construct_external(JSContext *c, GF_SceneGraph *sg)
{
return dom_document_construct(c, sg);
}
static JSValue dom_base_node_construct(JSContext *c, JSClassID class_id, GF_Node *n)
{
GF_SceneGraph *sg;
JSValue new_obj;
if (!n || !n->sgprivate->scenegraph) return JS_NULL;
if (n->sgprivate->tag<TAG_DOMText) return JS_NULL;
sg = n->sgprivate->scenegraph;
if (n->sgprivate->interact && n->sgprivate->interact->js_binding && !JS_IsUndefined(n->sgprivate->interact->js_binding->obj) ) {
return JS_DupValue(c, n->sgprivate->interact->js_binding->obj);
}
if (n->sgprivate->scenegraph->reference_count)
n->sgprivate->scenegraph->reference_count ++;
gf_node_register(n, NULL);
new_obj = JS_NewObjectClass(c, class_id);
JS_SetOpaque(new_obj, n);
if (n->sgprivate->tag == TAG_SVG_video || n->sgprivate->tag == TAG_SVG_audio)
{
#ifdef GPAC_ENABLE_HTML5_MEDIA
html_media_element_js_init(c, new_obj, n);
#endif
}
if (!n->sgprivate->interact) {
GF_SAFEALLOC(n->sgprivate->interact, struct _node_interactive_ext);
if (!n->sgprivate->interact) {
return JS_NULL;
}
}
if (!n->sgprivate->interact->js_binding) {
GF_SAFEALLOC(n->sgprivate->interact->js_binding, struct _node_js_binding);
if (!n->sgprivate->interact->js_binding) {
return JS_NULL;
}
}
n->sgprivate->interact->js_binding->obj = JS_DupValue(c, new_obj);
gf_list_add(sg->objects, n->sgprivate->interact->js_binding);
return new_obj;
}
static JSValue dom_node_construct(JSContext *c, GF_Node *n)
{
JSClassID __class = 0;
GF_SceneGraph *par_sg;
if (!n) return JS_NULL;
par_sg = n->sgprivate->scenegraph;
while (par_sg && !par_sg->get_element_class)
par_sg = par_sg->parent_scene;
if (par_sg && par_sg->get_element_class)
__class = par_sg->get_element_class(n);
if (!__class)
__class = domElementClass.class_id;
/*in our implementation ONLY ELEMENTS are created, never attributes. We therefore always
create Elements when asked to create a node !!*/
return dom_base_node_construct(c, __class, n);
}
JSValue dom_element_construct(JSContext *c, GF_Node *n)
{
JSClassID __class = 0;
GF_SceneGraph *par_sg;
if (!n) return JS_NULL;
par_sg = n->sgprivate->scenegraph;
while (par_sg && !par_sg->get_element_class)
par_sg = par_sg->parent_scene;
if (par_sg && par_sg->get_element_class)
__class = par_sg->get_element_class(n);
if (!__class)
__class = domElementClass.class_id;
return dom_base_node_construct(c, __class, n);
}
static JSValue dom_text_construct(JSContext *c, GF_Node *n)
{
return dom_base_node_construct(c, domTextClass.class_id, n);
}
static void dom_unregister_node(GF_Node *n)
{
GF_SceneGraph *sg = n->sgprivate->scenegraph;
if (!sg) return;
/*!! node is being deleted !! */
if (!n->sgprivate->num_instances) return;
gf_node_unregister(n, NULL);
if (sg->reference_count) {
sg->reference_count--;
if (!sg->reference_count)
gf_sg_del(sg);
}
}
JSValue dom_node_get_sibling(JSContext *c, GF_Node *n, Bool is_prev, Bool elt_only)
{
GF_Node *val;
GF_ChildNodeItem *child;
s32 idx, cur;
GF_ParentNode *par;
if (!n) return JS_NULL;
par = (GF_ParentNode *)gf_node_get_parent(n, 0);
if (!par) return JS_NULL;
idx = gf_node_list_find_child(par->children, n);
if (idx<0) return JS_NULL;
if (!elt_only) {
if (is_prev) {
idx--;
if (idx<0) return JS_NULL;
}
else idx++;
return dom_node_construct(c, gf_node_list_get_child(par->children, idx) );
}
cur = 0;
val = NULL;
child = par->children;
while (child) {
if ((idx!=cur) && (child->node->sgprivate->tag!=TAG_DOMText)) {
val = child->node;
}
if (is_prev) {
if (idx<=cur)
break;
} else {
if (idx>=cur) val = NULL;
if (val && idx<cur)
break;
}
child = child->next;
cur++;
}
return dom_node_construct(c, val);
}
/*dom3 NodeList/NamedNodeMap*/
typedef struct
{
/*set if the object is a childList from an existing node*/
GF_ParentNode *owner;
/*child list*/
GF_ChildNodeItem *child;
} DOMNodeList;
static JSValue dom_nodelist_construct(JSContext *c, GF_ParentNode *n)
{
DOMNodeList *nl;
JSValue new_obj;
if (!n) return JS_NULL;
GF_SAFEALLOC(nl, DOMNodeList);
if (!nl) return GF_JS_EXCEPTION(c);
nl->owner = n;
if (n->sgprivate->scenegraph->reference_count)
n->sgprivate->scenegraph->reference_count++;
gf_node_register((GF_Node*)n, NULL);
new_obj = JS_NewObjectClass(c, domNodeListClass.class_id);
JS_SetOpaque(new_obj, nl);
return new_obj;
}
static void dom_nodelist_finalize(JSRuntime *rt, JSValue obj)
{
DOMNodeList *nl = JS_GetOpaque(obj, domNodeListClass.class_id);
if (!nl) return;
if (nl->owner) {
dom_unregister_node((GF_Node*)nl->owner);
} else {
/*unregister all nodes for created lists*/
while (nl->child) {
GF_ChildNodeItem *child = nl->child;
nl->child = child->next;
dom_unregister_node(child->node);
gf_free(child);
}
}
gf_free(nl);
}
static JSValue dom_nodelist_item(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
{
GF_Node *n;
u32 count;
s32 idx;
DOMNodeList *nl;
nl = (DOMNodeList *) JS_GetOpaque(obj, domNodeListClass.class_id);
if (!nl || (argc!=1) || JS_ToInt32(c, &idx, argv[0]))
return GF_JS_EXCEPTION(c);
count = gf_node_list_get_count(nl->owner ? nl->owner->children : nl->child);
if ((idx<0) || ((u32) idx>=count)) {
return JS_NULL;
}
n = gf_node_list_get_child(nl->owner ? nl->owner->children : nl->child, idx);
return dom_node_construct(c, n);
}
static JSValue dom_nodelist_getProperty(JSContext *c, JSValueConst obj, int magic)
{
DOMNodeList *nl;
u32 count;
nl = (DOMNodeList *) JS_GetOpaque(obj, domNodeListClass.class_id);
if (!nl) return GF_JS_EXCEPTION(c);
count = gf_node_list_get_count(nl->owner ? nl->owner->children : nl->child);
switch (magic) {
case NODELIST_JSPROPERTY_LENGTH:
return JS_NewInt32(c, count);
default:
break;
}
return js_throw_err(c, GF_DOM_EXC_SYNTAX_ERR);
}
static void dom_handler_remove(GF_Node *node, void *rs, Bool is_destroy)
{
if (is_destroy) {
SVG_handlerElement *handler = (SVG_handlerElement *)node;
if (handler->js_data) {
if (handler->js_data->ctx && !JS_IsUndefined(handler->js_data->fun_val)) {
/*unprotect the function*/
JS_FreeValue(handler->js_data->ctx, handler->js_data->fun_val);
gf_list_del_item(dom_rt->handlers, handler);
}
gf_free(handler->js_data);
handler->js_data = NULL;
}
}
}
static void sg_js_get_event_target(JSContext *c, JSValue obj, GF_EventType evtType, GF_Node *vrml_node,
GF_SceneGraph **sg, GF_DOMEventTarget **target, GF_Node **n)
{
Bool is_svg_document_class(JSContext *c, JSValue obj);
Bool is_svg_element_class(JSContext *c, JSValue obj);
Bool gf_mse_is_mse_object(JSContext *c, JSValue obj);
*target = NULL;
*sg = NULL;
*n = NULL;
#ifdef GPAC_ENABLE_HTML5_MEDIA
if (gf_dom_event_get_category(evtType) == GF_DOM_EVENT_MEDIA) {
void gf_html_media_get_event_target(JSContext *c, JSValue obj, GF_DOMEventTarget **target, GF_SceneGraph **sg);
gf_html_media_get_event_target(c, obj, target, sg);
if (*target && *sg) return;
}
if (gf_dom_event_get_category(evtType) == GF_DOM_EVENT_MEDIASOURCE) {
void gf_mse_get_event_target(JSContext *c, JSValue obj, GF_DOMEventTarget **target, GF_SceneGraph **sg);
gf_mse_get_event_target(c, obj, target, sg);
if (*target && *sg) return;
}
#endif
if (JS_GetOpaque(obj, domDocumentClass.class_id) || is_svg_document_class(c, obj)) {
/*document interface*/
*sg = dom_get_doc(c, obj);
if (*sg) {
#ifndef GPAC_DISABLE_SVG
*target = (*sg)->dom_evt;
#else
return;
#endif
} else {
return;
}
} else if (JS_GetOpaque(obj, domElementClass.class_id) || is_svg_element_class(c, obj) || vrml_node) {
/*Element interface*/
if (vrml_node) {
*n = vrml_node;
} else {
*n = dom_get_element(c, obj);
}
if (*n) {
*sg = (*n)->sgprivate->scenegraph;
if (!(*n)->sgprivate->interact->dom_evt) {
(*n)->sgprivate->interact->dom_evt = gf_dom_event_target_new(GF_DOM_EVENT_TARGET_NODE, *n);
}
*target = (*n)->sgprivate->interact->dom_evt;
}
} else {
#if !defined(GPAC_DISABLE_SVG) && defined(GPAC_USE_DOWNLOADER)
void xhr_get_event_target(JSContext *c, JSValue obj, GF_SceneGraph **sg, GF_DOMEventTarget **target);
xhr_get_event_target(c, obj, sg, target);
#endif
}
}
static GF_Err sg_js_parse_event_args(JSContext *c, JSValue obj, int argc, JSValue *argv,
GF_EventType *evtType,
char **callback, JSValue *funval, JSValue *evt_handler) {
u32 offset = 0;
const char *type = NULL;
const char *inNS = NULL;
*evtType = GF_EVENT_UNKNOWN;
*callback = NULL;
*funval = JS_NULL;
if (evt_handler) *evt_handler = obj;
/*NS version (4 args in DOM2, 5 in DOM3 for evt_group param)*/
if (argc>=4) {
if (!JS_CHECK_STRING(argv[0])) return GF_BAD_PARAM;
inNS = JS_ToCString(c, argv[0]);
offset = 1;
}
if (!JS_CHECK_STRING(argv[offset])) goto err_exit;
type = JS_ToCString(c, argv[offset]);
if (JS_CHECK_STRING(argv[offset+1])) {
const char *str = JS_ToCString(c, argv[offset+1]);
if (!str) goto err_exit;
*callback = gf_strdup(str);
JS_FreeCString(c, str);
} else if (JS_IsFunction(c, argv[offset+1])) {
*funval = argv[offset+1];
} else if (JS_IsObject(argv[offset+1])) {
JSValue evt_fun;
if (evt_handler) {
*evt_handler = JS_DupValue(c, argv[offset+1]);
} else {
goto err_exit;
}
evt_fun = JS_GetPropertyStr(c, *evt_handler, "handleEvent");
if (!JS_IsFunction(c, evt_fun) ) goto err_exit;
*funval = evt_fun;
}
*evtType = gf_dom_event_type_by_name(type);
if (*evtType==GF_EVENT_UNKNOWN) goto err_exit;
JS_FreeCString(c, type);
JS_FreeCString(c, inNS);
return GF_OK;
err_exit:
if (type) JS_FreeCString(c, type);
if (inNS) JS_FreeCString(c, inNS);
if (callback) gf_free(*callback);
*callback = NULL;
*evtType = GF_EVENT_UNKNOWN;
*funval = JS_NULL;
if (evt_handler) *evt_handler = JS_UNDEFINED;
return GF_BAD_PARAM;
}
void svg_execute_handler(GF_Node *node, GF_DOM_Event *event, GF_Node *observer);
static GF_Node *create_listener(GF_SceneGraph *sg, GF_EventType evtType, GF_Node *n, GF_Node *vrml_node,
JSContext *c, char *callback, JSValue funval, JSValue evt_handler)
{
GF_FieldInfo info;
GF_Node *listener;
SVG_handlerElement *handler;
listener = gf_node_new(sg, TAG_SVG_listener);
/*we don't register the listener with the parent node , it will be registered
on gf_dom_listener_add*/
/*!!! create the handler in the scene owning the script context !!! */
handler = (SVG_handlerElement *) gf_node_new(sg, TAG_SVG_handler);
/*we register the handler with the listener node to avoid modifying the DOM*/
gf_node_register((GF_Node *)handler, listener);
gf_node_list_add_child(& ((GF_ParentNode *)listener)->children, (GF_Node*)handler);
if (!callback) {
GF_SAFEALLOC(handler->js_data, struct js_handler_context)
if (handler->js_data) {
handler->js_data->fun_val = funval;
handler->js_data->ctx = c;
if (JS_IsFunction(c, funval)) {
/*protect the function - we don't know how it was passed to us, so prevent it from being GCed*/
handler->js_data->fun_val = JS_DupValue(c, funval);
handler->sgprivate->UserCallback = dom_handler_remove;
gf_list_add(dom_rt->handlers, handler);
handler->handle_event = svg_execute_handler;
}
handler->js_data->evt_listen_obj = evt_handler;
}
}
/*create attributes if needed*/
gf_node_get_attribute_by_tag(listener, TAG_XMLEV_ATT_event, GF_TRUE, GF_FALSE, &info);
((XMLEV_Event*)info.far_ptr)->type = evtType;
gf_node_get_attribute_by_tag(listener, TAG_XMLEV_ATT_handler, GF_TRUE, GF_FALSE, &info);
((XMLRI*)info.far_ptr)->target = (GF_Node*)handler;
if (n) {
gf_node_get_attribute_by_tag(listener, TAG_XMLEV_ATT_target, GF_TRUE, GF_FALSE, &info);
((XMLRI*)info.far_ptr)->target = n;
}
gf_node_get_attribute_by_tag((GF_Node*)handler, TAG_XMLEV_ATT_event, GF_TRUE, GF_FALSE, &info);
((XMLEV_Event*)info.far_ptr)->type = evtType;
if (callback) gf_dom_add_text_node((GF_Node *)handler, gf_strdup(callback));
#ifndef GPAC_DISABLE_SVG
if (handler->sgprivate->scenegraph->svg_js && !handler->handle_event)
handler->handle_event = gf_sg_handle_dom_event;
#endif
if (vrml_node) {
gf_assert(handler->js_data);
handler->js_data->ctx = c;
#ifndef GPAC_DISABLE_VRML
if (vrml_node->sgprivate->tag <= GF_NODE_RANGE_LAST_VRML)
handler->handle_event = gf_sg_handle_dom_event_for_vrml;
#endif
}
return listener;
}
/*eventListeners routines used by document, element and XHR interfaces*/
JSValue gf_sg_js_event_add_listener(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv, GF_Node *vrml_node)
{
GF_DOMEventTarget *target = NULL;
GF_Node *listener = NULL;
GF_EventType evtType = GF_EVENT_UNKNOWN;
GF_SceneGraph *sg = NULL;
char *callback = NULL;
JSValue funval = JS_UNDEFINED;
GF_Node *n = NULL;
JSValue evt_handler = JS_UNDEFINED;
GF_Err e;
/* Determine the event type and handler params */
e = sg_js_parse_event_args(c, obj, argc, argv, &evtType, &callback, &funval, &evt_handler);
if (e != GF_OK) goto err_exit;
/* First retrieve the scenegraph and the GF_DOMEventTarget object */
sg_js_get_event_target(c, obj, evtType, vrml_node, &sg, &target, &n);
if (!sg || !target) goto err_exit;
listener = create_listener(sg, evtType, n, vrml_node, c, callback, funval, evt_handler);
if (!listener) goto err_exit;
/*don't add listener directly, post it and wait for event processing*/
if (n) {
gf_sg_listener_post_add((GF_Node *) n, listener);
} else {
gf_sg_listener_associate(listener, target);
}
err_exit:
if (callback) gf_free(callback);
if (e)
return js_throw_err(c, GF_DOM_EXC_SYNTAX_ERR);
return JS_UNDEFINED;
}
JSValue dom_event_add_listener(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
{
return gf_sg_js_event_add_listener(c, obj, argc, argv, NULL);
}
JSValue gf_sg_js_event_remove_listener(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv, GF_Node *vrml_node)
{
#ifndef GPAC_DISABLE_SVG
char *callback = NULL;
GF_EventType evtType = GF_EVENT_UNKNOWN;
u32 i, count;
GF_Node *node = NULL;
JSValue funval = JS_UNDEFINED;
GF_SceneGraph *sg = NULL;
GF_DOMEventTarget *target = NULL;
GF_Err e;
/* Determine the event type and handler params */
e = sg_js_parse_event_args(c, obj, argc, argv, &evtType, &callback, &funval, NULL);
if (e != GF_OK) goto err_exit;
/* First retrieve the scenegraph and the GF_DOMEventTarget object */
sg_js_get_event_target(c, obj, evtType, vrml_node, &sg, &target, &node);
if (!sg || !target) return JS_TRUE;
/*flush all pending add_listener*/
gf_dom_listener_process_add(sg);
count = gf_list_count(target->listeners);
for (i=0; i<count; i++) {
GF_FieldInfo info;
GF_DOMText *txt;
SVG_handlerElement *hdl;
GF_Node *el = (GF_Node *)gf_list_get(target->listeners, i);
gf_node_get_attribute_by_tag(el, TAG_XMLEV_ATT_event, GF_FALSE, GF_FALSE, &info);
if (!info.far_ptr) continue;
if (((XMLEV_Event*)info.far_ptr)->type != evtType) continue;
gf_node_get_attribute_by_tag(el, TAG_XMLEV_ATT_handler, GF_FALSE, GF_FALSE, &info);
if (!info.far_ptr) continue;
hdl = (SVG_handlerElement *) ((XMLRI*)info.far_ptr)->target;
if (!hdl) continue;
if (! JS_IsNull(funval) ) {
Bool is_same = GF_FALSE;
const char * f1 = JS_ToCString(c, funval);
const char * f2 = JS_ToCString(c, funval);
if (f1 && f2 && !strcmp(f1, f2)) is_same = GF_TRUE;
JS_FreeCString(c, f1);
JS_FreeCString(c, f2);
if (!is_same) continue;
} else if (hdl->children) {
txt = (GF_DOMText *) hdl->children->node;
if (txt->sgprivate->tag != TAG_DOMText) continue;
if (!txt->textContent || !callback || strcmp(txt->textContent, callback)) continue;
} else {
continue;
}
/*this will destroy the listener and its child handler*/
gf_dom_listener_del(el, target);
break;
}
#endif
err_exit:
if (callback) gf_free(callback);
return JS_UNDEFINED;
}
JSValue dom_event_remove_listener(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
{
return gf_sg_js_event_remove_listener(c, obj, argc, argv, NULL);
}
/*dom3 node*/
static void dom_node_finalize(JSRuntime *rt, JSValue obj)
{
JSClassID _classID;
GF_Node *n = (GF_Node *) JS_GetAnyOpaque(obj, &_classID);
/*the JS proto of the svgClass or a destroyed object*/
if (!n) return;
if (!n->sgprivate) return;
JS_SetOpaque(obj, NULL);
if (n->sgprivate->interact)
gf_list_del_item(n->sgprivate->scenegraph->objects, n->sgprivate->interact->js_binding);
gf_sg_js_dom_pre_destroy(rt, n->sgprivate->scenegraph, n);
dom_unregister_node(n);
}
static Bool check_dom_parents(JSContext *c, GF_Node *n, GF_Node *parent)
{
GF_ParentList *par = n->sgprivate->parents;
if (n->sgprivate->scenegraph != parent->sgprivate->scenegraph) {
js_throw_err(c, GF_DOM_EXC_WRONG_DOCUMENT_ERR);
return GF_FALSE;
}
while (par) {
if (par->node==parent) {
js_throw_err(c, GF_DOM_EXC_HIERARCHY_REQUEST_ERR);
return GF_FALSE;
}
if (!check_dom_parents(c, par->node, parent))
return GF_FALSE;
par = par->next;
}
return GF_TRUE;
}
static void dom_node_inserted(JSContext *c, GF_Node *n, GF_Node *parent, s32 pos)
{
GF_ParentNode *old_parent;
Bool do_init = GF_FALSE;
/*if node is already in graph, remove it from its parent*/
old_parent = (GF_ParentNode*)gf_node_get_parent(n, 0);
if (old_parent) {
/*prevent destruction when removing node*/
n->sgprivate->num_instances++;
gf_node_list_del_child(&old_parent->children, n);
gf_node_unregister(n, (GF_Node*)old_parent);
n->sgprivate->num_instances--;
} else {
do_init = (n->sgprivate->UserCallback || n->sgprivate->UserPrivate) ? GF_FALSE : GF_TRUE;
}
if (pos<0) gf_node_list_add_child( & ((GF_ParentNode *)parent)->children, n);
else gf_node_list_insert_child( & ((GF_ParentNode *)parent)->children, n, (u32) pos);
gf_node_register(n, parent);
if (do_init) {
/*node is a handler, create listener*/
if (parent && (n->sgprivate->tag==TAG_SVG_handler)) {
gf_dom_listener_build_ex(parent, 0, 0, n, NULL);
}
gf_node_init(n);
#ifndef GPAC_DISABLE_SVG
if (n->sgprivate->interact && n->sgprivate->interact->dom_evt) {
GF_DOM_Event evt;
memset(&evt, 0, sizeof(GF_DOM_Event));
evt.type = GF_EVENT_LOAD;
gf_dom_event_fire(n, &evt);
}
#endif
}
/*node is being re-inserted, activate it in case*/
if (!old_parent) gf_node_activate(n);
dom_node_changed(parent, GF_TRUE, NULL);
}
static JSValue xml_node_insert_before(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
{
s32 idx;
u32 tag;
GF_Node *n, *target, *new_node;
GF_ParentNode *par;
if (!argc || !JS_IsObject(argv[0]))
return GF_JS_EXCEPTION(c);
n = dom_get_node(obj);
if (!n) {
return js_throw_err(c, GF_DOM_EXC_HIERARCHY_REQUEST_ERR);
}
new_node = dom_get_node(argv[0]);
if (!new_node)
return js_throw_err(c, GF_DOM_EXC_SYNTAX_ERR);
target = NULL;
if ((argc==2) && JS_IsObject(argv[1]) && !JS_IsNull(argv[1])) {
target = dom_get_node(argv[1]);
if (!target) return JS_NULL;
}
tag = gf_node_get_tag(n);
if (tag==TAG_DOMText)
return js_throw_err(c, GF_DOM_EXC_SYNTAX_ERR);
if (!check_dom_parents(c, n, new_node))
return js_throw_err(c, GF_DOM_EXC_VALIDATION_ERR);
par = (GF_ParentNode*)n;
idx = -1;
if (target) {
idx = gf_node_list_find_child(par->children, target);
if (idx<0) {
return js_throw_err(c, GF_DOM_EXC_NOT_FOUND_ERR);
}
}
dom_node_inserted(c, new_node, n, idx);
return JS_DupValue(c, argv[0] );
}
static JSValue xml_node_append_child(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
{
u32 tag;
GF_Node *n, *new_node;
if (!argc || !JS_IsObject(argv[0])) {
return GF_JS_EXCEPTION(c);
}
n = dom_get_node(obj);
if (!n) {
return js_throw_err(c, GF_DOM_EXC_HIERARCHY_REQUEST_ERR);
}
new_node = dom_get_node(argv[0]);
if (!new_node) {
return js_throw_err(c, GF_DOM_EXC_SYNTAX_ERR);
}
tag = gf_node_get_tag(n);
if (tag==TAG_DOMText) {
return js_throw_err(c, GF_DOM_EXC_SYNTAX_ERR);
}
if (!check_dom_parents(c, n, new_node)) {
return js_throw_err(c, GF_DOM_EXC_VALIDATION_ERR);
}
dom_node_inserted(c, new_node, n, -1);
return JS_DupValue(c, argv[0]);
}
void svg_mark_gc(struct __tag_svg_script_ctx *svg_js);
static JSValue xml_node_replace_child(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
{
s32 idx;
u32 tag;
GF_Node *n, *new_node, *old_node;
GF_ParentNode *par;
if ((argc!=2) || !JS_IsObject(argv[0]) || !JS_IsObject(argv[1])) return GF_JS_EXCEPTION(c);
n = dom_get_node(obj);
if (!n) return js_throw_err(c, GF_DOM_EXC_HIERARCHY_REQUEST_ERR);
new_node = dom_get_node(argv[0]);
if (!new_node) return js_throw_err(c, GF_DOM_EXC_HIERARCHY_REQUEST_ERR);
old_node = dom_get_node(argv[1]);
if (!old_node) return js_throw_err(c, GF_DOM_EXC_HIERARCHY_REQUEST_ERR);
tag = gf_node_get_tag(n);
if (tag==TAG_DOMText) return js_throw_err(c, GF_DOM_EXC_HIERARCHY_REQUEST_ERR);
par = (GF_ParentNode*)n;
idx = gf_node_list_find_child(par->children, old_node);
if (idx<0) return js_throw_err(c, GF_DOM_EXC_HIERARCHY_REQUEST_ERR);
gf_node_list_del_child(&par->children, old_node);
gf_node_unregister(old_node, n);
dom_node_inserted(c, new_node, n, -1);
/*whenever we remove a node from the tree, call GC to cleanup the JS binding if any. Not doing so may screw up node IDs*/
svg_mark_gc(n->sgprivate->scenegraph->svg_js);
return JS_DupValue(c, argv[0]);
}
static JSValue xml_node_remove_child(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
{
u32 tag;
GF_Node *n, *old_node;
GF_ParentNode *par;
if (!argc || !JS_IsObject(argv[0])) return JS_TRUE;
n = dom_get_node(obj);
if (!n) return js_throw_err(c, GF_DOM_EXC_HIERARCHY_REQUEST_ERR);
old_node = dom_get_node(argv[0]);
if (!old_node) return js_throw_err(c, GF_DOM_EXC_HIERARCHY_REQUEST_ERR);
tag = gf_node_get_tag(n);
if (tag==TAG_DOMText) return js_throw_err(c, GF_DOM_EXC_HIERARCHY_REQUEST_ERR);
par = (GF_ParentNode*)n;
/*if node is present in parent, unregister*/
if (gf_node_list_del_child(&par->children, old_node)) {
gf_node_unregister(old_node, n);
} else {
return js_throw_err(c, GF_DOM_EXC_NOT_FOUND_ERR);
}
dom_node_changed(n, GF_TRUE, NULL);
/*whenever we remove a node from the tree, call GC to cleanup the JS binding if any. Not doing so may screw up node IDs*/
svg_mark_gc(n->sgprivate->scenegraph->svg_js);
return JS_DupValue(c, argv[0]);
}
static JSValue xml_clone_node(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
{
Bool deep;
GF_Node *n, *clone;
n = dom_get_node(obj);
if (!n) return js_throw_err(c, GF_DOM_EXC_HIERARCHY_REQUEST_ERR);
deep = argc ? (JS_ToBool(c, argv[0]) ? GF_TRUE : GF_FALSE) : GF_FALSE;
clone = gf_node_clone(n->sgprivate->scenegraph, n, NULL, "", deep);
return dom_node_construct(c, clone);
}
static JSValue xml_node_has_children(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
{
GF_Node *n;
n = dom_get_node(obj);
if (!n) return js_throw_err(c, GF_DOM_EXC_HIERARCHY_REQUEST_ERR);
return ((GF_ParentNode*)n)->children ? JS_TRUE : JS_FALSE;
}
static JSValue xml_node_has_attributes(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
{
u32 tag;
GF_Node *n;
n = dom_get_node(obj);
if (!n) return js_throw_err(c, GF_DOM_EXC_HIERARCHY_REQUEST_ERR);
tag = gf_node_get_tag(n);
if (tag>=GF_NODE_FIRST_DOM_NODE_TAG) {
return ((GF_DOMNode*)n)->attributes ? JS_TRUE : JS_FALSE;
}
/*not supported for other node types*/
return JS_FALSE;
}
static JSValue xml_node_is_same_node(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
{
GF_Node *n, *a_node;
if (!argc || !JS_IsObject(argv[0])) return JS_TRUE;
n = dom_get_node(obj);
if (!n) return js_throw_err(c, GF_DOM_EXC_HIERARCHY_REQUEST_ERR);
a_node = dom_get_node(argv[0]);
if (!a_node) return js_throw_err(c, GF_DOM_EXC_HIERARCHY_REQUEST_ERR);
return (a_node==n) ? JS_TRUE : JS_FALSE;
}
static const char *node_get_local_name(GF_Node *node)
{
const char *res;
GF_List *l;
if (!node) return NULL;
l = node->sgprivate->scenegraph->ns;
node->sgprivate->scenegraph->ns = NULL;
res = gf_node_get_class_name(node);
node->sgprivate->scenegraph->ns = l;
return res;
}
static const char *node_lookup_namespace_by_tag(GF_Node *node, u32 tag)
{
/*browse attributes*/
GF_DOMAttribute *att;
if (!node) return NULL;
if (node->sgprivate->tag < GF_NODE_FIRST_DOM_NODE_TAG) return NULL;
att = ((SVG_Element*)node)->attributes;
while (att) {
if (att->tag==TAG_DOM_ATT_any) {
GF_DOMFullAttribute *datt = (GF_DOMFullAttribute*)att;
if (datt->name && !strncmp(datt->name, "xmlns", 5)) {
char *xmlns = *(DOM_String *) datt->data;
u32 crc = gf_crc_32(xmlns, (u32) strlen(xmlns));
if (tag==crc)
return xmlns;
if (!tag && !strcmp(datt->name, "xmlns"))
return xmlns;
}
}
att = att->next;
}
/*browse for parent*/
return node_lookup_namespace_by_tag(gf_node_get_parent(node, 0), tag);
}
#ifdef GPAC_UNUSED_FUNC
/**
* FIXME : function is not used by anybody
*/
static u32 get_namespace_code_by_prefix(GF_Node *node, char *prefix)
{
/*browse attributes*/
GF_DOMAttribute *att;
if (!node) return 0;
att = ((SVG_Element*)node)->attributes;
while (att) {
if (att->tag==TAG_DOM_ATT_any) {
GF_DOMFullAttribute *datt = (GF_DOMFullAttribute*)att;
if (!prefix) {
if (!strcmp(datt->name, "xmlns")) return datt->xmlns;
} else if (!strncmp(datt->name, "xmlns:", 6)) {
if (!strcmp(datt->name+6, prefix)) return datt->xmlns;
}
}
att = att->next;
}
/*browse for parent*/
return get_namespace_code_by_prefix(gf_node_get_parent(node, 0), prefix);
}
#endif /*GPAC_UNUSED_FUNC*/
static JSValue dom_node_getProperty(JSContext *c, JSValueConst obj, int magic)
{
u32 tag;
GF_Node *n;
GF_SceneGraph *sg = NULL;
GF_ParentNode *par;
n = dom_get_node(obj);
if (!n) {
sg = dom_get_doc(c, obj);
if (!sg) return GF_JS_EXCEPTION(c);
}
tag = n ? gf_node_get_tag(n) : 0;
par = (GF_ParentNode*)n;
switch (magic) {
case NODE_JSPROPERTY_NODENAME:
if (sg) {
return JS_NewString(c, "#document");
}
else if (tag==TAG_DOMText) {
GF_DOMText *txt = (GF_DOMText *)n;
if (txt->type==GF_DOM_TEXT_CDATA) return JS_NewString(c, "#cdata-section");
else return JS_NewString(c, "#text");
}
return JS_NewString(c, gf_node_get_class_name(n) );
case NODE_JSPROPERTY_NODEVALUE:
if (tag==TAG_DOMText) {
GF_DOMText *txt = (GF_DOMText *)n;
return JS_NewString(c, txt->textContent);
}
return JS_NULL;
case NODE_JSPROPERTY_NODETYPE:
if (sg) return JS_NewInt32(c, 9);
else if (tag==TAG_DOMText) {
GF_DOMText *txt = (GF_DOMText *)n;
if (txt->type==GF_DOM_TEXT_CDATA) return JS_NewInt32(c, 4);
else return JS_NewInt32(c, 3);
}
return JS_NewInt32(c, 1);
case NODE_JSPROPERTY_PARENTNODE:
if (sg) {
return JS_NULL;
}
/*if root node of the tree, the parentNode is the document*/
else if (n->sgprivate->scenegraph->RootNode==n) {
return dom_document_construct(c, n->sgprivate->scenegraph);
} else {
return dom_node_construct(c, gf_node_get_parent(n, 0) );
}
case NODE_JSPROPERTY_CHILDNODES:
/*NOT SUPPORTED YET*/
if (sg) return JS_UNDEFINED;
else if (tag==TAG_DOMText) return JS_NULL;
else return dom_nodelist_construct(c, par);
case NODE_JSPROPERTY_FIRSTCHILD:
if (sg) return dom_node_construct(c, sg->RootNode);
else if (tag==TAG_DOMText) return JS_NULL;
else if (!par->children) return JS_NULL;
else return dom_node_construct(c, par->children->node);
case NODE_JSPROPERTY_LASTCHILD:
if (sg) return dom_node_construct(c, sg->RootNode);
else if ((tag==TAG_DOMText) || !par->children) return JS_NULL;
else return dom_node_construct(c, gf_node_list_get_child(par->children, -1) );
case NODE_JSPROPERTY_PREVIOUSSIBLING:
/*works for doc as well since n is NULL*/
return dom_node_get_sibling(c, n, GF_TRUE, GF_FALSE);
case NODE_JSPROPERTY_NEXTSIBLING:
return dom_node_get_sibling(c, n, GF_FALSE, GF_FALSE);
case NODE_JSPROPERTY_ATTRIBUTES:
/*NOT SUPPORTED YET*/
return JS_UNDEFINED;
case NODE_JSPROPERTY_OWNERDOCUMENT:
if (sg) return JS_NULL;
else return dom_document_construct(c, n->sgprivate->scenegraph);
case NODE_JSPROPERTY_NAMESPACEURI:
if (!sg) {
const char *xmlns;
tag = gf_xml_get_element_namespace(n);
xmlns = gf_sg_get_namespace(n->sgprivate->scenegraph, tag);
if (!xmlns) xmlns = node_lookup_namespace_by_tag(n, tag);
return xmlns ? JS_NewString(c, xmlns) : JS_NULL;
}
return JS_NULL;
case NODE_JSPROPERTY_PREFIX:
if (sg) tag = gf_sg_get_namespace_code(sg, NULL);
else tag = gf_xml_get_element_namespace(n);
if (tag) {
char *xmlns = (char *)gf_sg_get_namespace_qname(sg ? sg : n->sgprivate->scenegraph, tag);
if (xmlns) return JS_NewString(c, xmlns);
}
return JS_NULL;
case NODE_JSPROPERTY_LOCALNAME:
if (!sg && (tag!=TAG_DOMText)) {
return JS_NewString(c, node_get_local_name(n) );
}
return JS_NULL;
case NODE_JSPROPERTY_BASEURI:
/*NOT SUPPORTED YET*/
return JS_NULL;
case NODE_JSPROPERTY_TEXTCONTENT:
if (!sg) {
char *res = gf_dom_flatten_textContent(n);
JSValue ret = JS_NewString(c, res ? res : "");
if (res) gf_free(res);
return ret;
}
return JS_NewString(c, "");
case NODE_JSPROPERTY_FIRSTELEMENTCHILD:
if (n->sgprivate->tag!=TAG_DOMText) {
GF_ChildNodeItem *child = ((GF_ParentNode*)n)->children;
while (child) {
if (child->node->sgprivate->tag != TAG_DOMText) {
return dom_element_construct(c, child->node);
}
child = child->next;
}
}
return JS_NULL;
case NODE_JSPROPERTY_LASTELEMENTCHILD:
if (n->sgprivate->tag!=TAG_DOMText) {
GF_Node *last = NULL;
GF_ChildNodeItem *child = ((GF_ParentNode*)n)->children;
while (child) {
if (child->node->sgprivate->tag != TAG_DOMText) {
last = child->node;
}
child = child->next;
}
if (last) return dom_element_construct(c, last);
}
return JS_NULL;
case NODE_JSPROPERTY_PREVIOUSELEMENTSIBLING:
return dom_node_get_sibling(c, n, GF_TRUE, GF_TRUE);
case NODE_JSPROPERTY_NEXTELEMENTSIBLING:
return dom_node_get_sibling(c, n, GF_FALSE, GF_TRUE);
}
return JS_UNDEFINED;
}
void dom_node_set_textContent(GF_Node *n, char *text)
{
GF_FieldInfo info;
gf_dom_set_textContent(n, text);
gf_node_dirty_set(n, GF_SG_CHILD_DIRTY, GF_FALSE);
memset(&info, 0, sizeof(GF_FieldInfo));
info.fieldIndex = (u32) -1;
gf_node_changed(n, &info);
}
static JSValue dom_node_setProperty(JSContext *c, JSValueConst obj, JSValueConst value, int magic)
{
u32 tag;
GF_Node *n;
n = dom_get_node(obj);
/*note an element - we don't support property setting on document yet*/
if (!n) return GF_JS_EXCEPTION(c);
tag = n ? gf_node_get_tag(n) : 0;
switch (magic) {
case NODE_JSPROPERTY_NODEVALUE:
if ((tag==TAG_DOMText) && JS_CHECK_STRING(value)) {
const char *str;
GF_DOMText *txt = (GF_DOMText *)n;
if (txt->textContent) gf_free(txt->textContent);
str = JS_ToCString(c, value);
txt->textContent = str ? gf_strdup(str) : NULL;
JS_FreeCString(c, str);
dom_node_changed(n, GF_TRUE, NULL);
}
/*we only support element and sg in the Node interface, no set*/
return JS_TRUE;
case NODE_JSPROPERTY_PREFIX:
/*NOT SUPPORTED YET*/
return JS_TRUE;
case NODE_JSPROPERTY_TEXTCONTENT:
{
const char *txt = JS_ToCString(c, value);
dom_node_set_textContent(n, (char *) txt);
if (txt) JS_FreeCString(c, txt);
}
return JS_TRUE;
}
/*not supported*/
return JS_TRUE;
}
/*dom3 document*/
/*don't attempt to do anything with the scenegraph, it may be destroyed
fortunately a sg cannot be created like that...*/
void dom_document_finalize(JSRuntime *rt, JSValue obj)
{
GF_SceneGraph *sg;
JSClassID _classID;
sg = (GF_SceneGraph*) JS_GetAnyOpaque(obj, &_classID);
/*the JS proto of the svgClass or a destroyed object*/
if (!sg) return;
if (sg->js_data) {
JS_SetOpaque(sg->js_data->document, NULL);
JS_FreeValueRT(rt, sg->js_data->document);
gf_free(sg->js_data);
sg->js_data = NULL;
}
//no need to unregister root, we did not registered it in the constructor
// if (sg->RootNode) {
// gf_node_unregister(sg->RootNode, NULL);
// }
if (sg->reference_count) {
sg->reference_count--;
if (!sg->reference_count)
gf_sg_del(sg);
}
}
static JSValue dom_document_getProperty(JSContext *c, JSValueConst obj, int magic)
{
GF_SceneGraph *sg = dom_get_doc(c, obj);
if (!sg) return GF_JS_EXCEPTION(c);
switch (magic) {
case DOCUMENT_JSPROPERTY_IMPLEMENTATION:
{ /*FIXME, this is wrong, we should have our own implementation
but at the current time we rely on the global object to provide it*/
JSValue global = JS_GetGlobalObject(c);
JSValue ret = JS_GetPropertyStr(c, global, "Window");
JS_FreeValue(c, global);
return ret;
}
case DOCUMENT_JSPROPERTY_DOCUMENTELEMENT:
return dom_element_construct(c, sg->RootNode);
case DOCUMENT_JSPROPERTY_GLOBAL:
return JS_GetGlobalObject(c);
case DOCUMENT_JSPROPERTY_DOCTYPE:
case DOCUMENT_JSPROPERTY_INPUTENCODING:
case DOCUMENT_JSPROPERTY_XMLENCODING:
case DOCUMENT_JSPROPERTY_XMLSTANDALONE:
case DOCUMENT_JSPROPERTY_XMLVERSION:
case DOCUMENT_JSPROPERTY_STRICTERRORCHECKING:
case DOCUMENT_JSPROPERTY_DOCUMENTURI:
case DOCUMENT_JSPROPERTY_LOCATION:
case DOCUMENT_JSPROPERTY_DOMCONFIG:
return JS_NULL;
}
return JS_UNDEFINED;
}
static JSValue dom_document_setProperty(JSContext *c, JSValueConst obj, JSValueConst value, int magic)
{
GF_SceneGraph *sg = dom_get_doc(c, obj);
if (!sg) return GF_JS_EXCEPTION(c);
return js_throw_err(c, GF_DOM_EXC_NOT_SUPPORTED_ERR);
}
static JSValue xml_document_create_element(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
{
u32 tag, ns;
JSValue ret = JS_NULL;
const char *name;
const char *xmlns;
GF_SceneGraph *sg = dom_get_doc(c, obj);
if (!sg || !argc || !JS_CHECK_STRING(argv[0]) )
return GF_JS_EXCEPTION(c);
name = NULL;
/*NS version*/
ns = 0;
xmlns = NULL;
if (argc==2) {
if (!JS_CHECK_STRING(argv[1])) return GF_JS_EXCEPTION(c);
xmlns = JS_ToCString(c, argv[0]);
if (xmlns) ns = gf_sg_get_namespace_code_from_name(sg, (char *) xmlns);
name = JS_ToCString(c, argv[1]);
} else {
name = JS_ToCString(c, argv[0]);
}
if (name) {
GF_Node *n;
/*browse all our supported DOM implementations*/
tag = gf_xml_get_element_tag(name, ns);
if (!tag) tag = TAG_DOMFullNode;
n = gf_node_new(sg, tag);
if (n && (tag == TAG_DOMFullNode)) {
GF_DOMFullNode *elt = (GF_DOMFullNode *)n;
elt->name = gf_strdup(name);
if (xmlns) {
gf_sg_add_namespace(sg, (char *) xmlns, NULL);
elt->ns = gf_sg_get_namespace_code_from_name(sg, (char *) xmlns);
}
}
ret = dom_element_construct(c, n);
}
JS_FreeCString(c, name);
JS_FreeCString(c, xmlns);
return ret;
}
static JSValue xml_document_create_text(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
{
GF_Node *n;
GF_SceneGraph *sg = dom_get_doc(c, obj);
if (!sg) return GF_JS_EXCEPTION(c);
n = gf_node_new(sg, TAG_DOMText);
if (argc) {
const char *str = JS_ToCString(c, argv[0]);
char *ntext = gf_strdup(str ? str : "");
((GF_DOMText*)n)->textContent = ntext;
JS_FreeCString(c, str);
}
return dom_text_construct(c, n);
}
static void xml_doc_gather_nodes(GF_ParentNode *node, char *name, DOMNodeList *nl)
{
Bool bookmark = GF_TRUE;
GF_ChildNodeItem *child;
if (!node) return;
if (name) {
const char *node_name = gf_node_get_class_name((GF_Node*)node);
if (strcmp(node_name, name)) bookmark = GF_FALSE;
}
if (bookmark) {
gf_node_register((GF_Node*)node, NULL);
if (node->sgprivate->scenegraph->reference_count)
node->sgprivate->scenegraph->reference_count++;
gf_node_list_add_child(&nl->child, (GF_Node*)node);
}
if (node->sgprivate->tag<GF_NODE_FIRST_PARENT_NODE_TAG) return;
child = node->children;
while (child) {
xml_doc_gather_nodes((GF_ParentNode*)child->node, name, nl);
child = child->next;
}
}
static JSValue xml_document_elements_by_tag(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
{
DOMNodeList *nl;
JSValue new_obj;
const char *name;
GF_SceneGraph *sg = dom_get_doc(c, obj);
if (!sg) return GF_JS_EXCEPTION(c);
if (!argc || !JS_CHECK_STRING(argv[0])) return GF_JS_EXCEPTION(c);
/*NS version - TODO*/
if (argc==2) {
if (!JS_CHECK_STRING(argv[1])) return GF_JS_EXCEPTION(c);
name = JS_ToCString(c, argv[1]);
} else {
name = JS_ToCString(c, argv[0]);
}
GF_SAFEALLOC(nl, DOMNodeList);
if (!nl) return GF_JS_EXCEPTION(c);
if (name && !strcmp(name, "*"))
xml_doc_gather_nodes((GF_ParentNode*)sg->RootNode, NULL, nl);
else
xml_doc_gather_nodes((GF_ParentNode*)sg->RootNode, (char *) name, nl);
new_obj = JS_NewObjectClass(c, domNodeListClass.class_id);
JS_SetOpaque(new_obj, nl);
JS_FreeCString(c, name);
return new_obj;
}
static JSValue xml_document_element_by_id(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
{
NodeIDedItem *reg_node;
GF_Node *n;
const char *id;
JSValue ret;
GF_SceneGraph *sg = dom_get_doc(c, obj);
if (!sg) return GF_JS_EXCEPTION(c);
if (!argc || !JS_CHECK_STRING(argv[0])) return GF_JS_EXCEPTION(c);
id = JS_ToCString(c, argv[0]);
/*we don't use the regular gf_sg_find_node_by_name because we may have nodes defined with the
same ID and we need to locate the first one which is inserted in the tree*/
n = NULL;
reg_node = sg->id_node;
while (reg_node) {
if (reg_node->NodeName && !strcmp(reg_node->NodeName, id)) {
n = reg_node->node;
/*element is not inserted - fixme, we should check all parents*/
if (n && (n->sgprivate->scenegraph->RootNode!=n) && !n->sgprivate->parents) n = NULL;
else break;
}
reg_node = reg_node->next;
}
ret = dom_element_construct(c, n);
JS_FreeCString(c, id);
return ret;
}
/*dom3 element*/
void dom_element_finalize(JSRuntime *rt, JSValue obj)
{
dom_node_finalize(rt, obj);
}
static JSValue dom_element_getProperty(JSContext *c, JSValueConst obj, int magic)
{
GF_Node *n = dom_get_node(obj);
if (!n) return JS_TRUE;
switch (magic) {
case ELEMENT_JSPROPERTY_TAGNAME:
return JS_NewString(c, gf_node_get_class_name(n) );
case ELEMENT_JSPROPERTY_SCHEMATYPEINFO:
/*NOT SUPPORTED YET*/
return JS_NULL;
}
return JS_UNDEFINED;
}
static JSValue xml_element_get_attribute(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
{
const char *name;
const char *ns;
JSValue ret = JS_NULL;
GF_Node *n = dom_get_node(obj);
if (!n) return GF_JS_EXCEPTION(c);
if (!argc || !JS_CHECK_STRING(argv[0]))
return JS_TRUE;
ns = NULL;
/*NS version*/
if (argc==2) {
if (!JS_CHECK_STRING(argv[1]))
return JS_TRUE;
ns = JS_ToCString(c, argv[0]);
name = JS_ToCString(c, argv[1]);
} else {
name = JS_ToCString(c, argv[0]);
}
if (!name) goto exit;
/*ugly ugly hack ...*/
if (!strcmp(name, "id") || !strcmp(name, "xml:id") ) {
char *sID = (char *) gf_node_get_name(n);
if (sID) {
ret = JS_NewString(c, sID);
goto exit;
}
}
if (n->sgprivate->tag==TAG_DOMFullNode) {
GF_DOMFullNode *node = (GF_DOMFullNode*)n;
GF_DOMFullAttribute *att = (GF_DOMFullAttribute*)node->attributes;
u32 ns_code = ns ? gf_sg_get_namespace_code_from_name(n->sgprivate->scenegraph, (char *) ns) : 0;
while (att) {
if (att->tag==TAG_DOM_ATT_any) {
Bool found = GF_FALSE;
if (!strcmp(att->name, name) && (ns_code == att->xmlns))
found = GF_TRUE;
else if (ns) {
char *nssep = strchr(att->name, ':');
if (nssep && !strcmp(nssep+1, name)) {
u32 ns_len = (u32) (nssep - att->name);
//todo check namespace
if (!strcmp(ns, "*") || !strncmp(ns, att->name, ns_len))
found = GF_TRUE;
}
}
if (found) {
ret = JS_NewString(c, *(char**)att->data );
goto exit;
}
}
att = (GF_DOMFullAttribute *) att->next;
}
}
else if (n->sgprivate->tag==TAG_DOMText) {
} else if (n->sgprivate->tag<=GF_NODE_RANGE_LAST_SVG) {
GF_FieldInfo info;
u32 ns_code = 0;
if (ns) {
ns_code = gf_sg_get_namespace_code_from_name(n->sgprivate->scenegraph, (char *) ns);
if (!ns_code) ns_code = gf_crc_32(ns, (u32) strlen(ns));
}
else {
ns_code = gf_xml_get_element_namespace(n);
}
if (gf_node_get_attribute_by_name(n, (char *) name, ns_code, GF_FALSE, GF_FALSE, &info)==GF_OK) {
char *szAtt = gf_svg_dump_attribute(n, &info);
ret = JS_NewString(c, szAtt);
if (szAtt) gf_free(szAtt);
goto exit;
}
}
ret = JS_NewString(c, "");
exit:
JS_FreeCString(c, name);
JS_FreeCString(c, ns);
return ret;
}
static JSValue xml_element_has_attribute(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
{
const char *name;
const char *ns;
JSValue ret = JS_NULL;
GF_Node *n = dom_get_node(obj);
if (!n) return GF_JS_EXCEPTION(c);
if (!argc || !JS_CHECK_STRING(argv[0])) return GF_JS_EXCEPTION(c);
ns = NULL;
/*NS version*/
if (argc==2) {
if (!JS_CHECK_STRING(argv[1])) return GF_JS_EXCEPTION(c);
ns = JS_ToCString(c, argv[0]);
name = JS_ToCString(c, argv[1]);
} else {
name = JS_ToCString(c, argv[0]);
}
if (!name) goto exit;
if (n->sgprivate->tag==TAG_DOMFullNode) {
GF_DOMFullNode *node = (GF_DOMFullNode*)n;
GF_DOMFullAttribute *att = (GF_DOMFullAttribute*)node->attributes;
while (att) {
if ((att->tag==TAG_DOM_ATT_any) && !strcmp(att->name, name)) {
ret = JS_TRUE;
goto exit;
}
att = (GF_DOMFullAttribute *) att->next;
}
}
else if (n->sgprivate->tag==TAG_DOMText) {
}
else if (n->sgprivate->tag<=GF_NODE_RANGE_LAST_SVG) {
GF_FieldInfo info;
u32 ns_code = 0;
if (ns) ns_code = gf_sg_get_namespace_code_from_name(n->sgprivate->scenegraph, (char *) ns);
else ns_code = gf_sg_get_namespace_code(n->sgprivate->scenegraph, NULL);
if (gf_node_get_attribute_by_name(n, (char *) name, ns_code, GF_FALSE, GF_FALSE, &info)==GF_OK) {
ret = JS_TRUE;
goto exit;
}
}
ret = JS_FALSE;
exit:
JS_FreeCString(c, name);
JS_FreeCString(c, ns);
return ret;
}
static JSValue xml_element_remove_attribute(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
{
u32 tag;
GF_DOMFullNode *node;
GF_DOMFullAttribute *prev, *att;
const char *name, *ns;
GF_Node *n = dom_get_node(obj);
if (!n) return GF_JS_EXCEPTION(c);
if (!argc || !JS_CHECK_STRING(argv[0])) return GF_JS_EXCEPTION(c);
ns = NULL;
/*NS version*/
if (argc==2) {
if (!JS_CHECK_STRING(argv[1])) return GF_JS_EXCEPTION(c);
ns = JS_ToCString(c, argv[0]);
name = JS_ToCString(c, argv[1]);
} else {
name = JS_ToCString(c, argv[0]);
}
if (!name) goto exit;
tag = TAG_DOM_ATT_any;
node = (GF_DOMFullNode*)n;
prev = NULL;
att = (GF_DOMFullAttribute*)node->attributes;
if (n->sgprivate->tag==TAG_DOMFullNode) tag = TAG_DOM_ATT_any;
else if (n->sgprivate->tag==TAG_DOMText) {
goto exit;
} else if (n->sgprivate->tag<=GF_NODE_RANGE_LAST_SVG) {
u32 ns_code = 0;
if (ns) ns_code = gf_sg_get_namespace_code_from_name(n->sgprivate->scenegraph, (char *) ns);
else if (!strchr(name, ':'))
ns_code = gf_sg_get_namespace_code(n->sgprivate->scenegraph, NULL);
tag = gf_xml_get_attribute_tag(n, (char *)name, ns_code);
}
while (att) {
if (att->tag==TAG_DOM_ATT_any) {
if (!strcmp(att->name, name)) {
DOM_String *s;
if (prev) prev->next = att->next;
else node->attributes = att->next;
s = att->data;
if (*s) gf_free(*s);
gf_free(s);
gf_free(att->name);
gf_free(att);
dom_node_changed(n, GF_FALSE, NULL);
goto exit;
}
} else if (tag==att->tag) {
if (prev) prev->next = att->next;
else node->attributes = att->next;
gf_svg_delete_attribute_value(att->data_type, att->data, n->sgprivate->scenegraph);
gf_free(att);
dom_node_changed(n, GF_FALSE, NULL);
goto exit;
}
prev = att;
att = (GF_DOMFullAttribute *) att->next;
}
exit:
JS_FreeCString(c, name);
JS_FreeCString(c, ns);
return JS_TRUE;
}
static void gf_dom_add_handler_listener(GF_Node *n, u32 evtType, char *handlerCode)
{
/*check if we're modifying an existing listener*/
SVG_handlerElement *handler;
u32 i, count = gf_dom_listener_count(n);
for (i=0; i<count; i++) {
GF_FieldInfo info;
GF_DOMText *text;
GF_Node *listen = gf_dom_listener_get(n, i);
gf_node_get_attribute_by_tag(listen, TAG_XMLEV_ATT_event, GF_FALSE, GF_FALSE, &info);
if (!info.far_ptr || (((XMLEV_Event*)info.far_ptr)->type != evtType)) continue;
/* found a listener for this event, override the handler
TODO: FIX this, there may be a listener/handler already set with JS, why overriding ? */
gf_node_get_attribute_by_tag(listen, TAG_XMLEV_ATT_handler, GF_FALSE, GF_FALSE, &info);
if (!info.far_ptr) {
gf_assert(0);
return;
}
handler = (SVG_handlerElement *) ((XMLRI*)info.far_ptr)->target;
text = (GF_DOMText*)handler->children->node;
if (text->sgprivate->tag==TAG_DOMText) {
if (text->textContent) gf_free(text->textContent);
text->textContent = gf_strdup(handlerCode);
}
return;
}
/*nope, create a listener*/
handler = gf_dom_listener_build(n, evtType, 0);
gf_dom_add_text_node((GF_Node*)handler, gf_strdup(handlerCode));
return;
}
static void gf_dom_full_set_attribute(GF_DOMFullNode *node, char *attribute_name, char *attribute_content)
{
GF_DOMFullAttribute *prev = NULL;
GF_DOMFullAttribute *att = (GF_DOMFullAttribute*)node->attributes;
while (att) {
if ((att->tag==TAG_DOM_ATT_any) && !strcmp(att->name, attribute_name)) {
if ((att->data_type == DOM_String_datatype) && att->data) {
DOM_String *s;
s = (DOM_String *) att->data;
if ( *s ) gf_free( *s);
*s = gf_strdup(attribute_content);
dom_node_changed((GF_Node *)node, GF_FALSE, NULL);
} else {
gf_assert(0);
}
return;
}
prev = att;
att = (GF_DOMFullAttribute *) att->next;
}
/*create new att*/
GF_SAFEALLOC(att, GF_DOMFullAttribute);
if (!att) {
GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[DOMJS] Failed to allocate DOM attribute\n"));
return;
}
att->tag = TAG_DOM_ATT_any;
att->name = gf_strdup(attribute_name);
att->data_type = (u16) DOM_String_datatype;
att->data = gf_svg_create_attribute_value(att->data_type);
*((char **)att->data) = gf_strdup(attribute_content);
if (prev) prev->next = (GF_DOMAttribute*) att;
else node->attributes = (GF_DOMAttribute*) att;
return;
}
void gf_svg_set_attributeNS(GF_Node *n, u32 ns_code, char *name, char *val)
{
GF_FieldInfo info;
u32 anim_value_type = 0;
if (!strcmp(name, "attributeName")) {
if (gf_node_get_attribute_by_tag(n, TAG_SVG_ATT_attributeName, GF_FALSE, GF_FALSE, &info) == GF_OK) {
SMIL_AttributeName *attname = (SMIL_AttributeName *)info.far_ptr;
/*parse the attribute name even if the target is not found, because a namespace could be specified and
only valid for the current node*/
if (!attname->type) {
char *sep;
char *a_name = attname->name;
sep = strchr(a_name, ':');
if (sep) {
sep[0] = 0;
attname->type = gf_sg_get_namespace_code(n->sgprivate->scenegraph, a_name);
sep[0] = ':';
a_name = gf_strdup(sep+1);
gf_free(attname->name);
attname->name = a_name;
}
}
}
}
if ((n->sgprivate->tag == TAG_SVG_animateTransform) && (strstr(name, "from") || strstr(name, "to")) ) {
if (gf_node_get_attribute_by_tag((GF_Node *)n, TAG_SVG_ATT_transform_type, GF_TRUE, GF_FALSE, &info) != GF_OK) {
GF_LOG(GF_LOG_WARNING, GF_LOG_SCRIPT, ("Cannot retrieve attribute 'type' from animateTransform\n"));
return;
}
switch(*(SVG_TransformType *) info.far_ptr) {
case SVG_TRANSFORM_TRANSLATE:
anim_value_type = SVG_Transform_Translate_datatype;
break;
case SVG_TRANSFORM_SCALE:
anim_value_type = SVG_Transform_Scale_datatype;
break;
case SVG_TRANSFORM_ROTATE:
anim_value_type = SVG_Transform_Rotate_datatype;
break;
case SVG_TRANSFORM_SKEWX:
anim_value_type = SVG_Transform_SkewX_datatype;
break;
case SVG_TRANSFORM_SKEWY:
anim_value_type = SVG_Transform_SkewY_datatype;
break;
case SVG_TRANSFORM_MATRIX:
anim_value_type = SVG_Transform_datatype;
break;
default:
return;
}
}
if (gf_node_get_attribute_by_name(n, name, ns_code, GF_TRUE, GF_TRUE, &info)==GF_OK) {
GF_Err e;
if (!strcmp(name, "from") || !strcmp(name, "to") || !strcmp(name, "values") ) {
GF_FieldInfo attType;
SMIL_AttributeName *attname;
if (gf_node_get_attribute_by_tag((GF_Node *)n, TAG_SVG_ATT_attributeName, GF_FALSE, GF_FALSE, &attType) != GF_OK) {
GF_LOG(GF_LOG_WARNING, GF_LOG_SCRIPT, ("Cannot retrieve attribute 'attributeName'\n"));
return;
}
attname = (SMIL_AttributeName *)attType.far_ptr;
if (!attname->type && attname->name) {
GF_Node *anim_target = gf_smil_anim_get_target(n);
if (anim_target) {
gf_node_get_attribute_by_name((GF_Node *)anim_target, attname->name, attname->type, GF_FALSE, GF_FALSE, &attType);
attname->type = attType.fieldType;
} else {
GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[DOM] Cannot find target of the animation to parse attribute %s\n", attname->name));
return;
}
}
anim_value_type = attname->type;
}
e = gf_svg_parse_attribute(n, &info, val, anim_value_type);
if (e != GF_OK) {
GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[DOM] Error parsing attribute\n"));
}
if (info.fieldType==SVG_ID_datatype) {
char *idname = *(SVG_String*)info.far_ptr;
gf_svg_parse_element_id(n, idname, GF_FALSE);
}
if (info.fieldType==XMLRI_datatype) {
gf_node_dirty_set(n, GF_SG_SVG_XLINK_HREF_DIRTY, GF_FALSE);
}
dom_node_changed(n, GF_FALSE, &info);
return;
}
}
void gf_svg_set_attribute(GF_Node *n, char * ns, char *name, char *val)
{
u32 ns_code = 0;
if (ns) {
ns_code = gf_sg_get_namespace_code_from_name(n->sgprivate->scenegraph, ns);
} else {
ns_code = gf_xml_get_element_namespace(n);
}
gf_svg_set_attributeNS(n, ns_code, name, val);
}
static JSValue xml_element_set_attribute(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
{
u32 idx;
const char *name, *_val, *val, *ns;
char szVal[100];
GF_Node *n = dom_get_node(obj);
if (!n) return GF_JS_EXCEPTION(c);
if ((argc < 2)) return GF_JS_EXCEPTION(c);
if (!JS_CHECK_STRING(argv[0]))
return GF_JS_EXCEPTION(c);
idx = 1;
name = _val = val = NULL;
ns = NULL;
/*NS version*/
if (argc==3) {
char *sep;
if (!JS_CHECK_STRING(argv[1]))
return GF_JS_EXCEPTION(c);
ns = JS_ToCString(c, argv[0]);
gf_sg_add_namespace(n->sgprivate->scenegraph, (char *) ns, NULL);
name = JS_ToCString(c, argv[1]);
idx = 2;
sep = strchr(name, ':');
if (sep) name = sep+1;
} else {
name = JS_ToCString(c, argv[0]);
}
val = NULL;
if (JS_CHECK_STRING(argv[idx])) {
val = _val = JS_ToCString(c, argv[idx]);
} else if (JS_IsBool(argv[idx])) {
sprintf(szVal, "%s", JS_ToBool(c, argv[idx]) ? "true" : "false");
val = szVal;
} else if (JS_IsNumber(argv[idx])) {
Double d;
JS_ToFloat64(c, &d, argv[idx]);
sprintf(szVal, "%g", d);
val = szVal;
} else if (JS_IsInteger(argv[idx])) {
u32 i;
JS_ToInt32(c, &i, argv[idx]);
sprintf(szVal, "%d", i);
val = szVal;
} else {
goto exit;
}
if (!name || !val)
goto exit;
/* For on* attribute (e.g. onclick), we create a couple listener/handler elements on setting the attribute */
if ((name[0]=='o') && (name[1]=='n')) {
u32 evtType = gf_dom_event_type_by_name(name + 2);
if (evtType != GF_EVENT_UNKNOWN) {
gf_dom_add_handler_listener(n, evtType, (char *) val);
goto exit;
}
}
if (n->sgprivate->tag==TAG_DOMFullNode) {
gf_dom_full_set_attribute((GF_DOMFullNode*)n, (char *) name, (char *) val);
goto exit;
}
if (n->sgprivate->tag==TAG_DOMText) {
goto exit;
}
if (n->sgprivate->tag<=GF_NODE_RANGE_LAST_SVG) {
gf_svg_set_attribute(n, (char *) ns, (char *) name, (char *) val);
}
exit:
JS_FreeCString(c, name);
JS_FreeCString(c, ns);
JS_FreeCString(c, _val);
return JS_TRUE;
}
static JSValue xml_element_elements_by_tag(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
{
DOMNodeList *nl;
JSValue new_obj;
const char *name;
GF_Node *n = dom_get_node(obj);
if (!n) return GF_JS_EXCEPTION(c);
if (!argc || !JS_CHECK_STRING(argv[0])) return GF_JS_EXCEPTION(c);
/*NS version*/
if (argc==2) {
if (!JS_CHECK_STRING(argv[1])) return GF_JS_EXCEPTION(c);
name = JS_ToCString(c, argv[1]);
} else {
name = JS_ToCString(c, argv[0]);
}
GF_SAFEALLOC(nl, DOMNodeList);
if (!nl) return GF_JS_EXCEPTION(c);
if (name && !strcmp(name, "*")) {
JS_FreeCString(c, name);
name = NULL;
}
xml_doc_gather_nodes((GF_ParentNode*)n, (char *)name, nl);
new_obj = JS_NewObjectClass(c, domNodeListClass.class_id);
JS_SetOpaque(new_obj, nl);
JS_FreeCString(c, name);
return new_obj;
}
static JSValue xml_element_set_id(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
{
u32 node_id;
const char *name;
Bool is_id;
GF_Node *n = dom_get_node(obj);
if (!n) return GF_JS_EXCEPTION(c);
if ((argc<2) || !JS_CHECK_STRING(argv[0])) return GF_JS_EXCEPTION(c);
/*NS version*/
if (argc==3) {
if (!JS_CHECK_STRING(argv[1])) return GF_JS_EXCEPTION(c);
name = JS_ToCString(c, argv[1]);
is_id = JS_ToBool(c, argv[2]) ? GF_TRUE : GF_FALSE;
} else {
name = JS_ToCString(c, argv[0]);
is_id = JS_ToBool(c, argv[1]) ? GF_TRUE : GF_FALSE;
}
gf_node_get_name_and_id(n, &node_id);
if (node_id && is_id) {
/*we only support ONE ID per node*/
JS_FreeCString(c, name);
return GF_JS_EXCEPTION(c);
}
if (is_id) {
if (!name) return GF_JS_EXCEPTION(c);
gf_node_set_id(n, gf_sg_get_max_node_id(n->sgprivate->scenegraph) + 1, name);
} else if (node_id) {
gf_node_remove_id(n);
}
JS_FreeCString(c, name);
return JS_TRUE;
}
static void gather_text(GF_ParentNode *n, char **out_str)
{
if (n->sgprivate->tag==TAG_DOMText) {
GF_DOMText *dom_text = (GF_DOMText *)n;
if (dom_text->textContent) gf_dynstrcat(out_str, dom_text->textContent, NULL);
} else {
GF_ChildNodeItem *child = ((GF_ParentNode *) n)->children;
while (child) {
gather_text((GF_ParentNode *)child->node, out_str);
child = child->next;
}
}
}
static JSValue xml_element_to_string(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
{
JSValue ret;
char *out_str = NULL;
GF_Node *n = dom_get_node(obj);
if (!n) return GF_JS_EXCEPTION(c);
GF_ChildNodeItem *child;
child = ((GF_ParentNode *) n)->children;
while (child) {
gather_text((GF_ParentNode *)child->node, &out_str);
child = child->next;
}
if (!out_str) {
const char *node_name = gf_node_get_class_name(n);
if (node_name)
return JS_NewString(c, node_name);
return JS_NULL;
}
ret = JS_NewString(c, out_str);
gf_free(out_str);
return ret;
}
/*dom3 character/text/comment*/
static JSValue dom_text_getProperty(JSContext *c, JSValueConst obj, int magic)
{
GF_DOMText *txt = (GF_DOMText*)dom_get_node(obj);
if (!txt || (txt->sgprivate->tag != TAG_DOMText)) return GF_JS_EXCEPTION(c);
switch (magic) {
case TEXT_JSPROPERTY_DATA:
if (txt->textContent) return JS_NewString(c, txt->textContent);
else return JS_NewString(c, "");
case TEXT_JSPROPERTY_LENGTH:
return JS_NewInt32(c, txt->textContent ? (u32) strlen(txt->textContent) : 0);
case TEXT_JSPROPERTY_ISELEMENTCONTENTWHITESPACE:
return JS_FALSE;
case TEXT_JSPROPERTY_WHOLETEXT:
/*FIXME - this is wrong we should serialize adjacent text strings as well*/
if (txt->textContent) return JS_NewString(c, txt->textContent);
else return JS_NewString(c, "");
}
return JS_UNDEFINED;
}
static JSValue dom_text_setProperty(JSContext *c, JSValueConst obj, JSValueConst value, int magic)
{
GF_DOMText *txt = (GF_DOMText*)dom_get_node(obj);
if (!txt || (txt->sgprivate->tag != TAG_DOMText)) return GF_JS_EXCEPTION(c);
switch (magic) {
case TEXT_JSPROPERTY_DATA:
if (txt->textContent) gf_free(txt->textContent);
txt->textContent = NULL;
if (JS_CHECK_STRING(value)) {
const char *str = JS_ToCString(c, value);
txt->textContent = gf_strdup(str ? str : "");
JS_FreeCString(c, str);
}
dom_node_changed((GF_Node*)txt, GF_FALSE, NULL);
return JS_TRUE;
/*the rest is read-only*/
}
return JS_UNDEFINED;
}
static JSValue event_stop_propagation(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
{
GF_DOM_Event *evt = JS_GetOpaque(obj, domEventClass.class_id);
if (!evt) return GF_JS_EXCEPTION(c);
evt->event_phase |= GF_DOM_EVENT_PHASE_CANCEL;
return JS_TRUE;
}
static JSValue event_stop_immediate_propagation(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
{
GF_DOM_Event *evt = JS_GetOpaque(obj, domEventClass.class_id);
if (!evt) return GF_JS_EXCEPTION(c);
evt->event_phase |= GF_DOM_EVENT_PHASE_CANCEL_ALL;
return JS_TRUE;
}
static JSValue event_prevent_default(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
{
GF_DOM_Event *evt = JS_GetOpaque(obj, domEventClass.class_id);
if (!evt) return GF_JS_EXCEPTION(c);
evt->event_phase |= GF_DOM_EVENT_PHASE_PREVENT;
return JS_TRUE;
}
static JSValue event_getProperty(JSContext *c, JSValueConst obj, int magic)
{
GF_DOM_Event *evt = JS_GetOpaque(obj, domEventClass.class_id);
if (evt==NULL) return JS_TRUE;
switch (magic) {
case EVENT_JSPROPERTY_TYPE:
return JS_NewString(c, gf_dom_event_get_name(evt->type) );
case EVENT_JSPROPERTY_TARGET:
if (evt->is_vrml) return JS_TRUE;
switch (evt->target_type) {
case GF_DOM_EVENT_TARGET_NODE:
return dom_element_construct(c, (GF_Node*) evt->target);
case GF_DOM_EVENT_TARGET_DOCUMENT:
return dom_document_construct(c, (GF_SceneGraph *) evt->target);
#ifdef GPAC_ENABLE_HTML5_MEDIA
case GF_DOM_EVENT_TARGET_MSE_MEDIASOURCE:
return OBJECT_TO_JSValue(((GF_HTML_MediaSource *)evt->target)->_this);
case GF_DOM_EVENT_TARGET_MSE_SOURCEBUFFER:
return OBJECT_TO_JSValue(((GF_HTML_SourceBuffer *)evt->target)->_this);
case GF_DOM_EVENT_TARGET_MSE_SOURCEBUFFERLIST:
return OBJECT_TO_JSValue(((GF_HTML_SourceBufferList *)evt->target)->_this);
#endif
default:
break;
}
return JS_TRUE;
case EVENT_JSPROPERTY_CURRENTTARGET:
if (evt->is_vrml) return JS_NULL;
switch (evt->currentTarget->ptr_type) {
case GF_DOM_EVENT_TARGET_NODE:
return dom_element_construct(c, (GF_Node*) evt->currentTarget->ptr);
case GF_DOM_EVENT_TARGET_DOCUMENT:
return dom_document_construct(c, (GF_SceneGraph *) evt->currentTarget->ptr);
#ifdef GPAC_ENABLE_HTML5_MEDIA
case GF_DOM_EVENT_TARGET_MSE_MEDIASOURCE:
return OBJECT_TO_JSValue(((GF_HTML_MediaSource *)evt->target)->_this);
case GF_DOM_EVENT_TARGET_MSE_SOURCEBUFFER:
return OBJECT_TO_JSValue(((GF_HTML_SourceBuffer *)evt->target)->_this);
break;
case GF_DOM_EVENT_TARGET_MSE_SOURCEBUFFERLIST:
return OBJECT_TO_JSValue(((GF_HTML_SourceBufferList *)evt->target)->_this);
#endif
default:
break;
}
return JS_TRUE;
case EVENT_JSPROPERTY_EVENTPHASE:
return JS_NewInt32(c, (evt->event_phase & 0x3) );
case EVENT_JSPROPERTY_BUBBLES:
return evt->bubbles ? JS_TRUE : JS_FALSE;
case EVENT_JSPROPERTY_CANCELABLE:
return evt->cancelable ? JS_TRUE : JS_FALSE;
case EVENT_JSPROPERTY_NAMESPACEURI:
return JS_NULL;
case EVENT_JSPROPERTY_TIMESTAMP:
return JS_NULL;
case EVENT_JSPROPERTY_DEFAULTPREVENTED:
return (evt->event_phase & GF_DOM_EVENT_PHASE_PREVENT) ? JS_TRUE : JS_FALSE;
case EVENT_JSPROPERTY_DETAIL:
return JS_NewInt32(c, evt->detail);
case EVENT_JSPROPERTY_DATA:
{
u32 len;
s16 txt[2];
const u16 *srcp;
char szData[5];
txt[0] = evt->detail;
txt[1] = 0;
srcp = (const u16 *) txt;
len = gf_utf8_wcstombs(szData, 5, &srcp);
if (len == GF_UTF8_FAIL) len = 0;
szData[len] = 0;
return JS_NewString(c, szData);
}
case EVENT_JSPROPERTY_SCREENX:
return JS_NewInt32(c, evt->screenX);
case EVENT_JSPROPERTY_SCREENY:
return JS_NewInt32(c, evt->screenY);
case EVENT_JSPROPERTY_CLIENTX:
return JS_NewInt32(c, evt->clientX);
case EVENT_JSPROPERTY_CLIENTY:
return JS_NewInt32(c, evt->clientY);
case EVENT_JSPROPERTY_BUTTON:
return JS_NewInt32(c, evt->button);
case EVENT_JSPROPERTY_RELATEDTARGET:
if (evt->is_vrml) return JS_NULL;
return dom_element_construct(c, evt->relatedTarget);
case EVENT_JSPROPERTY_WHEELDELTA:
return JS_NewInt32(c, FIX2INT(evt->new_scale) );
case EVENT_JSPROPERTY_KEYIDENTIFIER:
return JS_NewString(c, gf_dom_get_key_name(evt->detail) );
/*Mozilla keyChar, charCode: wrap up to same value*/
case EVENT_JSPROPERTY_KEYCHAR:
case EVENT_JSPROPERTY_CHARCODE:
return JS_NewInt32(c, evt->detail);
case EVENT_JSPROPERTY_LOADED:
return JS_NewInt64(c, evt->media_event.loaded_size);
case EVENT_JSPROPERTY_TOTAL:
return JS_NewInt64(c, evt->media_event.total_size);
case EVENT_JSPROPERTY_BUFFER_ON:
return evt->media_event.bufferValid ? JS_TRUE : JS_FALSE;
case EVENT_JSPROPERTY_BUFFERLEVEL:
return JS_NewInt32(c, evt->media_event.level);
case EVENT_JSPROPERTY_BUFFERREMAININGTIME:
return JS_NewFloat64(c, evt->media_event.remaining_time);
case EVENT_JSPROPERTY_STATUS:
return JS_NewInt32(c, evt->media_event.status);
/*VRML ones*/
case EVENT_JSPROPERTY_WIDTH:
return JS_NewFloat64(c, FIX2FLT(evt->screen_rect.width) );
case EVENT_JSPROPERTY_HEIGHT:
return JS_NewFloat64(c, FIX2FLT(evt->screen_rect.height) );
case EVENT_JSPROPERTY_OFFSETX:
return JS_NewFloat64(c, FIX2FLT(evt->screen_rect.x) );
case EVENT_JSPROPERTY_OFFSETY:
return JS_NewFloat64(c, FIX2FLT(evt->screen_rect.y) );
case EVENT_JSPROPERTY_VPWIDTH:
return JS_NewFloat64(c, FIX2FLT(evt->prev_translate.x) );
case EVENT_JSPROPERTY_VPHEIGHT:
return JS_NewFloat64(c, FIX2FLT(evt->prev_translate.y) );
case EVENT_JSPROPERTY_TRANSLATIONX:
return JS_NewFloat64(c, FIX2FLT(evt->new_translate.x) );
case EVENT_JSPROPERTY_TRANSLATIONY:
return JS_NewFloat64(c, FIX2FLT(evt->new_translate.y) );
case EVENT_JSPROPERTY_TYPE3D:
return JS_NewInt32(c, evt->detail);
case EVENT_JSPROPERTY_ERROR:
return JS_NewInt32(c, evt->error_state);
case EVENT_JSPROPERTY_DYNAMIC_SCENE:
return JS_NewInt32(c, evt->key_flags);
case EVENT_JSPROPERTY_URL:
return JS_NewString(c, evt->addon_url ? evt->addon_url : "");
default:
break;
}
return JS_UNDEFINED;
}
#define SETUP_JSCLASS(_class, _name, _proto_funcs, _construct, _finalize, _proto_class_id) \
if (! _class.class_id) {\
JS_NewClassID(&(_class.class_id)); \
_class.class.class_name = _name; \
_class.class.finalizer = _finalize;\
JS_NewClass(jsrt, _class.class_id, &(_class.class));\
}\
proto = JS_NewObjectClass(c, _proto_class_id ? _proto_class_id : _class.class_id);\
JS_SetPropertyFunctionList(c, proto, _proto_funcs, countof(_proto_funcs));\
JS_SetClassProto(c, _class.class_id, proto);\
if (_construct) {\
JSValue ctor = JS_NewCFunction2(c, _construct, _name, 1, JS_CFUNC_constructor, 0);\
JS_SetPropertyStr(c, global, _name, ctor);\
}\
/*SVGMatrix class*/
static const JSCFunctionListEntry node_Funcs[] =
{
JS_CGETSET_MAGIC_DEF("nodeName", dom_node_getProperty, NULL, NODE_JSPROPERTY_NODENAME),
JS_CGETSET_MAGIC_DEF("nodeValue", dom_node_getProperty, dom_node_setProperty, NODE_JSPROPERTY_NODEVALUE),
JS_CGETSET_MAGIC_DEF("nodeType", dom_node_getProperty, NULL, NODE_JSPROPERTY_NODETYPE),
JS_CGETSET_MAGIC_DEF("parentNode", dom_node_getProperty, NULL, NODE_JSPROPERTY_PARENTNODE),
JS_CGETSET_MAGIC_DEF("childNodes", dom_node_getProperty, NULL, NODE_JSPROPERTY_CHILDNODES),
JS_CGETSET_MAGIC_DEF("firstChild", dom_node_getProperty, NULL, NODE_JSPROPERTY_FIRSTCHILD),
JS_CGETSET_MAGIC_DEF("lastChild", dom_node_getProperty, NULL, NODE_JSPROPERTY_LASTCHILD),
JS_CGETSET_MAGIC_DEF("previousSibling", dom_node_getProperty, NULL, NODE_JSPROPERTY_PREVIOUSSIBLING),
JS_CGETSET_MAGIC_DEF("nextSibling", dom_node_getProperty, NULL, NODE_JSPROPERTY_NEXTSIBLING),
JS_CGETSET_MAGIC_DEF("attributes", dom_node_getProperty, NULL, NODE_JSPROPERTY_ATTRIBUTES),
JS_CGETSET_MAGIC_DEF("ownerDocument", dom_node_getProperty, NULL, NODE_JSPROPERTY_OWNERDOCUMENT),
JS_CGETSET_MAGIC_DEF("namespaceURI", dom_node_getProperty, NULL, NODE_JSPROPERTY_NAMESPACEURI),
JS_CGETSET_MAGIC_DEF("prefix", dom_node_getProperty, dom_node_setProperty, NODE_JSPROPERTY_PREFIX),
JS_CGETSET_MAGIC_DEF("localName", dom_node_getProperty, NULL, NODE_JSPROPERTY_LOCALNAME),
JS_CGETSET_MAGIC_DEF("baseURI", dom_node_getProperty, NULL, NODE_JSPROPERTY_BASEURI),
JS_CGETSET_MAGIC_DEF("textContent", dom_node_getProperty, dom_node_setProperty, NODE_JSPROPERTY_TEXTCONTENT),
/*elementTraversal interface*/
JS_CGETSET_MAGIC_DEF("firstElementChild", dom_node_getProperty, NULL, NODE_JSPROPERTY_FIRSTELEMENTCHILD),
JS_CGETSET_MAGIC_DEF("lastElementChild", dom_node_getProperty, NULL, NODE_JSPROPERTY_LASTELEMENTCHILD),
JS_CGETSET_MAGIC_DEF("previousElementSibling", dom_node_getProperty, NULL, NODE_JSPROPERTY_PREVIOUSELEMENTSIBLING),
JS_CGETSET_MAGIC_DEF("nextElementSibling", dom_node_getProperty, NULL, NODE_JSPROPERTY_NEXTELEMENTSIBLING),
JS_CFUNC_DEF("insertBefore", 2, xml_node_insert_before),
JS_CFUNC_DEF("replaceChild", 2, xml_node_replace_child),
JS_CFUNC_DEF("removeChild", 1, xml_node_remove_child),
JS_CFUNC_DEF("appendChild", 1, xml_node_append_child),
JS_CFUNC_DEF("hasChildNodes", 0, xml_node_has_children),
JS_CFUNC_DEF("cloneNode", 1, xml_clone_node),
JS_CFUNC_DEF("normalize", 0, xml_dom3_not_implemented),
JS_CFUNC_DEF("isSupported", 2, xml_dom3_not_implemented),
JS_CFUNC_DEF("hasAttributes", 0, xml_node_has_attributes),
JS_CFUNC_DEF("compareDocumentPosition", 1, xml_dom3_not_implemented),
JS_CFUNC_DEF("isSameNode", 1, xml_node_is_same_node),
JS_CFUNC_DEF("lookupPrefix", 1, xml_dom3_not_implemented),
JS_CFUNC_DEF("isDefaultNamespace", 1, xml_dom3_not_implemented),
JS_CFUNC_DEF("lookupNamespaceURI", 1, xml_dom3_not_implemented),
/*we don't support full node compare*/
JS_CFUNC_DEF("isEqualNode", 1, xml_node_is_same_node),
JS_CFUNC_DEF("getFeature", 2, xml_dom3_not_implemented),
JS_CFUNC_DEF("setUserData", 3, xml_dom3_not_implemented),
JS_CFUNC_DEF("getUserData", 1, xml_dom3_not_implemented),
};
static const JSCFunctionListEntry document_Funcs[] =
{
JS_CGETSET_MAGIC_DEF("doctype", dom_document_getProperty, NULL, DOCUMENT_JSPROPERTY_DOCTYPE),
JS_CGETSET_MAGIC_DEF("implementation", dom_document_getProperty, NULL, DOCUMENT_JSPROPERTY_IMPLEMENTATION),
JS_CGETSET_MAGIC_DEF("documentElement", dom_document_getProperty, NULL, DOCUMENT_JSPROPERTY_DOCUMENTELEMENT),
JS_CGETSET_MAGIC_DEF("inputEncoding", dom_document_getProperty, NULL, DOCUMENT_JSPROPERTY_INPUTENCODING),
JS_CGETSET_MAGIC_DEF("xmlEncoding", dom_document_getProperty, NULL, DOCUMENT_JSPROPERTY_XMLENCODING),
JS_CGETSET_MAGIC_DEF("xmlStandalone", dom_document_getProperty, dom_document_setProperty, DOCUMENT_JSPROPERTY_XMLSTANDALONE),
JS_CGETSET_MAGIC_DEF("xmlVersion", dom_document_getProperty, dom_document_setProperty, DOCUMENT_JSPROPERTY_XMLVERSION),
JS_CGETSET_MAGIC_DEF("strictErrorChecking", dom_document_getProperty, dom_document_setProperty, DOCUMENT_JSPROPERTY_STRICTERRORCHECKING),
JS_CGETSET_MAGIC_DEF("documentURI", dom_document_getProperty, dom_document_setProperty, DOCUMENT_JSPROPERTY_DOCUMENTURI),
JS_CGETSET_MAGIC_DEF("location", dom_document_getProperty, dom_document_setProperty, DOCUMENT_JSPROPERTY_LOCATION),
JS_CGETSET_MAGIC_DEF("domConfig", dom_document_getProperty, dom_document_setProperty, DOCUMENT_JSPROPERTY_DOMCONFIG),
JS_CGETSET_MAGIC_DEF("global", dom_document_getProperty, dom_document_setProperty, DOCUMENT_JSPROPERTY_GLOBAL),
JS_CFUNC_DEF("createElement", 1, xml_document_create_element),
JS_CFUNC_DEF("createDocumentFragment", 0, xml_dom3_not_implemented),
JS_CFUNC_DEF("createTextNode", 1, xml_document_create_text),
JS_CFUNC_DEF("createComment", 1, xml_dom3_not_implemented),
JS_CFUNC_DEF("createCDATASection", 1, xml_dom3_not_implemented),
JS_CFUNC_DEF("createProcessingInstruction", 2, xml_dom3_not_implemented),
JS_CFUNC_DEF("createAttribute", 1, xml_dom3_not_implemented),
JS_CFUNC_DEF("createEntityReference", 1, xml_dom3_not_implemented),
JS_CFUNC_DEF("getElementsByTagName", 1, xml_document_elements_by_tag),
JS_CFUNC_DEF("importNode", 2, xml_dom3_not_implemented),
JS_CFUNC_DEF("createElementNS", 2, xml_document_create_element),
JS_CFUNC_DEF("createAttributeNS", 2, xml_dom3_not_implemented),
JS_CFUNC_DEF("getElementsByTagNameNS", 2, xml_document_elements_by_tag),
JS_CFUNC_DEF("getElementById", 1, xml_document_element_by_id),
JS_CFUNC_DEF("adoptNode", 1, xml_dom3_not_implemented),
JS_CFUNC_DEF("normalizeDocument", 0, xml_dom3_not_implemented),
JS_CFUNC_DEF("renameNode", 3, xml_dom3_not_implemented),
/*eventTarget interface*/
JS_DOM3_EVENT_TARGET_INTERFACE
/*DocumentEvent interface*/
JS_CFUNC_DEF("createEvent", 1, xml_dom3_not_implemented),
};
static const JSCFunctionListEntry element_Funcs[] =
{
JS_CGETSET_MAGIC_DEF("tagName", dom_element_getProperty, NULL, ELEMENT_JSPROPERTY_TAGNAME),
JS_CGETSET_MAGIC_DEF("schemaTypeInfo", dom_element_getProperty, NULL, ELEMENT_JSPROPERTY_SCHEMATYPEINFO),
JS_CFUNC_DEF("getAttribute", 1, xml_element_get_attribute),
JS_CFUNC_DEF("setAttribute", 2, xml_element_set_attribute),
JS_CFUNC_DEF("removeAttribute", 1, xml_element_remove_attribute),
JS_CFUNC_DEF("getAttributeNS", 2, xml_element_get_attribute),
JS_CFUNC_DEF("setAttributeNS", 3, xml_element_set_attribute),
JS_CFUNC_DEF("removeAttributeNS", 2, xml_element_remove_attribute),
JS_CFUNC_DEF("hasAttribute", 1, xml_element_has_attribute),
JS_CFUNC_DEF("hasAttributeNS", 2, xml_element_has_attribute),
JS_CFUNC_DEF("getElementsByTagName", 1, xml_element_elements_by_tag),
JS_CFUNC_DEF("getElementsByTagNameNS", 2, xml_element_elements_by_tag),
JS_CFUNC_DEF("setIdAttribute", 2, xml_element_set_id),
JS_CFUNC_DEF("setIdAttributeNS", 3, xml_element_set_id),
JS_CFUNC_DEF("toString", 0, xml_element_to_string),
JS_CFUNC_DEF("getAttributeNode", 1, xml_dom3_not_implemented),
JS_CFUNC_DEF("setAttributeNode", 1, xml_dom3_not_implemented),
JS_CFUNC_DEF("removeAttributeNode", 1, xml_dom3_not_implemented),
JS_CFUNC_DEF("getAttributeNodeNS", 2, xml_dom3_not_implemented),
JS_CFUNC_DEF("setAttributeNodeNS", 1, xml_dom3_not_implemented),
JS_CFUNC_DEF("setIdAttributeNode", 2, xml_dom3_not_implemented),
/*eventTarget interface*/
JS_DOM3_EVENT_TARGET_INTERFACE
};
static const JSCFunctionListEntry text_Funcs[] =
{
JS_CGETSET_MAGIC_DEF("data", dom_text_getProperty, dom_text_setProperty, TEXT_JSPROPERTY_DATA),
JS_CGETSET_MAGIC_DEF("length", dom_text_getProperty, NULL, TEXT_JSPROPERTY_LENGTH),
JS_CGETSET_MAGIC_DEF("isElementContentWhitespace", dom_text_getProperty, NULL, TEXT_JSPROPERTY_ISELEMENTCONTENTWHITESPACE),
JS_CGETSET_MAGIC_DEF("wholeText", dom_text_getProperty, NULL, TEXT_JSPROPERTY_WHOLETEXT),
#if 0
JS_CFUNC_DEF("substringData", 2, xml_dom3_not_implemented),
JS_CFUNC_DEF("appendData", 1, xml_dom3_not_implemented),
JS_CFUNC_DEF("insertData", 2, xml_dom3_not_implemented),
JS_CFUNC_DEF("deleteData", 2, xml_dom3_not_implemented),
JS_CFUNC_DEF("replaceData", 3, xml_dom3_not_implemented),
JS_CFUNC_DEF("splitText", 1, xml_dom3_not_implemented),
JS_CFUNC_DEF("replaceWholeText", 1, xml_dom3_not_implemented),
#endif
};
static const JSCFunctionListEntry event_Funcs[] =
{
JS_CGETSET_MAGIC_DEF("type", event_getProperty, NULL, EVENT_JSPROPERTY_TYPE),
JS_CGETSET_MAGIC_DEF("target", event_getProperty, NULL, EVENT_JSPROPERTY_TARGET),
JS_CGETSET_MAGIC_DEF("currentTarget", event_getProperty, NULL, EVENT_JSPROPERTY_CURRENTTARGET),
JS_CGETSET_MAGIC_DEF("eventPhase", event_getProperty, NULL, EVENT_JSPROPERTY_EVENTPHASE),
JS_CGETSET_MAGIC_DEF("bubbles", event_getProperty, NULL, EVENT_JSPROPERTY_BUBBLES),
JS_CGETSET_MAGIC_DEF("cancelable", event_getProperty, NULL, EVENT_JSPROPERTY_CANCELABLE),
JS_CGETSET_MAGIC_DEF("timeStamp", event_getProperty, NULL, EVENT_JSPROPERTY_TIMESTAMP),
JS_CGETSET_MAGIC_DEF("namespaceURI", event_getProperty, NULL, EVENT_JSPROPERTY_NAMESPACEURI),
JS_CGETSET_MAGIC_DEF("defaultPrevented", event_getProperty, NULL, EVENT_JSPROPERTY_DEFAULTPREVENTED),
/*UIEvent*/
JS_CGETSET_MAGIC_DEF("detail", event_getProperty, NULL, EVENT_JSPROPERTY_DETAIL),
/*text, connectionEvent*/
JS_CGETSET_MAGIC_DEF("data", event_getProperty, NULL, EVENT_JSPROPERTY_DATA),
/*MouseEvent*/
JS_CGETSET_MAGIC_DEF("screenX", event_getProperty, NULL, EVENT_JSPROPERTY_SCREENX),
JS_CGETSET_MAGIC_DEF("screenY", event_getProperty, NULL, EVENT_JSPROPERTY_SCREENY),
JS_CGETSET_MAGIC_DEF("clientX", event_getProperty, NULL, EVENT_JSPROPERTY_CLIENTX),
JS_CGETSET_MAGIC_DEF("clientY", event_getProperty, NULL, EVENT_JSPROPERTY_CLIENTY),
JS_CGETSET_MAGIC_DEF("button", event_getProperty, NULL, EVENT_JSPROPERTY_BUTTON),
JS_CGETSET_MAGIC_DEF("relatedTarget", event_getProperty, NULL, EVENT_JSPROPERTY_RELATEDTARGET),
/*wheelEvent*/
JS_CGETSET_MAGIC_DEF("wheelDelta", event_getProperty, NULL, EVENT_JSPROPERTY_WHEELDELTA),
/*keyboard*/
JS_CGETSET_MAGIC_DEF("keyIdentifier", event_getProperty, NULL, EVENT_JSPROPERTY_KEYIDENTIFIER),
JS_CGETSET_MAGIC_DEF("keyChar", event_getProperty, NULL, EVENT_JSPROPERTY_KEYCHAR),
JS_CGETSET_MAGIC_DEF("charCode", event_getProperty, NULL, EVENT_JSPROPERTY_CHARCODE),
/*progress*/
JS_CGETSET_MAGIC_DEF("lengthComputable", event_getProperty, NULL, EVENT_JSPROPERTY_LENGTHCOMPUTABLE),
JS_CGETSET_MAGIC_DEF("typeArg", event_getProperty, NULL, EVENT_JSPROPERTY_TYPEARG),
JS_CGETSET_MAGIC_DEF("loaded", event_getProperty, NULL, EVENT_JSPROPERTY_LOADED),
JS_CGETSET_MAGIC_DEF("total", event_getProperty, NULL, EVENT_JSPROPERTY_TOTAL),
JS_CGETSET_MAGIC_DEF("buffering", event_getProperty, NULL, EVENT_JSPROPERTY_BUFFER_ON),
JS_CGETSET_MAGIC_DEF("bufferLevel", event_getProperty, NULL, EVENT_JSPROPERTY_BUFFERLEVEL),
JS_CGETSET_MAGIC_DEF("bufferRemainingTime", event_getProperty, NULL, EVENT_JSPROPERTY_BUFFERREMAININGTIME),
JS_CGETSET_MAGIC_DEF("status", event_getProperty, NULL, EVENT_JSPROPERTY_STATUS),
/*used by vrml*/
JS_CGETSET_MAGIC_DEF("width", event_getProperty, NULL, EVENT_JSPROPERTY_WIDTH),
JS_CGETSET_MAGIC_DEF("height", event_getProperty, NULL, EVENT_JSPROPERTY_HEIGHT),
JS_CGETSET_MAGIC_DEF("offset_x", event_getProperty, NULL, EVENT_JSPROPERTY_OFFSETX),
JS_CGETSET_MAGIC_DEF("offset_y", event_getProperty, NULL, EVENT_JSPROPERTY_OFFSETY),
JS_CGETSET_MAGIC_DEF("vp_width", event_getProperty, NULL, EVENT_JSPROPERTY_VPWIDTH),
JS_CGETSET_MAGIC_DEF("vp_height", event_getProperty, NULL, EVENT_JSPROPERTY_VPHEIGHT),
JS_CGETSET_MAGIC_DEF("translation_x", event_getProperty, NULL, EVENT_JSPROPERTY_TRANSLATIONX),
JS_CGETSET_MAGIC_DEF("translation_y", event_getProperty, NULL, EVENT_JSPROPERTY_TRANSLATIONY),
JS_CGETSET_MAGIC_DEF("type3d", event_getProperty, NULL, EVENT_JSPROPERTY_TYPE3D),
JS_CGETSET_MAGIC_DEF("error", event_getProperty, NULL, EVENT_JSPROPERTY_ERROR),
JS_CGETSET_MAGIC_DEF("dynamic_scene", event_getProperty, NULL, EVENT_JSPROPERTY_DYNAMIC_SCENE),
JS_CGETSET_MAGIC_DEF("url", event_getProperty, NULL, EVENT_JSPROPERTY_URL),
JS_CFUNC_DEF("stopPropagation", 0, event_stop_propagation),
JS_CFUNC_DEF("stopImmediatePropagation", 0, event_stop_immediate_propagation),
JS_CFUNC_DEF("preventDefault", 0, event_prevent_default),
#if 0
JS_CFUNC_DEF("initEvent", 3, event_prevent_default),
JS_CFUNC_DEF("initEventNS", 4, event_prevent_default),
#endif
};
static const JSCFunctionListEntry nodeList_Funcs[] =
{
JS_CGETSET_MAGIC_DEF("length", dom_nodelist_getProperty, NULL, NODELIST_JSPROPERTY_LENGTH),
JS_CFUNC_DEF("item", 1, dom_nodelist_item),
};
void domDocument_gc_mark(JSRuntime *rt, JSValueConst obj, JS_MarkFunc *mark_func)
{
GF_SceneGraph *sg;
JSClassID _classID;
sg = (GF_SceneGraph*) JS_GetAnyOpaque(obj, &_classID);
/*the JS proto of the svgClass or a destroyed object*/
if (!sg || !sg->js_data) return;
if (!JS_IsUndefined(sg->js_data->document))
JS_MarkValue(rt, sg->js_data->document, mark_func);
}
void domElement_gc_mark(JSRuntime *rt, JSValueConst obj, JS_MarkFunc *mark_func)
{
JSClassID _classID;
GF_Node *n = (GF_Node *) JS_GetAnyOpaque(obj, &_classID);
/*the JS proto of the svgClass or a destroyed object*/
if (!n) return;
if (!n->sgprivate || !n->sgprivate->interact || !n->sgprivate->interact->js_binding) return;
if (!JS_IsUndefined(n->sgprivate->interact->js_binding->obj))
JS_MarkValue(rt, n->sgprivate->interact->js_binding->obj, mark_func);
}
void dom_js_load(GF_SceneGraph *scene, JSContext *c)
{
JSValue proto;
JSValue global = JS_GetGlobalObject(c);
JSRuntime *jsrt = JS_GetRuntime(c);
if (!dom_rt) {
GF_SAFEALLOC(dom_rt, GF_DOMRuntime);
if (!dom_rt) {
GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[DOMJS] Failed to allocate DOM runtime\n"));
return;
}
dom_rt->handlers = gf_list_new();
GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[DOMCore] dom run-time allocated\n"));
}
dom_rt->nb_inst++;
define_dom_exception(c, global);
domNodeClass.class.gc_mark = domElement_gc_mark;
SETUP_JSCLASS(domNodeClass, "Node", node_Funcs, NULL, dom_node_finalize, 0);
JS_SetPropertyStr(c, proto, "ELEMENT_NODE", JS_NewInt32(c, 1) );
JS_SetPropertyStr(c, proto, "TEXT_NODE", JS_NewInt32(c, 3));
JS_SetPropertyStr(c, proto, "CDATA_SECTION_NODE", JS_NewInt32(c, 4));
JS_SetPropertyStr(c, proto, "DOCUMENT_NODE", JS_NewInt32(c, 9));
domDocumentClass.class.gc_mark = domDocument_gc_mark;
SETUP_JSCLASS(domDocumentClass, "Document", document_Funcs, NULL, dom_document_finalize, domNodeClass.class_id);
domElementClass.class.gc_mark = domElement_gc_mark;
SETUP_JSCLASS(domElementClass, "Element", element_Funcs, NULL, dom_node_finalize, domNodeClass.class_id);
domTextClass.class.gc_mark = domElement_gc_mark;
SETUP_JSCLASS(domTextClass, "Text", text_Funcs, NULL, dom_node_finalize, domNodeClass.class_id);
SETUP_JSCLASS(domEventClass, "Event", event_Funcs, NULL, NULL, 0);
JS_SetPropertyStr(c, proto, "CAPTURING_PHASE", JS_NewInt32(c, 1));
JS_SetPropertyStr(c, proto, "AT_TARGET", JS_NewInt32(c, 2));
JS_SetPropertyStr(c, proto, "BUBBLING_PHASE", JS_NewInt32(c, 3));
JS_SetPropertyStr(c, proto, "DOM_KEY_LOCATION_STANDARD ", JS_NewInt32(c, 0));
JS_SetPropertyStr(c, proto, "DOM_KEY_LOCATION_LEFT", JS_NewInt32(c, 1));
JS_SetPropertyStr(c, proto, "DOM_KEY_LOCATION_RIGHT", JS_NewInt32(c, 2));
JS_SetPropertyStr(c, proto, "DOM_KEY_LOCATION_NUMPAD", JS_NewInt32(c, 3));
SETUP_JSCLASS(domNodeListClass, "NodeList", nodeList_Funcs, NULL, dom_nodelist_finalize, 0);
JS_FreeValue(c, global);
}
#ifdef GPAC_ENABLE_HTML5_MEDIA
void html_media_element_js_finalize(JSContext *c, GF_Node *n);
#endif
GF_EXPORT
void gf_sg_js_dom_pre_destroy(JSRuntime *rt, GF_SceneGraph *sg, GF_Node *n)
{
u32 i, count;
if (n) {
if (n->sgprivate->interact && n->sgprivate->interact->js_binding && !JS_IsUndefined(n->sgprivate->interact->js_binding->obj) ) {
JS_SetOpaque(n->sgprivate->interact->js_binding->obj, NULL);
if (gf_list_del_item(sg->objects, n->sgprivate->interact->js_binding)>=0) {
JS_SetOpaque(n->sgprivate->interact->js_binding->obj, NULL);
JS_FreeValueRT(rt, n->sgprivate->interact->js_binding->obj);
}
n->sgprivate->interact->js_binding->obj = JS_UNDEFINED;
}
return;
}
/*force cleanup of scripts/handlers not destroyed - this usually happens when a script/handler node has been created in a script
but not inserted in the graph*/
while (gf_list_count(sg->objects)) {
GF_Node *js_node;
struct _node_js_binding *js_bind = (struct _node_js_binding *)gf_list_get(sg->objects, 0);
js_node = dom_get_node(js_bind->obj);
if (js_node) {
if (js_node->sgprivate->tag == TAG_SVG_video || js_node->sgprivate->tag == TAG_SVG_audio) {
#ifdef GPAC_ENABLE_HTML5_MEDIA
html_media_element_js_finalize(c, js_node);
#endif
}
JS_SetOpaque(js_bind->obj, NULL);
JS_FreeValueRT(rt, js_node->sgprivate->interact->js_binding->obj);
js_node->sgprivate->interact->js_binding->obj=JS_UNDEFINED;
gf_node_unregister(js_node, NULL);
}
gf_list_rem(sg->objects, 0);
}
count = dom_rt ? gf_list_count(dom_rt->handlers) : 0;
for (i=0; i<count; i++) {
SVG_handlerElement *handler = (SVG_handlerElement *)gf_list_get(dom_rt->handlers, i);
if (!handler->js_data) continue;
/*if same context and same document, discard handler*/
if (!sg || (handler->sgprivate->scenegraph==sg)) {
/*unprotect the function*/
JS_FreeValueRT(rt, handler->js_data->fun_val);
gf_free(handler->js_data);
handler->js_data = NULL;
gf_list_rem(dom_rt->handlers, i);
i--;
count--;
}
}
if (sg->js_data) {
if (!JS_IsUndefined(sg->js_data->document)) {
//detach sg from object and free it
JS_SetOpaque(sg->js_data->document, NULL);
JS_FreeValueRT(rt, sg->js_data->document);
}
gf_free(sg->js_data);
sg->js_data = NULL;
}
}
void dom_js_unload()
{
if (!dom_rt) return;
dom_rt->nb_inst--;
if (!dom_rt->nb_inst) {
gf_list_del(dom_rt->handlers);
gf_free(dom_rt);
dom_rt = NULL;
}
}
JSValue dom_js_define_event(JSContext *c)
{
JSValue global = JS_GetGlobalObject(c);
JSValue obj = JS_NewObjectClass(c, domEventClass.class_id);
JS_SetPropertyStr(c, global, "evt", obj);
// JS_DefinePropertyStr(c, global, "event", obj);
JS_FreeValue(c, global);
return obj;
}
GF_DOM_Event *dom_get_evt_private(JSValue v)
{
return JS_GetOpaque(v, domEventClass.class_id);
}
JSValue gf_dom_new_event(JSContext *c)
{
return JS_NewObjectClass(c, domEventClass.class_id);
}
JSClassID dom_js_get_element_proto(JSContext *c)
{
return domElementClass.class_id;
}
JSClassID dom_js_get_document_proto(JSContext *c)
{
return domDocumentClass.class_id;
}
#endif /*GPAC_HAS_QJS*/
#if 0 //unused
/*! parses the given XML document and returns a scene graph composed of GF_DOMFullNode
\param src the source file
\param scene set to the new scene graph
\return error if any
*/
GF_Err gf_sg_new_from_xml_doc(const char *src, GF_SceneGraph **scene)
{
#ifdef GPAC_HAS_QJS
GF_Err e;
XMLHTTPContext *ctx;
GF_SceneGraph *sg;
GF_SAFEALLOC(ctx, XMLHTTPContext);
if (!ctx) return GF_OUT_OF_MEM;
ctx->sax = gf_xml_sax_new(xml_http_sax_start, xml_http_sax_end, xml_http_sax_text, ctx);
ctx->node_stack = gf_list_new();
sg = gf_sg_new();
ctx->document = sg;
e = gf_xml_sax_parse_file(ctx->sax, src, NULL);
gf_xml_sax_del(ctx->sax);
gf_list_del(ctx->node_stack);
gf_free(ctx);
*scene = NULL;
if (e<0) {
gf_sg_del(sg);
return e;
}
*scene = sg;
return GF_OK;
#else
return GF_NOT_SUPPORTED;
#endif
}
#endif
#if 0 //unused
typedef struct
{
GF_SAXParser *sax;
GF_List *node_stack;
/*dom graph*/
GF_SceneGraph *document;
} XMLReloadContext;
static void xml_reload_node_start(void *sax_cbck, const char *node_name, const char *name_space, const GF_XMLAttribute *attributes, u32 nb_attributes)
{
GF_DOM_Event evt;
Bool is_root = GF_FALSE;
Bool modified = GF_FALSE;
Bool atts_modified = GF_FALSE;
Bool new_node = GF_FALSE;
u32 i;
GF_DOMFullAttribute *prev = NULL;
GF_DOMFullNode *par = NULL;
XMLReloadContext *ctx = (XMLReloadContext *)sax_cbck;
GF_DOMFullNode *node;
node = (GF_DOMFullNode *)gf_list_last(ctx->node_stack);
if (!node) {
is_root = GF_TRUE;
node = (GF_DOMFullNode *) ctx->document->RootNode;
}
if (is_root) {
Bool same_node = GF_TRUE;
if (strcmp(node->name, node_name)) same_node = GF_FALSE;
if (node->ns && name_space && strcmp(gf_sg_get_namespace(node->sgprivate->scenegraph, node->ns), name_space)) same_node = GF_FALSE;
if (!same_node) {
if (node->name) gf_free(node->name);
node->name = gf_strdup(node_name);
node->ns = 0;
if (name_space) {
gf_sg_add_namespace(node->sgprivate->scenegraph, (char *) name_space, NULL);
node->ns = gf_sg_get_namespace_code_from_name(node->sgprivate->scenegraph, (char*)name_space);
}
modified = GF_TRUE;
}
} else {
GF_ChildNodeItem *child = node->children;
node = NULL;
/*locate the node in the children*/
while (child) {
Bool same_node = GF_TRUE;
node = (GF_DOMFullNode*)child->node;
if (strcmp(node->name, node_name)) same_node = GF_FALSE;
if (node->ns && name_space && strcmp(gf_sg_get_namespace(node->sgprivate->scenegraph, node->ns), name_space)) same_node = GF_FALSE;
if (same_node) {
break;
}
node = NULL;
child = child->next;
}
if (!node) {
modified = GF_TRUE;
/*create the new node*/
node = (GF_DOMFullNode *) gf_node_new(ctx->document, TAG_DOMFullNode);
node->name = gf_strdup(node_name);
if (name_space) {
gf_sg_add_namespace(node->sgprivate->scenegraph, (char *)name_space, NULL);
node->ns = gf_sg_get_namespace_code(node->sgprivate->scenegraph, (char *)name_space);
}
par = (GF_DOMFullNode *)gf_list_last(ctx->node_stack);
gf_node_register((GF_Node*)node, (GF_Node*)par);
gf_node_list_add_child(&par->children, (GF_Node*)node);
new_node = GF_TRUE;
}
}
gf_list_add(ctx->node_stack, node);
if (!modified) {
u32 count = 0;
GF_DOMFullAttribute *att = (GF_DOMFullAttribute *)node->attributes;
while (att) {
Bool found = GF_FALSE;
for (i=0; i<nb_attributes; i++) {
if (!stricmp(attributes[i].name, "xml:id")) {
const char *id = gf_node_get_name((GF_Node*)node);
if (!id || strcmp(id, attributes[i].value))
modified = GF_TRUE;
} else if (!strcmp(att->name, attributes[i].name)) {
found = GF_TRUE;
if (strcmp((const char *)att->data, attributes[i].value)) {
atts_modified = GF_TRUE;
gf_free(att->data);
att->data = gf_strdup(attributes[i].value);
}
}
}
if (!found) {
modified = GF_TRUE;
break;
}
count++;
att = (GF_DOMFullAttribute *)att->next;
}
if (count != nb_attributes)
modified = GF_TRUE;
}
if (modified) {
GF_DOMFullAttribute *tmp, *att;
att = (GF_DOMFullAttribute *)node->attributes;
while (att) {
if (att->name) gf_free(att->name);
if (att->data) gf_free(att->data);
tmp = att;
att = (GF_DOMFullAttribute *)att->next;
gf_free(tmp);
}
/*parse all atts*/
for (i=0; i<nb_attributes; i++) {
if (!stricmp(attributes[i].name, "xml:id")) {
u32 id = gf_sg_get_max_node_id(ctx->document) + 1;
gf_node_set_id((GF_Node *)node, id, attributes[i].value);
} else {
GF_DOMFullAttribute *att;
GF_SAFEALLOC(att, GF_DOMFullAttribute);
if (!att) {
GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[DOMJS] Failed to allocate DOM attribute\n"));
continue;
}
att->tag = TAG_DOM_ATT_any;
att->name = gf_strdup(attributes[i].name);
att->data = gf_strdup(attributes[i].value);
if (prev) prev->next = (GF_DOMAttribute*)att;
else node->attributes = (GF_DOMAttribute*)att;
prev = att;
}
}
atts_modified = GF_TRUE;
}
if (atts_modified || new_node) {
memset(&evt, 0, sizeof(GF_DOM_Event));
evt.type = new_node ? GF_EVENT_NODE_INSERTED : GF_EVENT_ATTR_MODIFIED;
evt.bubbles = 1;
evt.relatedNode = (GF_Node*) ( (evt.type == GF_EVENT_NODE_INSERTED) ? par : node);
gf_dom_event_fire((GF_Node*)node, &evt);
}
}
static void xml_reload_node_end(void *sax_cbck, const char *node_name, const char *name_space)
{
XMLReloadContext *ctx = (XMLReloadContext *)sax_cbck;
GF_DOMFullNode *par = (GF_DOMFullNode *)gf_list_last(ctx->node_stack);
if (par) {
/*depth mismatch*/
if (strcmp(par->name, node_name)) return;
gf_list_rem_last(ctx->node_stack);
}
}
static void xml_reload_text_content(void *sax_cbck, const char *content, Bool is_cdata)
{
GF_DOM_Event evt;
u32 i, len;
GF_ChildNodeItem *child;
GF_DOMText *txt = NULL;
XMLReloadContext *ctx = (XMLReloadContext *)sax_cbck;
GF_DOMFullNode *par = (GF_DOMFullNode *)gf_list_last(ctx->node_stack);
if (!par) return;
/*basic check, remove all empty text nodes*/
len = (u32) strlen(content);
for (i=0; i<len; i++) {
if (!strchr(" \n\r\t", content[i])) break;
}
if (i==len) return;
child = par->children;
while (child) {
if (child->node->sgprivate->tag == TAG_DOMText) {
txt = (GF_DOMText *)child->node;
if (!strcmp(txt->textContent, content) && ((txt->type==GF_DOM_TEXT_REGULAR) || is_cdata))
return;
if (txt->textContent) gf_free(txt->textContent);
txt->textContent = gf_strdup(content);
txt->type = is_cdata ? GF_DOM_TEXT_CDATA : GF_DOM_TEXT_REGULAR;
break;
}
child = child->next;
}
if (!txt) {
txt = gf_dom_add_text_node((GF_Node *)par, gf_strdup(content) );
txt->type = is_cdata ? GF_DOM_TEXT_CDATA : GF_DOM_TEXT_REGULAR;
memset(&evt, 0, sizeof(GF_DOM_Event));
evt.type = GF_EVENT_NODE_INSERTED;
evt.bubbles = 1;
evt.relatedNode = (GF_Node*) par;
gf_dom_event_fire((GF_Node*)txt, &evt);
}
memset(&evt, 0, sizeof(GF_DOM_Event));
evt.type = GF_EVENT_CHAR_DATA_MODIFIED;
evt.bubbles = 1;
evt.relatedNode = (GF_Node*)par;
gf_dom_event_fire((GF_Node*)par, &evt);
memset(&evt, 0, sizeof(GF_DOM_Event));
evt.type = GF_EVENT_DCCI_PROP_CHANGE;
evt.bubbles = 1;
evt.relatedNode = (GF_Node*)par;
gf_dom_event_fire((GF_Node*)par, &evt);
}
GF_Err gf_sg_reload_xml_doc(const char *src, GF_SceneGraph *scene)
{
GF_Err e;
XMLReloadContext ctx;
if (!src || !scene) return GF_BAD_PARAM;
memset(&ctx, 0, sizeof(XMLReloadContext));
ctx.document = scene;
ctx.node_stack = gf_list_new();
ctx.sax = gf_xml_sax_new(xml_reload_node_start, xml_reload_node_end, xml_reload_text_content, &ctx);
e = gf_xml_sax_parse_file(ctx.sax, src, NULL);
gf_list_del(ctx.node_stack);
gf_xml_sax_del(ctx.sax);
if (e<0) return e;
return GF_OK;
}
#endif
#endif /*GPAC_DISABLE_SVG*/