summaryrefslogtreecommitdiff
path: root/content/handlers/html/html_script.c
diff options
context:
space:
mode:
authorVincent Sanders <vince@netsurf-browser.org>2018-05-10 11:34:26 +0100
committerVincent Sanders <vince@kyllikki.org>2018-05-10 13:37:02 +0100
commit2a03ea30490892ac52b3da325ab78e1aa888f83e (patch)
treed041e4a2aab3b224ad41612d47ea2119895e27ac /content/handlers/html/html_script.c
parent1b892391d7859398c212b9fda5b532308fa6e8fd (diff)
downloadnetsurf-2a03ea30490892ac52b3da325ab78e1aa888f83e.tar.gz
netsurf-2a03ea30490892ac52b3da325ab78e1aa888f83e.tar.bz2
move html and text content handlers where they belong
Diffstat (limited to 'content/handlers/html/html_script.c')
-rw-r--r--content/handlers/html/html_script.c604
1 files changed, 604 insertions, 0 deletions
diff --git a/content/handlers/html/html_script.c b/content/handlers/html/html_script.c
new file mode 100644
index 000000000..e18a0caa0
--- /dev/null
+++ b/content/handlers/html/html_script.c
@@ -0,0 +1,604 @@
+/*
+ * Copyright 2012 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf, http://www.netsurf-browser.org/
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * implementation of content handling for text/html scripts.
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <strings.h>
+#include <stdlib.h>
+
+#include "utils/config.h"
+#include "utils/corestrings.h"
+#include "utils/log.h"
+#include "utils/messages.h"
+#include "netsurf/content.h"
+#include "javascript/js.h"
+#include "content/content_protected.h"
+#include "content/fetch.h"
+#include "content/hlcache.h"
+
+#include "html/html_internal.h"
+
+typedef bool (script_handler_t)(struct jscontext *jscontext, const char *data, size_t size) ;
+
+
+static script_handler_t *select_script_handler(content_type ctype)
+{
+ if (ctype == CONTENT_JS) {
+ return js_exec;
+ }
+ return NULL;
+}
+
+
+/* exported internal interface documented in html/html_internal.h */
+nserror html_script_exec(html_content *c)
+{
+ unsigned int i;
+ struct html_script *s;
+ script_handler_t *script_handler;
+
+ if (c->jscontext == NULL) {
+ return NSERROR_BAD_PARAMETER;
+ }
+
+ for (i = 0, s = c->scripts; i != c->scripts_count; i++, s++) {
+ if (s->already_started) {
+ continue;
+ }
+
+ if ((s->type == HTML_SCRIPT_ASYNC) ||
+ (s->type == HTML_SCRIPT_DEFER)) {
+ /* ensure script content is present */
+ if (s->data.handle == NULL)
+ continue;
+
+ /* ensure script content fetch status is not an error */
+ if (content_get_status(s->data.handle) ==
+ CONTENT_STATUS_ERROR)
+ continue;
+
+ /* ensure script handler for content type */
+ script_handler = select_script_handler(
+ content_get_type(s->data.handle));
+ if (script_handler == NULL)
+ continue; /* unsupported type */
+
+ if (content_get_status(s->data.handle) ==
+ CONTENT_STATUS_DONE) {
+ /* external script is now available */
+ const char *data;
+ unsigned long size;
+ data = content_get_source_data(
+ s->data.handle, &size );
+ script_handler(c->jscontext, data, size);
+
+ s->already_started = true;
+
+ }
+ }
+ }
+
+ return NSERROR_OK;
+}
+
+/* create new html script entry */
+static struct html_script *
+html_process_new_script(html_content *c,
+ dom_string *mimetype,
+ enum html_script_type type)
+{
+ struct html_script *nscript;
+ /* add space for new script entry */
+ nscript = realloc(c->scripts,
+ sizeof(struct html_script) * (c->scripts_count + 1));
+ if (nscript == NULL) {
+ return NULL;
+ }
+
+ c->scripts = nscript;
+
+ /* increment script entry count */
+ nscript = &c->scripts[c->scripts_count];
+ c->scripts_count++;
+
+ nscript->already_started = false;
+ nscript->parser_inserted = false;
+ nscript->force_async = true;
+ nscript->ready_exec = false;
+ nscript->async = false;
+ nscript->defer = false;
+
+ nscript->type = type;
+
+ nscript->mimetype = dom_string_ref(mimetype); /* reference mimetype */
+
+ return nscript;
+}
+
+/**
+ * Callback for asyncronous scripts
+ */
+static nserror
+convert_script_async_cb(hlcache_handle *script,
+ const hlcache_event *event,
+ void *pw)
+{
+ html_content *parent = pw;
+ unsigned int i;
+ struct html_script *s;
+
+ /* Find script */
+ for (i = 0, s = parent->scripts; i != parent->scripts_count; i++, s++) {
+ if (s->type == HTML_SCRIPT_ASYNC && s->data.handle == script)
+ break;
+ }
+
+ assert(i != parent->scripts_count);
+
+ switch (event->type) {
+ case CONTENT_MSG_LOADING:
+ break;
+
+ case CONTENT_MSG_READY:
+ break;
+
+ case CONTENT_MSG_DONE:
+ NSLOG(netsurf, INFO, "script %d done '%s'", i,
+ nsurl_access(hlcache_handle_get_url(script)));
+ parent->base.active--;
+ NSLOG(netsurf, INFO, "%d fetches active", parent->base.active);
+
+ break;
+
+ case CONTENT_MSG_ERROR:
+ NSLOG(netsurf, INFO, "script %s failed: %s",
+ nsurl_access(hlcache_handle_get_url(script)),
+ event->data.error);
+ /* fall through */
+
+ case CONTENT_MSG_ERRORCODE:
+ hlcache_handle_release(script);
+ s->data.handle = NULL;
+ parent->base.active--;
+ NSLOG(netsurf, INFO, "%d fetches active", parent->base.active);
+ content_add_error(&parent->base, "?", 0);
+
+ break;
+
+ default:
+ break;
+ }
+
+ /* if there are no active fetches remaining begin post parse
+ * conversion
+ */
+ if (html_can_begin_conversion(parent)) {
+ html_begin_conversion(parent);
+ }
+
+ return NSERROR_OK;
+}
+
+/**
+ * Callback for defer scripts
+ */
+static nserror
+convert_script_defer_cb(hlcache_handle *script,
+ const hlcache_event *event,
+ void *pw)
+{
+ html_content *parent = pw;
+ unsigned int i;
+ struct html_script *s;
+
+ /* Find script */
+ for (i = 0, s = parent->scripts; i != parent->scripts_count; i++, s++) {
+ if (s->type == HTML_SCRIPT_DEFER && s->data.handle == script)
+ break;
+ }
+
+ assert(i != parent->scripts_count);
+
+ switch (event->type) {
+
+ case CONTENT_MSG_DONE:
+ NSLOG(netsurf, INFO, "script %d done '%s'", i,
+ nsurl_access(hlcache_handle_get_url(script)));
+ parent->base.active--;
+ NSLOG(netsurf, INFO, "%d fetches active", parent->base.active);
+
+ break;
+
+ case CONTENT_MSG_ERROR:
+ NSLOG(netsurf, INFO, "script %s failed: %s",
+ nsurl_access(hlcache_handle_get_url(script)),
+ event->data.error);
+ /* fall through */
+
+ case CONTENT_MSG_ERRORCODE:
+ hlcache_handle_release(script);
+ s->data.handle = NULL;
+ parent->base.active--;
+ NSLOG(netsurf, INFO, "%d fetches active", parent->base.active);
+ content_add_error(&parent->base, "?", 0);
+
+ break;
+
+ default:
+ break;
+ }
+
+ /* if there are no active fetches remaining begin post parse
+ * conversion
+ */
+ if (html_can_begin_conversion(parent)) {
+ html_begin_conversion(parent);
+ }
+
+ return NSERROR_OK;
+}
+
+/**
+ * Callback for syncronous scripts
+ */
+static nserror
+convert_script_sync_cb(hlcache_handle *script,
+ const hlcache_event *event,
+ void *pw)
+{
+ html_content *parent = pw;
+ unsigned int i;
+ struct html_script *s;
+ script_handler_t *script_handler;
+ dom_hubbub_error err;
+
+ /* Find script */
+ for (i = 0, s = parent->scripts; i != parent->scripts_count; i++, s++) {
+ if (s->type == HTML_SCRIPT_SYNC && s->data.handle == script)
+ break;
+ }
+
+ assert(i != parent->scripts_count);
+
+ switch (event->type) {
+ case CONTENT_MSG_DONE:
+ NSLOG(netsurf, INFO, "script %d done '%s'", i,
+ nsurl_access(hlcache_handle_get_url(script)));
+ parent->base.active--;
+ NSLOG(netsurf, INFO, "%d fetches active", parent->base.active);
+
+ s->already_started = true;
+
+ /* attempt to execute script */
+ script_handler = select_script_handler(content_get_type(s->data.handle));
+ if (script_handler != NULL && parent->jscontext != NULL) {
+ /* script has a handler */
+ const char *data;
+ unsigned long size;
+ data = content_get_source_data(s->data.handle, &size );
+ script_handler(parent->jscontext, data, size);
+ }
+
+ /* continue parse */
+ err = dom_hubbub_parser_pause(parent->parser, false);
+ if (err != DOM_HUBBUB_OK) {
+ NSLOG(netsurf, INFO, "unpause returned 0x%x", err);
+ }
+
+ break;
+
+ case CONTENT_MSG_ERROR:
+ NSLOG(netsurf, INFO, "script %s failed: %s",
+ nsurl_access(hlcache_handle_get_url(script)),
+ event->data.error);
+ /* fall through */
+
+ case CONTENT_MSG_ERRORCODE:
+ hlcache_handle_release(script);
+ s->data.handle = NULL;
+ parent->base.active--;
+
+ NSLOG(netsurf, INFO, "%d fetches active", parent->base.active);
+ content_add_error(&parent->base, "?", 0);
+
+ s->already_started = true;
+
+ /* continue parse */
+ err = dom_hubbub_parser_pause(parent->parser, false);
+ if (err != DOM_HUBBUB_OK) {
+ NSLOG(netsurf, INFO, "unpause returned 0x%x", err);
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ /* if there are no active fetches remaining begin post parse
+ * conversion
+ */
+ if (html_can_begin_conversion(parent)) {
+ html_begin_conversion(parent);
+ }
+
+ return NSERROR_OK;
+}
+
+/**
+ * process a script with a src tag
+ */
+static dom_hubbub_error
+exec_src_script(html_content *c,
+ dom_node *node,
+ dom_string *mimetype,
+ dom_string *src)
+{
+ nserror ns_error;
+ nsurl *joined;
+ hlcache_child_context child;
+ struct html_script *nscript;
+ bool async;
+ bool defer;
+ enum html_script_type script_type;
+ hlcache_handle_callback script_cb;
+ dom_hubbub_error ret = DOM_HUBBUB_OK;
+ dom_exception exc; /* returned by libdom functions */
+
+ /* src url */
+ ns_error = nsurl_join(c->base_url, dom_string_data(src), &joined);
+ if (ns_error != NSERROR_OK) {
+ content_broadcast_errorcode(&c->base, NSERROR_NOMEM);
+ return DOM_HUBBUB_NOMEM;
+ }
+
+ NSLOG(netsurf, INFO, "script %i '%s'", c->scripts_count,
+ nsurl_access(joined));
+
+ /* there are three ways to process the script tag at this point:
+ *
+ * Syncronously pause the parent parse and continue after
+ * the script has downloaded and executed. (default)
+ * Async Start the script downloading and execute it when it
+ * becomes available.
+ * Defered Start the script downloading and execute it when
+ * the page has completed parsing, may be set along
+ * with async where it is ignored.
+ */
+
+ /* we interpret the presence of the async and defer attribute
+ * as true and ignore its value, technically only the empty
+ * value or the attribute name itself are valid. However
+ * various browsers interpret this in various ways the most
+ * compatible approach is to be liberal and accept any
+ * value. Note setting the values to "false" still makes them true!
+ */
+ exc = dom_element_has_attribute(node, corestring_dom_async, &async);
+ if (exc != DOM_NO_ERR) {
+ return DOM_HUBBUB_OK; /* dom error */
+ }
+
+ if (async) {
+ /* asyncronous script */
+ script_type = HTML_SCRIPT_ASYNC;
+ script_cb = convert_script_async_cb;
+
+ } else {
+ exc = dom_element_has_attribute(node,
+ corestring_dom_defer, &defer);
+ if (exc != DOM_NO_ERR) {
+ return DOM_HUBBUB_OK; /* dom error */
+ }
+
+ if (defer) {
+ /* defered script */
+ script_type = HTML_SCRIPT_DEFER;
+ script_cb = convert_script_defer_cb;
+ } else {
+ /* syncronous script */
+ script_type = HTML_SCRIPT_SYNC;
+ script_cb = convert_script_sync_cb;
+ }
+ }
+
+ nscript = html_process_new_script(c, mimetype, script_type);
+ if (nscript == NULL) {
+ nsurl_unref(joined);
+ content_broadcast_errorcode(&c->base, NSERROR_NOMEM);
+ return DOM_HUBBUB_NOMEM;
+ }
+
+ /* set up child fetch encoding and quirks */
+ child.charset = c->encoding;
+ child.quirks = c->base.quirks;
+
+ ns_error = hlcache_handle_retrieve(joined,
+ 0,
+ content_get_url(&c->base),
+ NULL,
+ script_cb,
+ c,
+ &child,
+ CONTENT_SCRIPT,
+ &nscript->data.handle);
+
+
+ nsurl_unref(joined);
+
+ if (ns_error != NSERROR_OK) {
+ /* @todo Deal with fetch error better. currently assume
+ * fetch never became active
+ */
+ /* mark duff script fetch as already started */
+ nscript->already_started = true;
+ NSLOG(netsurf, INFO, "Fetch failed with error %d", ns_error);
+ } else {
+ /* update base content active fetch count */
+ c->base.active++;
+ NSLOG(netsurf, INFO, "%d fetches active", c->base.active);
+
+ switch (script_type) {
+ case HTML_SCRIPT_SYNC:
+ ret = DOM_HUBBUB_HUBBUB_ERR | HUBBUB_PAUSED;
+
+ case HTML_SCRIPT_ASYNC:
+ break;
+
+ case HTML_SCRIPT_DEFER:
+ break;
+
+ default:
+ assert(0);
+ }
+ }
+
+ return ret;
+}
+
+static dom_hubbub_error
+exec_inline_script(html_content *c, dom_node *node, dom_string *mimetype)
+{
+ dom_string *script;
+ dom_exception exc; /* returned by libdom functions */
+ struct lwc_string_s *lwcmimetype;
+ script_handler_t *script_handler;
+ struct html_script *nscript;
+
+ /* does not appear to be a src so script is inline content */
+ exc = dom_node_get_text_content(node, &script);
+ if ((exc != DOM_NO_ERR) || (script == NULL)) {
+ return DOM_HUBBUB_OK; /* no contents, skip */
+ }
+
+ nscript = html_process_new_script(c, mimetype, HTML_SCRIPT_INLINE);
+ if (nscript == NULL) {
+ dom_string_unref(script);
+
+ content_broadcast_errorcode(&c->base, NSERROR_NOMEM);
+ return DOM_HUBBUB_NOMEM;
+
+ }
+
+ nscript->data.string = script;
+ nscript->already_started = true;
+
+ /* ensure script handler for content type */
+ dom_string_intern(mimetype, &lwcmimetype);
+ script_handler = select_script_handler(content_factory_type_from_mime_type(lwcmimetype));
+ lwc_string_unref(lwcmimetype);
+
+ if (script_handler != NULL) {
+ script_handler(c->jscontext,
+ dom_string_data(script),
+ dom_string_byte_length(script));
+ }
+ return DOM_HUBBUB_OK;
+}
+
+
+/**
+ * process script node parser callback
+ *
+ *
+ */
+dom_hubbub_error
+html_process_script(void *ctx, dom_node *node)
+{
+ html_content *c = (html_content *)ctx;
+ dom_exception exc; /* returned by libdom functions */
+ dom_string *src, *mimetype;
+ dom_hubbub_error err = DOM_HUBBUB_OK;
+
+ /* ensure javascript context is available */
+ /* We should only ever be here if scripting was enabled for this
+ * content so it's correct to make a javascript context if there
+ * isn't one already. */
+ if (c->jscontext == NULL) {
+ union content_msg_data msg_data;
+
+ msg_data.jscontext = &c->jscontext;
+ content_broadcast(&c->base, CONTENT_MSG_GETCTX, &msg_data);
+ NSLOG(netsurf, INFO, "javascript context %p ", c->jscontext);
+ if (c->jscontext == NULL) {
+ /* no context and it could not be created, abort */
+ return DOM_HUBBUB_OK;
+ }
+ }
+
+ NSLOG(netsurf, INFO, "content %p parser %p node %p", c, c->parser,
+ node);
+
+ exc = dom_element_get_attribute(node, corestring_dom_type, &mimetype);
+ if (exc != DOM_NO_ERR || mimetype == NULL) {
+ mimetype = dom_string_ref(corestring_dom_text_javascript);
+ }
+
+ exc = dom_element_get_attribute(node, corestring_dom_src, &src);
+ if (exc != DOM_NO_ERR || src == NULL) {
+ err = exec_inline_script(c, node, mimetype);
+ } else {
+ err = exec_src_script(c, node, mimetype, src);
+ dom_string_unref(src);
+ }
+
+ dom_string_unref(mimetype);
+
+ return err;
+}
+
+/* exported internal interface documented in html/html_internal.h */
+nserror html_script_free(html_content *html)
+{
+ unsigned int i;
+
+ for (i = 0; i != html->scripts_count; i++) {
+ if (html->scripts[i].mimetype != NULL) {
+ dom_string_unref(html->scripts[i].mimetype);
+ }
+
+ if ((html->scripts[i].type == HTML_SCRIPT_INLINE) &&
+ (html->scripts[i].data.string != NULL)) {
+
+ dom_string_unref(html->scripts[i].data.string);
+
+ } else if ((html->scripts[i].type == HTML_SCRIPT_SYNC) &&
+ (html->scripts[i].data.handle != NULL)) {
+
+ hlcache_handle_release(html->scripts[i].data.handle);
+
+ }
+ }
+ free(html->scripts);
+
+ return NSERROR_OK;
+}
+
+/* exported internal interface documented in html/html_internal.h */
+nserror html_script_invalidate_ctx(html_content *htmlc)
+{
+ htmlc->jscontext = NULL;
+ return NSERROR_OK;
+}