blob: c462c30201cebf07531f6448eb9dcb79f80d0727 [file] [edit]
/*
* GPAC - Multimedia Framework C SDK
*
* Authors: Jean Le Feuvre
* Copyright (c) Telecom ParisTech 2007-2025
* All rights reserved
*
* This file is part of GPAC / JavaScript XmlHttpRequest bindings
*
* 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.
*
*/
/*
ANY CHANGE TO THE API MUST BE REFLECTED IN THE DOCUMENTATION IN gpac/share/doc/idl/xhr.idl
(no way to define inline JS doc with doxygen)
*/
#include <gpac/setup.h>
#if defined(GPAC_HAS_QJS) && defined(GPAC_USE_DOWNLOADER)
/*base SVG type*/
#include <gpac/nodes_svg.h>
#include <gpac/nodes_mpeg4.h>
#include <gpac/nodes_x3d.h>
/*dom events*/
#include <gpac/events.h>
#include <gpac/download.h>
#include <gpac/network.h>
#include <gpac/xml.h>
#include <gpac/internal/scenegraph_dev.h>
#include "../quickjs/quickjs.h"
#include "../scenegraph/qjs_common.h"
typedef struct __xhr_context XMLHTTPContext;
/************************************************************
*
* xmlHttpRequest implementation
*
*************************************************************/
typedef enum {
XHR_ONABORT,
XHR_ONERROR,
XHR_ONLOAD,
XHR_ONLOADEND,
XHR_ONLOADSTART,
XHR_ONPROGRESS,
XHR_ONREADYSTATECHANGE,
XHR_ONTIMEOUT,
XHR_READYSTATE,
XHR_RESPONSE,
XHR_RESPONSETYPE,
XHR_RESPONSETEXT,
XHR_RESPONSEXML,
XHR_STATUS,
XHR_STATUSTEXT,
XHR_TIMEOUT,
XHR_UPLOAD,
XHR_WITHCREDENTIALS,
XHR_STATIC_UNSENT,
XHR_STATIC_OPENED,
XHR_STATIC_HEADERS_RECEIVED,
XHR_STATIC_LOADING,
XHR_STATIC_DONE,
XHR_CACHE,
} XHR_JSProperty;
typedef enum {
XHR_READYSTATE_UNSENT = 0,
XHR_READYSTATE_OPENED = 1,
XHR_READYSTATE_HEADERS_RECEIVED = 2,
XHR_READYSTATE_LOADING = 3,
XHR_READYSTATE_DONE = 4
} XHR_ReadyState;
typedef enum {
XHR_RESPONSETYPE_NONE,
XHR_RESPONSETYPE_ARRAYBUFFER,
XHR_RESPONSETYPE_BLOB,
XHR_RESPONSETYPE_DOCUMENT,
XHR_RESPONSETYPE_JSON,
XHR_RESPONSETYPE_TEXT,
XHR_RESPONSETYPE_SAX,
XHR_RESPONSETYPE_PUSH,
} XHR_ResponseType;
typedef enum {
XHR_CACHETYPE_NORMAL,
XHR_CACHETYPE_NONE,
XHR_CACHETYPE_MEMORY,
} XHR_CacheType;
struct __xhr_context
{
JSContext *c;
JSValue _this;
/* callback functions */
JSValue onabort;
JSValue onerror;
JSValue onreadystatechange;
JSValue onload;
JSValue onloadstart;
JSValue onloadend;
JSValue onprogress;
JSValue ontimeout;
XHR_ReadyState readyState;
Bool async;
Bool in_send;
/* GPAC extension to control the caching of XHR-downloaded resources */
XHR_CacheType cache;
/*header/header-val, terminated by NULL*/
char **headers;
u32 cur_header;
char *method, *url;
GF_DownloadSession *sess;
char *data;
u32 size;
JSValue arraybuffer;
GF_Err ret_code;
u32 html_status;
char *statusText;
char *mime;
u32 timeout;
XHR_ResponseType responseType;
Bool withCredentials;
Bool isFile;
GF_SAXParser *sax;
GF_List *node_stack;
GF_DOMEventTarget *event_target;
#if !defined(GPAC_DISABLE_SVG)
/* dom graph in which the XHR is created */
GF_SceneGraph *owning_graph;
Bool local_graph;
/* dom graph used to parse XML into */
GF_SceneGraph *document;
#endif
Bool js_dom_loaded;
};
GF_JSClass xhrClass;
#if 0 //unused
GF_SceneGraph *xml_http_get_scenegraph(XMLHTTPContext *ctx)
{
return ctx->owning_graph;
}
#endif
static void xml_http_append_send_header(XMLHTTPContext *ctx, char *hdr, char *val)
{
if (!hdr) return;
if (ctx->headers) {
u32 nb_hdr = 0;
while (ctx->headers && ctx->headers[nb_hdr]) {
if (stricmp(ctx->headers[nb_hdr], hdr)) {
nb_hdr+=2;
continue;
}
/*ignore these ones*/
if (!stricmp(hdr, "Accept-Charset")
|| !stricmp(hdr, "Accept-Encoding")
|| !stricmp(hdr, "Content-Length")
|| !stricmp(hdr, "Expect")
|| !stricmp(hdr, "Date")
|| !stricmp(hdr, "Host")
|| !stricmp(hdr, "Keep-Alive")
|| !stricmp(hdr, "Referer")
|| !stricmp(hdr, "TE")
|| !stricmp(hdr, "Trailer")
|| !stricmp(hdr, "Transfer-Encoding")
|| !stricmp(hdr, "Upgrade")
) {
return;
}
/*replace content for these ones*/
if (!stricmp(hdr, "Authorization")
|| !stricmp(hdr, "Content-Base")
|| !stricmp(hdr, "Content-Location")
|| !stricmp(hdr, "Content-MD5")
|| !stricmp(hdr, "Content-Range")
|| !stricmp(hdr, "Content-Type")
|| !stricmp(hdr, "Content-Version")
|| !stricmp(hdr, "Delta-Base")
|| !stricmp(hdr, "Depth")
|| !stricmp(hdr, "Destination")
|| !stricmp(hdr, "ETag")
|| !stricmp(hdr, "From")
|| !stricmp(hdr, "If-Modified-Since")
|| !stricmp(hdr, "If-Range")
|| !stricmp(hdr, "If-Unmodified-Since")
|| !stricmp(hdr, "Max-Forwards")
|| !stricmp(hdr, "MIME-Version")
|| !stricmp(hdr, "Overwrite")
|| !stricmp(hdr, "Proxy-Authorization")
|| !stricmp(hdr, "SOAPAction")
|| !stricmp(hdr, "Timeout") ) {
gf_free(ctx->headers[nb_hdr+1]);
ctx->headers[nb_hdr+1] = gf_strdup(val);
return;
}
/*append value*/
else {
char *new_val = (char *)gf_malloc(sizeof(char) * (strlen(ctx->headers[nb_hdr+1])+strlen(val)+3));
sprintf(new_val, "%s, %s", ctx->headers[nb_hdr+1], val);
gf_free(ctx->headers[nb_hdr+1]);
ctx->headers[nb_hdr+1] = new_val;
return;
}
}
}
}
static void xml_http_del_data(XMLHTTPContext *ctx)
{
if (!JS_IsUndefined(ctx->arraybuffer)) {
JS_FreeValue(ctx->c, ctx->arraybuffer);
ctx->arraybuffer = JS_UNDEFINED;
}
//only free data if no arraybuffer was used to fetch it
else if (ctx->data) {
gf_free(ctx->data);
}
ctx->data = NULL;
ctx->size = 0;
}
static void xml_http_reset_partial(XMLHTTPContext *ctx)
{
xml_http_del_data(ctx);
if (ctx->mime) {
gf_free(ctx->mime);
ctx->mime = NULL;
}
if (ctx->statusText) {
gf_free(ctx->statusText);
ctx->statusText = NULL;
}
if (ctx->headers) {
u32 i=0;
while (ctx->headers[i]) {
gf_free(ctx->headers[i]);
i++;
}
gf_free(ctx->headers);
ctx->headers = NULL;
}
ctx->cur_header = 0;
ctx->html_status = 0;
}
static void xml_http_reset(XMLHTTPContext *ctx, Bool delete_conn)
{
if (ctx->method) {
gf_free(ctx->method);
ctx->method = NULL;
}
if (ctx->url) {
gf_free(ctx->url);
ctx->url = NULL;
}
xml_http_reset_partial(ctx);
if (ctx->sess && delete_conn) {
GF_DownloadSession *tmp = ctx->sess;
ctx->sess = NULL;
gf_dm_sess_abort(tmp);
gf_dm_sess_del(tmp);
}
if (ctx->url) {
gf_free(ctx->url);
ctx->url = NULL;
}
if (ctx->sax) {
gf_xml_sax_del(ctx->sax);
ctx->sax = NULL;
}
if (ctx->node_stack) {
gf_list_del(ctx->node_stack);
ctx->node_stack = NULL;
}
#ifndef GPAC_DISABLE_SVG
if (ctx->document) {
if (ctx->js_dom_loaded) {
dom_js_unload();
ctx->js_dom_loaded = GF_FALSE;
}
gf_node_unregister(ctx->document->RootNode, NULL);
/*we're sure the graph is a "nomade" one since we initially put the refcount to 1 ourselves*/
ctx->document->reference_count--;
if (!ctx->document->reference_count) {
gf_sg_js_dom_pre_destroy(JS_GetRuntime(ctx->c), ctx->document, NULL);
gf_sg_del(ctx->document);
}
}
ctx->document = NULL;
#endif
ctx->size = 0;
ctx->async = GF_FALSE;
ctx->readyState = XHR_READYSTATE_UNSENT;
ctx->ret_code = GF_OK;
ctx->in_send = GF_FALSE;
}
static void xml_http_finalize(JSRuntime *rt, JSValue obj)
{
XMLHTTPContext *ctx = (XMLHTTPContext *) JS_GetOpaque(obj, xhrClass.class_id);
if (!ctx) return;
JS_FreeValueRT(rt, ctx->onabort);
JS_FreeValueRT(rt, ctx->onerror);
JS_FreeValueRT(rt, ctx->onload);
JS_FreeValueRT(rt, ctx->onloadend);
JS_FreeValueRT(rt, ctx->onloadstart);
JS_FreeValueRT(rt, ctx->onprogress);
JS_FreeValueRT(rt, ctx->onreadystatechange);
JS_FreeValueRT(rt, ctx->ontimeout);
xml_http_reset(ctx, GF_TRUE);
#if !defined(GPAC_DISABLE_SVG) && !defined(GPAC_DISABLE_VRML)
if (ctx->event_target) {
if (ctx->local_graph) {
while (gf_list_count(ctx->event_target->listeners)) {
GF_Node *listener = gf_list_get(ctx->event_target->listeners, 0);
gf_dom_listener_del(listener, ctx->event_target);
}
}
gf_dom_event_target_del(ctx->event_target);
}
if (ctx->local_graph) {
dom_js_unload();
gf_sg_del(ctx->owning_graph);
}
#endif
gf_free(ctx);
}
static GFINLINE GF_SceneGraph *xml_get_scenegraph(JSContext *c)
{
GF_SceneGraph *scene;
JSClassID _classID;
JSValue global = JS_GetGlobalObject(c);
JSValue sg_obj = JS_GetPropertyStr(c,global, "__scenegraph");
JS_FreeValue(c, global);
if (JS_IsUndefined(sg_obj)) return NULL;
scene = (GF_SceneGraph *) JS_GetAnyOpaque(sg_obj, &_classID);
JS_FreeValue(c, sg_obj);
return scene;
}
#if !defined(GPAC_DISABLE_SVG) && defined(GPAC_USE_DOWNLOADER)
void xhr_get_event_target(JSContext *c, JSValue obj, GF_SceneGraph **sg, GF_DOMEventTarget **target)
{
if (c) {
/*XHR interface*/
XMLHTTPContext *ctx = JS_GetOpaque(obj, xhrClass.class_id);
if (!ctx) return;
if (ctx->local_graph)
*sg = ctx->owning_graph;
else
*sg = xml_get_scenegraph(c);
*target = ctx->event_target;
}
}
#endif
static JSValue xml_http_constructor(JSContext *c, JSValueConst new_target, int argc, JSValueConst *argv)
{
XMLHTTPContext *p;
JSValue obj;
GF_SAFEALLOC(p, XMLHTTPContext);
if (!p) {
GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[WHR] Failed to allocate XHR object\n"));
return GF_JS_EXCEPTION(c);
}
obj = JS_NewObjectClass(c, xhrClass.class_id);
p->c = c;
p->_this = obj;
#if !defined(GPAC_DISABLE_SVG)
p->owning_graph = xml_get_scenegraph(c);
if (!p->owning_graph) {
p->local_graph = GF_TRUE;
p->owning_graph = gf_sg_new();
dom_js_load(p->owning_graph, c);
}
if (p->owning_graph)
p->event_target = gf_dom_event_target_new(GF_DOM_EVENT_TARGET_XHR, p);
#endif
p->onabort = JS_NULL;
p->onerror = JS_NULL;
p->onreadystatechange = JS_NULL;
p->onload = JS_NULL;
p->onloadstart = JS_NULL;
p->onloadend = JS_NULL;
p->onprogress = JS_NULL;
p->ontimeout = JS_NULL;
JS_SetOpaque(obj, p);
return obj;
}
static void xml_http_fire_event(XMLHTTPContext *ctx, GF_EventType evtType)
{
#if !defined(GPAC_DISABLE_SVG)
GF_DOM_Event xhr_evt;
if (!ctx->event_target)
return;
memset(&xhr_evt, 0, sizeof(GF_DOM_Event));
xhr_evt.type = evtType;
xhr_evt.target = ctx->event_target->ptr;
xhr_evt.target_type = ctx->event_target->ptr_type;
gf_sg_fire_dom_event(ctx->event_target, &xhr_evt, ctx->owning_graph, NULL);
#endif
}
static void xml_http_state_change(XMLHTTPContext *ctx)
{
gf_js_lock(ctx->c, GF_TRUE);
if (! JS_IsNull(ctx->onreadystatechange)) {
JSValue ret = JS_Call(ctx->c, ctx->onreadystatechange, ctx->_this, 0, NULL);
if (JS_IsException(ret))
js_dump_error(ctx->c);
JS_FreeValue(ctx->c, ret);
}
js_std_loop(ctx->c);
gf_js_lock(ctx->c, GF_FALSE);
#if !defined(GPAC_DISABLE_SVG) && !defined(GPAC_DISABLE_VRML)
GF_SceneGraph *scene;
GF_Node *n;
if (! ctx->owning_graph) return;
if (ctx->local_graph) return;
/*Flush BIFS eventOut events*/
scene = (GF_SceneGraph *)JS_GetContextOpaque(ctx->c);
/*this is a scene, we look for a node (if scene is used, this is DOM-based scripting not VRML*/
if (scene->__reserved_null == 0) return;
n = (GF_Node *)JS_GetContextOpaque(ctx->c);
gf_js_vrml_flush_event_out(n, (GF_ScriptPriv *)n->sgprivate->UserPrivate);
#endif
}
static JSValue xml_http_open(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
{
const char *val;
GF_JSAPIParam par;
XMLHTTPContext *ctx;
GF_SceneGraph *scene;
ctx = (XMLHTTPContext *) JS_GetOpaque(obj, xhrClass.class_id);
if (!ctx) return GF_JS_EXCEPTION(c);
/*reset*/
if (ctx->readyState) xml_http_reset(ctx, GF_FALSE);
if (argc<2) return GF_JS_EXCEPTION(c);
/*method is a string*/
if (!JS_CHECK_STRING(argv[0])) return GF_JS_EXCEPTION(c);
/*url is a string*/
if (!JS_CHECK_STRING(argv[1])) return GF_JS_EXCEPTION(c);
xml_http_reset(ctx, GF_FALSE);
val = JS_ToCString(c, argv[0]);
if (strcmp(val, "GET") && strcmp(val, "POST") && strcmp(val, "HEAD")
&& strcmp(val, "PUT") && strcmp(val, "DELETE") && strcmp(val, "OPTIONS") ) {
JS_FreeCString(c, val);
return GF_JS_EXCEPTION(c);
}
ctx->method = gf_strdup(val);
JS_FreeCString(c, val);
/*concatenate URL*/
scene = xml_get_scenegraph(c);
#ifndef GPAC_DISABLE_VRML
while (scene && scene->pOwningProto && scene->parent_scene) scene = scene->parent_scene;
#endif
par.uri.nb_params = 0;
val = JS_ToCString(c, argv[1]);
par.uri.url = (char *) val;
ctx->url = NULL;
if (!strncmp(val, "gpac://", 7)) {
ctx->url = gf_strdup(val+7);
}
else if (scene && scene->script_action) {
scene->script_action(scene->script_action_cbck, GF_JSAPI_OP_RESOLVE_URI, scene->RootNode, &par);
ctx->url = par.uri.url;
} else {
const char *par_url = jsf_get_script_filename(c);
ctx->url = gf_url_concatenate(par_url, val);
if (!ctx->url)
ctx->url = gf_strdup(val);
}
JS_FreeCString(c, val);
/*async defaults to true*/
ctx->async = GF_TRUE;
if (argc>2) {
val = NULL;
ctx->async = JS_ToBool(c, argv[2]) ? GF_TRUE : GF_FALSE;
if (argc>3) {
if (!JS_CHECK_STRING(argv[3])) return GF_JS_EXCEPTION(c);
/*TODO*/
if (argc>4) {
if (!JS_CHECK_STRING(argv[4])) return GF_JS_EXCEPTION(c);
val = JS_ToCString(c, argv[4]);
/*TODO*/
} else {
val = JS_ToCString(c, argv[3]);
}
}
JS_FreeCString(c, val);
}
/*OPEN success*/
ctx->readyState = XHR_READYSTATE_OPENED;
xml_http_state_change(ctx);
xml_http_fire_event(ctx, GF_EVENT_MEDIA_LOAD_START);
if (JS_IsFunction(c, ctx->onloadstart) ) {
return JS_Call(ctx->c, ctx->onloadstart, ctx->_this, 0, NULL);
}
return JS_TRUE;
}
static JSValue xml_http_set_header(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
{
const char *hdr, *val;
XMLHTTPContext *ctx = (XMLHTTPContext *) JS_GetOpaque(obj, xhrClass.class_id);
if (!ctx) return GF_JS_EXCEPTION(c);
if (ctx->readyState!=XHR_READYSTATE_OPENED) return GF_JS_EXCEPTION(c);
if (argc!=2) return GF_JS_EXCEPTION(c);
if (!JS_CHECK_STRING(argv[0])) return GF_JS_EXCEPTION(c);
if (!JS_CHECK_STRING(argv[1])) return GF_JS_EXCEPTION(c);
hdr = JS_ToCString(c, argv[0]);
val = JS_ToCString(c, argv[1]);
xml_http_append_send_header(ctx, (char *)hdr, (char *)val);
JS_FreeCString(c, hdr);
JS_FreeCString(c, val);
return JS_TRUE;
}
#ifndef GPAC_DISABLE_SVG
static void xml_http_sax_start(void *sax_cbck, const char *node_name, const char *name_space, const GF_XMLAttribute *attributes, u32 nb_attributes)
{
u32 i;
GF_DOMFullAttribute *prev = NULL;
GF_DOMFullNode *par;
XMLHTTPContext *ctx = (XMLHTTPContext *)sax_cbck;
GF_DOMFullNode *node = (GF_DOMFullNode *) gf_node_new(ctx->document, TAG_DOMFullNode);
node->name = gf_strdup(node_name);
for (i=0; i<nb_attributes; i++) {
/* special case for 'xml:id' to be parsed as an ID
NOTE: we do not test for the 'id' attribute because without DTD we are not sure that it's an ID */
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, ("[XHR] Fail to allocate DOM attribute\n"));
continue;
}
att->tag = TAG_DOM_ATT_any;
att->name = gf_strdup(attributes[i].name);
att->data_type = (u16) DOM_String_datatype;
att->data = gf_svg_create_attribute_value(att->data_type);
*((char **)att->data) = gf_strdup(attributes[i].value);
if (prev) prev->next = (GF_DOMAttribute*)att;
else node->attributes = (GF_DOMAttribute*)att;
prev = att;
}
}
par = (GF_DOMFullNode *)gf_list_last(ctx->node_stack);
gf_node_register((GF_Node*)node, (GF_Node*)par);
if (par) {
gf_node_list_add_child(&par->children, (GF_Node*)node);
} else {
gf_assert(!ctx->document->RootNode);
ctx->document->RootNode = (GF_Node*)node;
}
gf_list_add(ctx->node_stack, node);
}
static void xml_http_sax_end(void *sax_cbck, const char *node_name, const char *name_space)
{
XMLHTTPContext *ctx = (XMLHTTPContext *)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_http_sax_text(void *sax_cbck, const char *content, Bool is_cdata)
{
XMLHTTPContext *ctx = (XMLHTTPContext *)sax_cbck;
GF_DOMFullNode *par = (GF_DOMFullNode *)gf_list_last(ctx->node_stack);
if (par) {
u32 i, len;
GF_DOMText *txt;
/*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;
txt = gf_dom_add_text_node((GF_Node *)par, gf_strdup(content) );
txt->type = is_cdata ? GF_DOM_TEXT_CDATA : GF_DOM_TEXT_REGULAR;
}
}
#endif // GPAC_DISABLE_SVG
static void xml_http_terminate(XMLHTTPContext *ctx, GF_Err error)
{
/*if we get here, destroy downloader*/
if (ctx->sess && error && (error!=GF_URL_ERROR) && (error!=GF_URL_REMOVED)) {
gf_dm_sess_del(ctx->sess);
ctx->sess = NULL;
}
/*but stay in loaded mode*/
ctx->readyState = XHR_READYSTATE_DONE;
xml_http_state_change(ctx);
xml_http_fire_event(ctx, GF_EVENT_LOAD);
xml_http_fire_event(ctx, GF_EVENT_MEDIA_LOAD_DONE);
if (JS_IsFunction(ctx->c, ctx->onload)) {
JSValue rval = JS_Call(ctx->c, ctx->onload, ctx->_this, 0, NULL);
if (JS_IsException(rval)) js_dump_error(ctx->c);
JS_FreeValue(ctx->c, rval);
}
if (JS_IsFunction(ctx->c, ctx->onloadend)) {
JSValue rval = JS_Call(ctx->c, ctx->onloadend, ctx->_this, 0, NULL);
if (JS_IsException(rval)) js_dump_error(ctx->c);
JS_FreeValue(ctx->c, rval);
}
if (error && JS_IsFunction(ctx->c, ctx->onerror) ) {
JSValue rval = JS_Call(ctx->c, ctx->onerror, ctx->_this, 0, NULL);
if (JS_IsException(rval)) js_dump_error(ctx->c);
JS_FreeValue(ctx->c, rval);
}
}
static void xml_http_on_data(void *usr_cbk, GF_NETIO_Parameter *parameter)
{
Bool locked = GF_FALSE;
XMLHTTPContext *ctx = (XMLHTTPContext *)usr_cbk;
/*make sure we can grab JS and the session is not destroyed*/
while (ctx->sess) {
if (gf_js_try_lock(ctx->c) ) {
locked = GF_TRUE;
break;
}
gf_sleep(1);
}
/*if session not set, we've had an abort*/
if (!ctx->sess && !ctx->isFile) {
if (locked)
gf_js_lock(ctx->c, GF_FALSE);
return;
}
if (!ctx->isFile) {
gf_assert( locked );
gf_js_lock(ctx->c, GF_FALSE);
locked = GF_FALSE;
}
switch (parameter->msg_type) {
case GF_NETIO_SETUP:
/*nothing to do*/
goto exit;
case GF_NETIO_CONNECTED:
/*nothing to do*/
goto exit;
case GF_NETIO_WAIT_FOR_REPLY:
/*reset send() state (data, current header) and prepare recv headers*/
xml_http_reset_partial(ctx);
// ctx->readyState = XHR_READYSTATE_HEADERS_RECEIVED;
// xml_http_state_change(ctx);
xml_http_fire_event(ctx, GF_EVENT_MEDIA_PROGRESS);
if (JS_IsFunction(ctx->c, ctx->onprogress) ) {
JSValue rval = JS_Call(ctx->c, ctx->onprogress, ctx->_this, 0, NULL);
if (JS_IsException(rval)) js_dump_error(ctx->c);
JS_FreeValue(ctx->c, rval);
}
goto exit;
/*this is signaled sent AFTER headers*/
case GF_NETIO_PARSE_REPLY:
ctx->html_status = parameter->reply;
if (parameter->value) {
ctx->statusText = gf_strdup(parameter->value);
}
#ifndef GPAC_DISABLE_SVG
/*prepare SAX parser*/
if (ctx->sess && (ctx->responseType == XHR_RESPONSETYPE_SAX)) {
const char *type = gf_dm_sess_get_header(ctx->sess, "Content-Type");
if (!strncmp(type, "application/xml", 15)
|| !strncmp(type, "text/xml", 8)
|| strstr(type, "+xml")
|| strstr(type, "/xml")
) {
gf_assert(!ctx->sax);
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();
ctx->document = gf_sg_new();
/*mark this doc as "nomade", and let it leave until all references to it are destroyed*/
ctx->document->reference_count = 1;
}
}
#endif
ctx->readyState = XHR_READYSTATE_HEADERS_RECEIVED;
xml_http_state_change(ctx);
xml_http_fire_event(ctx, GF_EVENT_MEDIA_PROGRESS);
if (JS_IsFunction(ctx->c, ctx->onprogress) ) {
JSValue rval = JS_Call(ctx->c, ctx->onprogress, ctx->_this, 0, NULL);
if (JS_IsException(rval)) js_dump_error(ctx->c);
JS_FreeValue(ctx->c, rval);
}
goto exit;
case GF_NETIO_GET_METHOD:
parameter->name = ctx->method;
goto exit;
case GF_NETIO_GET_HEADER:
if (ctx->headers && ctx->headers[2*ctx->cur_header]) {
parameter->name = ctx->headers[2*ctx->cur_header];
parameter->value = ctx->headers[2*ctx->cur_header+1];
ctx->cur_header++;
}
goto exit;
case GF_NETIO_GET_CONTENT:
if (ctx->data) {
parameter->data = ctx->data;
parameter->size = ctx->size;
}
goto exit;
case GF_NETIO_DATA_EXCHANGE:
if (ctx->readyState == XHR_READYSTATE_HEADERS_RECEIVED) {
ctx->readyState = XHR_READYSTATE_LOADING;
xml_http_state_change(ctx);
}
if (parameter->data && parameter->size) {
if (ctx->sax) {
GF_Err e;
if (!ctx->size) e = gf_xml_sax_init(ctx->sax, (unsigned char *) parameter->data);
else e = gf_xml_sax_parse(ctx->sax, parameter->data);
if (e) {
gf_xml_sax_del(ctx->sax);
ctx->sax = NULL;
}
goto exit;
}
/*detach arraybuffer if any*/
if (!JS_IsUndefined(ctx->arraybuffer)) {
JS_FreeValue(ctx->c, ctx->arraybuffer);
ctx->arraybuffer = JS_UNDEFINED;
}
if (ctx->responseType!=XHR_RESPONSETYPE_PUSH) {
ctx->data = (char *)gf_realloc(ctx->data, sizeof(char)*(ctx->size+parameter->size+1));
memcpy(ctx->data + ctx->size, parameter->data, sizeof(char)*parameter->size);
ctx->size += parameter->size;
ctx->data[ctx->size] = 0;
}
if (JS_IsFunction(ctx->c, ctx->onprogress) ) {
JSValue prog_evt = JS_NewObject(ctx->c);
JSValue buffer_ab=JS_UNDEFINED;
u32 bps=0;
u64 tot_size=0;
gf_dm_sess_get_stats(ctx->sess, NULL, NULL, &tot_size, NULL, &bps, NULL);
JS_SetPropertyStr(ctx->c, prog_evt, "lengthComputable", JS_NewBool(ctx->c, tot_size ? 1 : 0));
JS_SetPropertyStr(ctx->c, prog_evt, "loaded", JS_NewInt64(ctx->c, ctx->size));
JS_SetPropertyStr(ctx->c, prog_evt, "total", JS_NewInt64(ctx->c, tot_size));
JS_SetPropertyStr(ctx->c, prog_evt, "bps", JS_NewInt64(ctx->c, bps*8));
if (ctx->responseType==XHR_RESPONSETYPE_PUSH) {
buffer_ab = JS_NewArrayBuffer(ctx->c, (u8 *) parameter->data, parameter->size, NULL, ctx, 0/*1*/);
JS_SetPropertyStr(ctx->c, prog_evt, "buffer", buffer_ab);
}
JSValue rval = JS_Call(ctx->c, ctx->onprogress, ctx->_this, 1, &prog_evt);
if (JS_IsException(rval)) {
js_dump_error(ctx->c);
} else if (JS_IsNumber(rval)) {
s32 throttle;
JS_ToInt32(ctx->c, &throttle, rval);
if (throttle>=0)
gf_dm_sess_set_max_rate(ctx->sess, (u32) throttle);
}
JS_FreeValue(ctx->c, rval);
if (ctx->responseType==XHR_RESPONSETYPE_PUSH) {
JS_DetachArrayBuffer(ctx->c, buffer_ab);
}
JS_FreeValue(ctx->c, prog_evt);
}
}
goto exit;
case GF_NETIO_DATA_TRANSFERED:
/* No return, go till the end of the function */
break;
case GF_NETIO_ICY_META:
goto exit;
case GF_NETIO_DISCONNECTED:
goto exit;
case GF_NETIO_STATE_ERROR:
ctx->ret_code = parameter->error;
/* No return, go till the end of the function */
GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[XmlHttpRequest] Download error: %s\n", gf_error_to_string(parameter->error)));
break;
case GF_NETIO_REQUEST_SESSION:
case GF_NETIO_CANCEL_STREAM:
parameter->error = GF_NOT_SUPPORTED;
goto exit;
}
if (ctx->async) {
xml_http_terminate(ctx, parameter->error);
}
exit:
if (locked) {
gf_js_lock(ctx->c, GF_FALSE);
}
}
static GF_Err xml_http_process_local(XMLHTTPContext *ctx)
{
/* For XML Http Requests to files, we fake the processing by calling the HTTP callbacks */
GF_NETIO_Parameter par;
u64 fsize;
FILE *responseFile;
/*opera-style local host*/
if (!strnicmp(ctx->url, "file://localhost", 16)) responseFile = gf_fopen(ctx->url+16, "rb");
/*regular-style local host*/
else if (!strnicmp(ctx->url, "file://", 7)) responseFile = gf_fopen(ctx->url+7, "rb");
/* other types: e.g. "C:\" */
else responseFile = gf_fopen(ctx->url, "rb");
if (!responseFile) {
ctx->html_status = 404;
GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[XmlHttpRequest] cannot open local file %s\n", ctx->url));
xml_http_fire_event(ctx, GF_EVENT_ERROR);
if (JS_IsFunction(ctx->c, ctx->onerror) ) {
JSValue rval = JS_Call(ctx->c, ctx->onerror, ctx->_this, 0, NULL);
if (JS_IsException(rval)) js_dump_error(ctx->c);
JS_FreeValue(ctx->c, rval);
}
return GF_BAD_PARAM;
}
ctx->isFile = GF_TRUE;
par.msg_type = GF_NETIO_WAIT_FOR_REPLY;
xml_http_on_data(ctx, &par);
fsize = gf_fsize(responseFile);
ctx->html_status = 200;
ctx->data = (char *)gf_malloc(sizeof(char)*(size_t)(fsize+1));
fsize = gf_fread(ctx->data, (size_t)fsize, responseFile);
gf_fclose(responseFile);
ctx->data[fsize] = 0;
ctx->size = (u32)fsize;
#ifndef GPAC_DISABLE_SVG
if ((ctx->responseType == XHR_RESPONSETYPE_SAX)
|| (ctx->responseType == XHR_RESPONSETYPE_DOCUMENT)
) {
gf_assert(!ctx->sax);
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();
ctx->document = gf_sg_new();
/*mark this doc as "nomade", and let it leave until all references to it are destroyed*/
ctx->document->reference_count = 1;
}
#endif
if (ctx->sax) {
memset(&par, 0, sizeof(GF_NETIO_Parameter));
par.msg_type = GF_NETIO_DATA_EXCHANGE;
par.data = ctx->data;
par.size = ctx->size;
ctx->size = 0;
xml_http_on_data(ctx, &par);
ctx->size = par.size;
}
memset(&par, 0, sizeof(GF_NETIO_Parameter));
par.msg_type = GF_NETIO_DATA_TRANSFERED;
xml_http_on_data(ctx, &par);
if (!ctx->async) {
xml_http_terminate(ctx, GF_OK);
}
return GF_OK;
}
#ifdef GPAC_USE_DOWNLOADER
GF_DownloadManager *jsf_get_download_manager(JSContext *c);
#endif
static JSValue xml_http_send(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
{
GF_Err e;
GF_JSAPIParam par;
GF_SceneGraph *scene;
const char *data = NULL;
u32 data_size = 0;
XMLHTTPContext *ctx = (XMLHTTPContext *) JS_GetOpaque(obj, xhrClass.class_id);
if (!ctx) return GF_JS_EXCEPTION(c);
if (ctx->readyState!=XHR_READYSTATE_OPENED) return GF_JS_EXCEPTION(c);
if (ctx->in_send) return GF_JS_EXCEPTION(c);
ctx->in_send = GF_TRUE;
scene = xml_get_scenegraph(c);
if (scene) {
par.dnld_man = NULL;
if (scene->script_action)
scene->script_action(scene->script_action_cbck, GF_JSAPI_OP_GET_DOWNLOAD_MANAGER, NULL, &par);
} else {
par.dnld_man = jsf_get_download_manager(c);
}
#ifndef GPAC_DISABLE_NETWORKING
if (!par.dnld_man && !ctx->sess) return GF_JS_EXCEPTION(c);
#endif
if (argc) {
if (JS_IsNull(argv[0])) {
} else if (JS_IsArrayBuffer(c, argv[0])) {
size_t asize;
data = JS_GetArrayBuffer(c, &asize, argv[0] );
if (data) data_size = (u32) asize;
} else if (JS_IsObject(argv[0])) {
/*NOT SUPPORTED YET, we must serialize the sg*/
return GF_JS_EXCEPTION(c);
} else {
if (!JS_CHECK_STRING(argv[0])) return GF_JS_EXCEPTION(c);
data = JS_ToCString(c, argv[0]);
data_size = (u32) strlen(data);
}
}
/*reset previous text*/
xml_http_del_data(ctx);
ctx->data = data ? gf_strdup(data) : NULL;
ctx->size = data_size;
JS_FreeCString(c, data);
if (!strncmp(ctx->url, "http://", 7) || !strncmp(ctx->url, "https://", 8)) {
u32 flags = GF_NETIO_SESSION_NOTIFY_DATA;
//disable sync XHR in emscripten
#ifdef GPAC_CONFIG_EMSCRIPTEN
return GF_JS_EXCEPTION(c);
#else
if (!ctx->async)
flags |= GF_NETIO_SESSION_NOT_THREADED;
#endif
if (ctx->cache != XHR_CACHETYPE_NORMAL) {
if (ctx->cache == XHR_CACHETYPE_NONE) {
flags |= GF_NETIO_SESSION_NOT_CACHED;
}
if (ctx->cache == XHR_CACHETYPE_MEMORY) {
flags |= GF_NETIO_SESSION_MEMORY_CACHE;
}
}
//allow connetion reuse
if (ctx->sess) {
e = gf_dm_sess_setup_from_url(ctx->sess, ctx->url, GF_FALSE);
if (e) {
gf_dm_sess_del(ctx->sess);
ctx->sess = NULL;
} else {
GF_NetIOStatus status;
gf_dm_sess_get_stats(ctx->sess, NULL, NULL, 0, 0, 0, &status);
assert(status<=GF_NETIO_CONNECTED);
}
}
if (!ctx->sess) {
ctx->sess = gf_dm_sess_new(par.dnld_man, ctx->url, flags, xml_http_on_data, ctx, &e);
if (!ctx->sess) return GF_JS_EXCEPTION(c);
}
/*start our download (whether the session is threaded or not)*/
e = gf_dm_sess_process(ctx->sess);
if (e!=GF_OK) {
GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[XmlHttpRequest] Error processing %s: %s\n", ctx->url, gf_error_to_string(e) ));
}
if (!ctx->async) {
xml_http_terminate(ctx, e);
}
} else {
e = xml_http_process_local(ctx);
if (e!=GF_OK) {
GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[XmlHttpRequest] Error processing %s: %s\n", ctx->url, gf_error_to_string(e) ));
}
}
return JS_TRUE;
}
static JSValue xml_http_abort(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
{
GF_DownloadSession *sess;
XMLHTTPContext *ctx = JS_GetOpaque(obj, xhrClass.class_id);
if (!ctx) return GF_JS_EXCEPTION(c);
sess = ctx->sess;
ctx->sess = NULL;
if (sess) {
//abort first, so that on HTTP/2 this results in RST_STREAM
gf_dm_sess_abort(sess);
}
xml_http_fire_event(ctx, GF_EVENT_ABORT);
xml_http_reset(ctx, GF_TRUE);
if (JS_IsFunction(c, ctx->onabort)) {
return JS_Call(ctx->c, ctx->onabort, ctx->_this, 0, NULL);
}
return JS_TRUE;
}
static JSValue xml_http_get_all_headers(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
{
u32 nb_hdr;
char *szVal = NULL;
const char *hdr_name, *hdr_val;
JSValue res;
XMLHTTPContext *ctx = JS_GetOpaque(obj, xhrClass.class_id);
if (!ctx) return GF_JS_EXCEPTION(c);
/*must be received or loaded*/
if (ctx->readyState<XHR_READYSTATE_HEADERS_RECEIVED) return GF_JS_EXCEPTION(c);
nb_hdr = 0;
while (1) {
GF_Err e = gf_dm_sess_enum_headers(ctx->sess, &nb_hdr, &hdr_name, &hdr_val);
if (e) break;
if (szVal) gf_dynstrcat(&szVal, "\r\n", NULL);
gf_dynstrcat(&szVal, hdr_name, NULL);
gf_dynstrcat(&szVal, hdr_val, ": ");
}
if (!szVal) {
return JS_NULL;
}
res = JS_NewString(c, szVal);
gf_free(szVal);
return res;
}
static JSValue xml_http_get_header(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
{
const char *hdr;
XMLHTTPContext *ctx = JS_GetOpaque(obj, xhrClass.class_id);
if (!ctx) return GF_JS_EXCEPTION(c);
if (!JS_CHECK_STRING(argv[0])) return GF_JS_EXCEPTION(c);
/*must be received or loaded*/
if (ctx->readyState<XHR_READYSTATE_HEADERS_RECEIVED) return GF_JS_EXCEPTION(c);
hdr = JS_ToCString(c, argv[0]);
const char *hdr_val = gf_dm_sess_get_header(ctx->sess, hdr);
JS_FreeCString(c, hdr);
if (!hdr_val) return JS_NULL;
JSValue res = JS_NewString(c, hdr_val);
return res;
}
#ifndef GPAC_DISABLE_SVG
static GF_Err xml_http_load_dom(XMLHTTPContext *ctx)
{
GF_Err e;
GF_DOMParser *parser = gf_xml_dom_new();
e = gf_xml_dom_parse_string(parser, ctx->data);
if (!e) {
e = gf_sg_init_from_xml_node(ctx->document, gf_xml_dom_get_root(parser));
}
gf_xml_dom_del(parser);
return e;
}
#endif //GPAC_DISABLE_SVG
static JSValue xml_http_overrideMimeType(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
{
const char *mime;
XMLHTTPContext *ctx = JS_GetOpaque(obj, xhrClass.class_id);
if (!ctx || !argc) return GF_JS_EXCEPTION(c);
if (!JS_CHECK_STRING(argv[0])) return GF_JS_EXCEPTION(c);
mime = JS_ToCString(c, argv[0]);
if (ctx->mime) gf_free(ctx->mime);
ctx->mime = gf_strdup(mime);
JS_FreeCString(c, mime);
return JS_TRUE;
}
static void xml_http_array_buffer_free(JSRuntime *rt, void *opaque, void *ptr)
{
//might already been destroyed !
// XMLHTTPContext *ctx = (XMLHTTPContext *)opaque;
gf_free(ptr);
}
static JSValue xml_http_getProperty(JSContext *c, JSValueConst obj, int magic)
{
XMLHTTPContext *ctx = JS_GetOpaque(obj, xhrClass.class_id);
if (!ctx) return GF_JS_EXCEPTION(c);
switch (magic) {
case XHR_ONABORT: return JS_DupValue(c, ctx->onabort);
case XHR_ONERROR: return JS_DupValue(c, ctx->onerror);
case XHR_ONLOAD: return JS_DupValue(c, ctx->onload);
case XHR_ONLOADSTART: return JS_DupValue(c, ctx->onloadstart);
case XHR_ONLOADEND: return JS_DupValue(c, ctx->onloadend);
case XHR_ONPROGRESS: return JS_DupValue(c, ctx->onprogress);
case XHR_ONREADYSTATECHANGE: return JS_DupValue(c, ctx->onreadystatechange);
case XHR_ONTIMEOUT: return JS_DupValue(c, ctx->ontimeout);
case XHR_READYSTATE: return JS_NewInt32(c, ctx->readyState);
case XHR_RESPONSETEXT:
if (ctx->readyState<XHR_READYSTATE_LOADING) return JS_NULL;
if (ctx->data) {
return JS_NewString(c, ctx->data);
} else {
return JS_NULL;
}
case XHR_RESPONSEXML:
if (ctx->readyState<XHR_READYSTATE_LOADING) return JS_NULL;
#ifndef GPAC_DISABLE_SVG
if (ctx->data) {
if (!ctx->document) {
ctx->document = gf_sg_new();
/*mark this doc as "nomade", and let it leave until all references to it are destroyed*/
ctx->document->reference_count = 1;
xml_http_load_dom(ctx);
}
if (!ctx->js_dom_loaded) {
dom_js_load(ctx->document, c);
ctx->js_dom_loaded = GF_TRUE;
}
return dom_document_construct_external(c, ctx->document);
} else {
return JS_NULL;
}
#else
return js_throw_err_msg(c, GF_NOT_SUPPORTED, "DOM support not included in buil");
#endif
case XHR_RESPONSE:
if (ctx->readyState<XHR_READYSTATE_LOADING) return JS_NULL;
if (ctx->data) {
switch(ctx->responseType)
{
case XHR_RESPONSETYPE_NONE:
case XHR_RESPONSETYPE_TEXT:
return JS_NewString(c, ctx->data);
case XHR_RESPONSETYPE_ARRAYBUFFER:
if (JS_IsUndefined(ctx->arraybuffer)) {
ctx->arraybuffer = JS_NewArrayBuffer(c, ctx->data, ctx->size, xml_http_array_buffer_free, ctx, 0/*1*/);
}
return JS_DupValue(c, ctx->arraybuffer);
case XHR_RESPONSETYPE_DOCUMENT:
#ifndef GPAC_DISABLE_SVG
if (ctx->data) {
if (!ctx->document) {
ctx->document = gf_sg_new();
/*mark this doc as "nomade", and let it leave until all references to it are destroyed*/
ctx->document->reference_count = 1;
xml_http_load_dom(ctx);
}
if (!ctx->js_dom_loaded) {
dom_js_load(ctx->document, c);
ctx->js_dom_loaded = GF_TRUE;
}
return dom_document_construct_external(c, ctx->document);
}
return JS_NULL;
#else
return js_throw_err_msg(c, GF_NOT_SUPPORTED, "DOM support not included in buil");
#endif
case XHR_RESPONSETYPE_JSON:
return JS_ParseJSON(c, ctx->data, ctx->size, "responseJSON");
case XHR_RESPONSETYPE_PUSH:
return GF_JS_EXCEPTION(c);
default:
/*other types not supported */
break;
}
}
return JS_NULL;
case XHR_STATUS:
return JS_NewInt32(c, ctx->html_status);
case XHR_STATUSTEXT:
if (ctx->statusText) {
return JS_NewString(c, ctx->statusText);
}
return JS_NULL;
case XHR_TIMEOUT:
return JS_NewInt32(c, ctx->timeout);
case XHR_WITHCREDENTIALS:
return ctx->withCredentials ? JS_TRUE : JS_FALSE;
case XHR_UPLOAD:
/* TODO */
return GF_JS_EXCEPTION(c);
case XHR_RESPONSETYPE:
switch (ctx->responseType) {
case XHR_RESPONSETYPE_NONE: return JS_NewString(c, "");
case XHR_RESPONSETYPE_ARRAYBUFFER: return JS_NewString(c, "arraybuffer");
case XHR_RESPONSETYPE_BLOB: return JS_NewString(c, "blob");
case XHR_RESPONSETYPE_DOCUMENT: return JS_NewString(c, "document");
case XHR_RESPONSETYPE_SAX: return JS_NewString(c, "sax");
case XHR_RESPONSETYPE_JSON: return JS_NewString(c, "json");
case XHR_RESPONSETYPE_TEXT: return JS_NewString(c, "text");
case XHR_RESPONSETYPE_PUSH: return JS_NewString(c, "push");
}
return JS_NULL;
case XHR_STATIC_UNSENT:
return JS_NewInt32(c, XHR_READYSTATE_UNSENT);
case XHR_STATIC_OPENED:
return JS_NewInt32(c, XHR_READYSTATE_OPENED);
case XHR_STATIC_HEADERS_RECEIVED:
return JS_NewInt32(c, XHR_READYSTATE_HEADERS_RECEIVED);
case XHR_STATIC_LOADING:
return JS_NewInt32(c, XHR_READYSTATE_LOADING);
case XHR_STATIC_DONE:
return JS_NewInt32(c, XHR_READYSTATE_DONE);
case XHR_CACHE:
switch (ctx->cache) {
case XHR_CACHETYPE_NORMAL: return JS_NewString(c, "normal");
case XHR_CACHETYPE_NONE: return JS_NewString(c, "none");
case XHR_CACHETYPE_MEMORY: return JS_NewString(c, "memory");
}
return JS_NULL;
default:
break;
}
return JS_UNDEFINED;
}
static JSValue xml_http_setProperty(JSContext *c, JSValueConst obj, JSValueConst value, int magic)
{
XMLHTTPContext *ctx = JS_GetOpaque(obj, xhrClass.class_id);
if (!ctx) return GF_JS_EXCEPTION(c);
#define SET_CBK(_sym) \
if (JS_IsFunction(c, value) || JS_IsUndefined(value) || JS_IsNull(value)) {\
JS_FreeValue(c, ctx->_sym);\
ctx->_sym = JS_DupValue(c, value);\
return JS_TRUE;\
}\
return GF_JS_EXCEPTION(c);\
switch (magic) {
case XHR_ONERROR:
SET_CBK(onerror)
case XHR_ONABORT:
SET_CBK(onabort)
case XHR_ONLOAD:
SET_CBK(onload)
case XHR_ONLOADSTART:
SET_CBK(onloadstart)
case XHR_ONLOADEND:
SET_CBK(onloadend)
case XHR_ONPROGRESS:
SET_CBK(onprogress)
case XHR_ONREADYSTATECHANGE:
SET_CBK(onreadystatechange)
case XHR_ONTIMEOUT:
SET_CBK(ontimeout)
case XHR_TIMEOUT:
if (JS_ToInt32(c, &ctx->timeout, value)) return GF_JS_EXCEPTION(c);
return JS_TRUE;
case XHR_WITHCREDENTIALS:
ctx->withCredentials = JS_ToBool(c, value) ? GF_TRUE : GF_FALSE;
return JS_TRUE;
case XHR_RESPONSETYPE:
{
const char *str = JS_ToCString(c, value);
if (!str || !strcmp(str, "")) {
ctx->responseType = XHR_RESPONSETYPE_NONE;
} else if (!strcmp(str, "arraybuffer")) {
ctx->responseType = XHR_RESPONSETYPE_ARRAYBUFFER;
} else if (!strcmp(str, "blob")) {
ctx->responseType = XHR_RESPONSETYPE_BLOB;
} else if (!strcmp(str, "document")) {
ctx->responseType = XHR_RESPONSETYPE_DOCUMENT;
} else if (!strcmp(str, "json")) {
ctx->responseType = XHR_RESPONSETYPE_JSON;
} else if (!strcmp(str, "text")) {
ctx->responseType = XHR_RESPONSETYPE_TEXT;
} else if (!strcmp(str, "sax")) {
ctx->responseType = XHR_RESPONSETYPE_SAX;
} else if (!strcmp(str, "push")) {
ctx->responseType = XHR_RESPONSETYPE_PUSH;
}
JS_FreeCString(c, str);
return JS_TRUE;
}
case XHR_CACHE:
{
const char *str = JS_ToCString(c, value);
if (!str) return GF_JS_EXCEPTION(c);
if (!strcmp(str, "normal")) {
ctx->cache = XHR_CACHETYPE_NORMAL;
} else if (!strcmp(str, "none")) {
ctx->cache = XHR_CACHETYPE_NONE;
} else if (!strcmp(str, "memory")) {
ctx->cache = XHR_CACHETYPE_MEMORY;
}
JS_FreeCString(c, str);
return JS_TRUE;
}
/*all other properties are read-only*/
default:
break;
}
return JS_FALSE;
}
static const JSCFunctionListEntry xhr_Funcs[] =
{
JS_CGETSET_MAGIC_DEF("onabort", xml_http_getProperty, xml_http_setProperty, XHR_ONABORT),
JS_CGETSET_MAGIC_DEF("onerror", xml_http_getProperty, xml_http_setProperty, XHR_ONERROR),
JS_CGETSET_MAGIC_DEF("onload", xml_http_getProperty, xml_http_setProperty, XHR_ONLOAD),
JS_CGETSET_MAGIC_DEF("onloadend", xml_http_getProperty, xml_http_setProperty, XHR_ONLOADEND),
JS_CGETSET_MAGIC_DEF("onloadstart", xml_http_getProperty, xml_http_setProperty, XHR_ONLOADSTART),
JS_CGETSET_MAGIC_DEF("onprogress", xml_http_getProperty, xml_http_setProperty, XHR_ONPROGRESS),
JS_CGETSET_MAGIC_DEF("onreadystatechange", xml_http_getProperty, xml_http_setProperty, XHR_ONREADYSTATECHANGE),
JS_CGETSET_MAGIC_DEF("ontimeout", xml_http_getProperty, xml_http_setProperty, XHR_ONTIMEOUT),
JS_CGETSET_MAGIC_DEF("readyState", xml_http_getProperty, NULL, XHR_READYSTATE),
JS_CGETSET_MAGIC_DEF("response", xml_http_getProperty, NULL, XHR_RESPONSE),
JS_CGETSET_MAGIC_DEF("responseType", xml_http_getProperty, xml_http_setProperty, XHR_RESPONSETYPE),
JS_CGETSET_MAGIC_DEF("responseText", xml_http_getProperty, NULL, XHR_RESPONSETEXT),
JS_CGETSET_MAGIC_DEF("responseXML", xml_http_getProperty, NULL, XHR_RESPONSEXML),
JS_CGETSET_MAGIC_DEF("status", xml_http_getProperty, NULL, XHR_STATUS),
JS_CGETSET_MAGIC_DEF("statusText", xml_http_getProperty, NULL, XHR_STATUSTEXT),
JS_CGETSET_MAGIC_DEF("timeout", xml_http_getProperty, xml_http_setProperty, XHR_TIMEOUT),
JS_CGETSET_MAGIC_DEF("upload", xml_http_getProperty, NULL, XHR_UPLOAD),
JS_CGETSET_MAGIC_DEF("withCredentials", xml_http_getProperty, xml_http_setProperty, XHR_WITHCREDENTIALS),
JS_CGETSET_MAGIC_DEF("cache", xml_http_getProperty, xml_http_setProperty, XHR_CACHE),
JS_CFUNC_DEF("abort", 0, xml_http_abort),
JS_CFUNC_DEF("getAllResponseHeaders", 0, xml_http_get_all_headers),
JS_CFUNC_DEF("getResponseHeader", 1, xml_http_get_header),
JS_CFUNC_DEF("open", 2, xml_http_open),
JS_CFUNC_DEF("overrideMimeType", 1, xml_http_overrideMimeType),
JS_CFUNC_DEF("send", 0, xml_http_send),
JS_CFUNC_DEF("setRequestHeader", 2, xml_http_set_header),
/*eventTarget interface*/
JS_DOM3_EVENT_TARGET_INTERFACE
};
static void xml_http_gc_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func)
{
XMLHTTPContext *ctx = JS_GetOpaque(val, xhrClass.class_id);
if (!ctx) return;
JS_MarkValue(rt, ctx->onabort, mark_func);
JS_MarkValue(rt, ctx->onerror, mark_func);
JS_MarkValue(rt, ctx->onload, mark_func);
JS_MarkValue(rt, ctx->onloadend, mark_func);
JS_MarkValue(rt, ctx->onloadstart, mark_func);
JS_MarkValue(rt, ctx->onprogress, mark_func);
JS_MarkValue(rt, ctx->onreadystatechange, mark_func);
JS_MarkValue(rt, ctx->ontimeout, mark_func);
if (!JS_IsUndefined(ctx->arraybuffer))
JS_MarkValue(rt, ctx->arraybuffer, mark_func);
}
static JSValue xhr_load_class(JSContext *c)
{
if (! xhrClass.class_id) {
JS_NewClassID(&xhrClass.class_id);
xhrClass.class.class_name = "XMLHttpRequest";
xhrClass.class.finalizer = xml_http_finalize;
xhrClass.class.gc_mark = xml_http_gc_mark;
JS_NewClass(JS_GetRuntime(c), xhrClass.class_id, &xhrClass.class);
}
JSValue proto = JS_NewObjectClass(c, xhrClass.class_id);
JS_SetPropertyFunctionList(c, proto, xhr_Funcs, countof(xhr_Funcs));
JS_SetClassProto(c, xhrClass.class_id, proto);
JS_SetPropertyStr(c, proto, "UNSENT", JS_NewInt32(c, XHR_STATIC_UNSENT));
JS_SetPropertyStr(c, proto, "OPENED", JS_NewInt32(c, XHR_STATIC_OPENED));
JS_SetPropertyStr(c, proto, "HEADERS_RECEIVED", JS_NewInt32(c, XHR_STATIC_HEADERS_RECEIVED));
JS_SetPropertyStr(c, proto, "LOADING", JS_NewInt32(c, XHR_STATIC_LOADING));
JS_SetPropertyStr(c, proto, "DONE", JS_NewInt32(c, XHR_STATIC_DONE));
return JS_NewCFunction2(c, xml_http_constructor, "XMLHttpRequest", 1, JS_CFUNC_constructor, 0);
}
void qjs_module_init_xhr_global(JSContext *c, JSValue global)
{
if (c) {
JSValue ctor = xhr_load_class(c);
JS_SetPropertyStr(c, global, "XMLHttpRequest", ctor);
}
}
static int js_xhr_load_module(JSContext *c, JSModuleDef *m)
{
JSValue global = JS_GetGlobalObject(c);
JSValue ctor = xhr_load_class(c);
JS_FreeValue(c, global);
JS_SetModuleExport(c, m, "XMLHttpRequest", ctor);
return 0;
}
void qjs_module_init_xhr(JSContext *ctx)
{
JSModuleDef *m;
m = JS_NewCModule(ctx, "xhr", js_xhr_load_module);
if (!m) return;
JS_AddModuleExport(ctx, m, "XMLHttpRequest");
#ifdef GPAC_ENABLE_COVERAGE
if (gf_sys_is_cov_mode()) {
qjs_module_init_xhr_global(NULL, JS_TRUE);
#if !defined(GPAC_DISABLE_SVG) && defined(GPAC_USE_DOWNLOADER)
xhr_get_event_target(NULL, JS_TRUE, NULL, NULL);
#endif
}
#endif
return;
}
#else
#include "../quickjs/quickjs.h"
void qjs_module_init_xhr(JSContext *ctx)
{
}
#endif // defined(GPAC_HAS_QJS) && defined(GPAC_USE_DOWNLOADER)