Support script insertion after conversion has begun

Signed-off-by: Daniel Silverstone <dsilvers@digital-scurf.org>
This commit is contained in:
Daniel Silverstone 2019-05-04 12:18:21 +01:00
parent 90cabaf8c8
commit 19b45fb494
8 changed files with 178 additions and 13 deletions

View File

@ -645,6 +645,56 @@ void html_finish_conversion(html_content *htmlc)
dom_node_unref(html);
}
/* handler for a SCRIPT which has been added to a tree */
static void
dom_SCRIPT_showed_up(html_content *htmlc, dom_html_script_element *script)
{
dom_exception exc;
dom_html_script_element_flags flags;
dom_hubbub_error res;
bool within;
if (!htmlc->enable_scripting) {
NSLOG(netsurf, INFO, "Encountered a script, but scripting is off, ignoring");
return;
}
NSLOG(netsurf, DEEPDEBUG, "Encountered a script, node %p showed up", script);
exc = dom_html_script_element_get_flags(script, &flags);
if (exc != DOM_NO_ERR) {
NSLOG(netsurf, DEEPDEBUG, "Unable to retrieve flags, giving up");
return;
}
if (flags & DOM_HTML_SCRIPT_ELEMENT_FLAG_PARSER_INSERTED) {
NSLOG(netsurf, DEBUG, "Script was parser inserted, skipping");
return;
}
exc = dom_node_contains(htmlc->document, script, &within);
if (exc != DOM_NO_ERR) {
NSLOG(netsurf, DEBUG, "Unable to determine if script was within document, ignoring");
return;
}
if (!within) {
NSLOG(netsurf, DEBUG, "Script was not within the document, ignoring for now");
return;
}
res = html_process_script(htmlc, (dom_node *) script);
if (res == DOM_HUBBUB_OK) {
NSLOG(netsurf, DEEPDEBUG, "Inserted script has finished running");
} else {
if (res == (DOM_HUBBUB_HUBBUB_ERR | HUBBUB_PAUSED)) {
NSLOG(netsurf, DEEPDEBUG, "Inserted script has launced asynchronously");
} else {
NSLOG(netsurf, DEEPDEBUG, "Failure starting script");
}
}
}
/* callback for DOMNodeInserted end type */
static void
dom_default_action_DOMNodeInserted_cb(struct dom_event *evt, void *pw)
@ -693,6 +743,9 @@ dom_default_action_DOMNodeInserted_cb(struct dom_event *evt, void *pw)
case DOM_HTML_ELEMENT_TYPE_STYLE:
html_css_process_style(htmlc, (dom_node *) node);
break;
case DOM_HTML_ELEMENT_TYPE_SCRIPT:
dom_SCRIPT_showed_up(htmlc, (dom_html_script_element *) node);
break;
default:
break;
}
@ -720,7 +773,39 @@ dom_default_action_DOMNodeInserted_cb(struct dom_event *evt, void *pw)
}
}
/* callback for DOMNodeInserted end type */
/* callback for DOMNodeInsertedIntoDocument end type */
static void
dom_default_action_DOMNodeInsertedIntoDocument_cb(struct dom_event *evt, void *pw)
{
html_content *htmlc = pw;
dom_event_target *node;
dom_node_type type;
dom_exception exc;
exc = dom_event_get_target(evt, &node);
if ((exc == DOM_NO_ERR) && (node != NULL)) {
exc = dom_node_get_node_type(node, &type);
if ((exc == DOM_NO_ERR) && (type == DOM_ELEMENT_NODE)) {
/* an element node has been modified */
dom_html_element_type tag_type;
exc = dom_html_element_get_tag_type(node, &tag_type);
if (exc != DOM_NO_ERR) {
tag_type = DOM_HTML_ELEMENT_TYPE__UNKNOWN;
}
switch (tag_type) {
case DOM_HTML_ELEMENT_TYPE_SCRIPT:
dom_SCRIPT_showed_up(htmlc, (dom_html_script_element *) node);
default:
break;
}
}
dom_node_unref(node);
}
}
/* callback for DOMSubtreeModified end type */
static void
dom_default_action_DOMSubtreeModified_cb(struct dom_event *evt, void *pw)
{
@ -787,11 +872,13 @@ dom_event_fetcher(dom_string *type,
dom_default_action_phase phase,
void **pw)
{
NSLOG(netsurf, DEEPDEBUG, "type:%s", dom_string_data(type));
NSLOG(netsurf, DEEPDEBUG, "phase:%d type:%s", phase, dom_string_data(type));
if (phase == DOM_DEFAULT_ACTION_END) {
if (dom_string_isequal(type, corestring_dom_DOMNodeInserted)) {
return dom_default_action_DOMNodeInserted_cb;
} else if (dom_string_isequal(type, corestring_dom_DOMNodeInsertedIntoDocument)) {
return dom_default_action_DOMNodeInsertedIntoDocument_cb;
} else if (dom_string_isequal(type, corestring_dom_DOMSubtreeModified)) {
return dom_default_action_DOMSubtreeModified_cb;
}
@ -847,6 +934,7 @@ html_create_html_data(html_content *c, const http_parameter *params)
c->parser = NULL;
c->parse_completed = false;
c->conversion_begun = false;
c->document = NULL;
c->quirks = DOM_DOCUMENT_QUIRKS_MODE_NONE;
c->encoding = NULL;
@ -948,6 +1036,7 @@ html_create_html_data(html_content *c, const http_parameter *params)
(void *) &old_node_data);
if (err != DOM_NO_ERR) {
dom_hubbub_parser_destroy(c->parser);
c->parser = NULL;
nsurl_unref(c->base_url);
c->base_url = NULL;
@ -1185,14 +1274,21 @@ bool html_can_begin_conversion(html_content *htmlc)
{
unsigned int i;
/* Cannot begin conversion if we already have */
if (htmlc->conversion_begun)
return false;
/* Cannot begin conversion if we're still fetching stuff */
if (htmlc->base.active != 0)
return false;
for (i = 0; i != htmlc->stylesheet_count; i++) {
/* Cannot begin conversion if the stylesheets are modified */
if (htmlc->stylesheets[i].modified)
return false;
}
/* All is good, begin */
return true;
}
@ -1206,6 +1302,10 @@ html_begin_conversion(html_content *htmlc)
dom_string *node_name = NULL;
dom_hubbub_error error;
if (htmlc->conversion_begun)
/* Conversion already began, so we are okay */
return true;
/* The act of completing the parse can result in additional data
* being flushed through the parser. This may result in new style or
* script nodes, upon which the conversion depends. Thus, once we
@ -1247,8 +1347,11 @@ html_begin_conversion(html_content *htmlc)
return false;
}
/* complete script execution */
html_script_exec(htmlc);
/* Conversion begins proper at this point */
htmlc->conversion_begun = true;
/* complete script execution, including deferred scripts */
html_script_exec(htmlc, true);
/* fire a simple event that bubbles named DOMContentLoaded at
* the Document.

View File

@ -98,6 +98,7 @@ typedef struct html_content {
dom_hubbub_parser *parser; /**< Parser object handle */
bool parse_completed; /**< Whether the parse has been completed */
bool conversion_begun; /**< Whether or not the conversion has begun */
/** Document tree */
dom_document *document;
@ -313,9 +314,10 @@ dom_hubbub_error html_process_script(void *ctx, dom_node *node);
* http://www.whatwg.org/specs/web-apps/current-work/multipage/scripting-1.html#the-script-element
*
* \param htmlc html content.
* \param allow_defer allow deferred execution, if not, only async scripts.
* \return NSERROR_OK error code.
*/
nserror html_script_exec(html_content *htmlc);
nserror html_script_exec(html_content *htmlc, bool allow_defer);
/**
* Free all script resources and references for a html content.

View File

@ -55,7 +55,7 @@ static script_handler_t *select_script_handler(content_type ctype)
/* exported internal interface documented in html/html_internal.h */
nserror html_script_exec(html_content *c)
nserror html_script_exec(html_content *c, bool allow_defer)
{
unsigned int i;
struct html_script *s;
@ -71,7 +71,7 @@ nserror html_script_exec(html_content *c)
}
if ((s->type == HTML_SCRIPT_ASYNC) ||
(s->type == HTML_SCRIPT_DEFER)) {
(allow_defer && (s->type == HTML_SCRIPT_DEFER))) {
/* ensure script content is present */
if (s->data.handle == NULL)
continue;
@ -200,6 +200,13 @@ convert_script_async_cb(hlcache_handle *script,
html_begin_conversion(parent);
}
/* if we have already started converting though, then we can handle the
* scripts as they come in.
*/
else if (parent->conversion_begun) {
html_script_exec(parent, false);
}
return NSERROR_OK;
}
@ -304,9 +311,11 @@ convert_script_sync_cb(hlcache_handle *script,
}
/* continue parse */
err = dom_hubbub_parser_pause(parent->parser, false);
if (err != DOM_HUBBUB_OK) {
NSLOG(netsurf, INFO, "unpause returned 0x%x", err);
if (parent->parser != NULL) {
err = dom_hubbub_parser_pause(parent->parser, false);
if (err != DOM_HUBBUB_OK) {
NSLOG(netsurf, INFO, "unpause returned 0x%x", err);
}
}
break;
@ -328,9 +337,11 @@ convert_script_sync_cb(hlcache_handle *script,
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);
if (parent->parser != NULL) {
err = dom_hubbub_parser_pause(parent->parser, false);
if (err != DOM_HUBBUB_OK) {
NSLOG(netsurf, INFO, "unpause returned 0x%x", err);
}
}
break;
@ -402,6 +413,12 @@ exec_src_script(html_content *c,
return DOM_HUBBUB_OK; /* dom error */
}
if (c->parse_completed) {
/* After parse completed, all scripts are essentially async */
async = true;
defer = false;
}
if (async) {
/* asyncronous script */
script_type = HTML_SCRIPT_ASYNC;

View File

@ -427,6 +427,7 @@ method Node::appendChild()
dom_exception err;
dom_node *spare;
NSLOG(dukky, DEEPDEBUG, "About to append %p to %p", other->node, priv->node);
err = dom_node_append_child(priv->node, other->node, &spare);
if (err != DOM_NO_ERR) return 0;
dukky_push_node(ctx, spare);

View File

@ -0,0 +1 @@
console.log("External asynchronous dynamism!");

View File

@ -0,0 +1 @@
console.log("External deferred dynamism!");

View File

@ -0,0 +1,39 @@
<html>
<head>
<title>Inserted script test</title>
<script>
/* After one second, insert an inline script element */
setTimeout(function() {
var div = document.createElement("DIV");
var script = document.createElement("SCRIPT");
var textnode = document.createTextNode("console.log(\"Dynamism\");");
script.appendChild(textnode);
div.appendChild(script);
document.body.appendChild(div);
}, 1000);
/* After two seconds, insert a script element for immediate fetch */
setTimeout(function() {
var script = document.createElement("SCRIPT");
script.setAttribute("src", "inserted-script.js");
document.body.appendChild(script);
}, 2000);
/* After three seconds, insert a script element for async fetch */
setTimeout(function() {
var script = document.createElement("SCRIPT");
script.setAttribute("src", "inserted-script-async.js");
script.setAttribute("async", "");
document.body.appendChild(script);
}, 3000);
/* After four seconds, insert a script element for deferred fetch */
setTimeout(function() {
var script = document.createElement("SCRIPT");
script.setAttribute("src", "inserted-script-defer.js");
script.setAttribute("defer", "");
document.body.appendChild(script);
}, 4000);
</script>
</head>
<body>
Check the log
</body>
</html>

View File

@ -0,0 +1 @@
console.log("External dynamism!");