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