Subversion Repositories Kolibri OS

Rev

Rev 3584 | Blame | Last modification | View Log | RSS feed

  1. /*
  2.  * Copyright 2012 Vincent Sanders <vince@netsurf-browser.org>
  3.  *
  4.  * This file is part of NetSurf, http://www.netsurf-browser.org/
  5.  *
  6.  * NetSurf is free software; you can redistribute it and/or modify
  7.  * it under the terms of the GNU General Public License as published by
  8.  * the Free Software Foundation; version 2 of the License.
  9.  *
  10.  * NetSurf is distributed in the hope that it will be useful,
  11.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13.  * GNU General Public License for more details.
  14.  *
  15.  * You should have received a copy of the GNU General Public License
  16.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  17.  */
  18.  
  19. /** \file
  20.  * Content for text/html scripts (implementation).
  21.  */
  22.  
  23. #include <assert.h>
  24. #include <ctype.h>
  25. #include <stdint.h>
  26. #include <stdbool.h>
  27. #include <string.h>
  28. #include <strings.h>
  29. #include <stdlib.h>
  30.  
  31. #include "utils/config.h"
  32. #include "utils/corestrings.h"
  33. #include "utils/log.h"
  34. #include "utils/messages.h"
  35. #include "javascript/js.h"
  36. #include "content/content_protected.h"
  37. #include "content/fetch.h"
  38. #include "content/hlcache.h"
  39. #include "render/html_internal.h"
  40.  
  41. typedef bool (script_handler_t)(struct jscontext *jscontext, const char *data, size_t size) ;
  42.  
  43.  
  44. static script_handler_t *select_script_handler(content_type ctype)
  45. {
  46.         if (ctype == CONTENT_JS) {
  47.                 return js_exec;
  48.         }
  49.         return NULL;
  50. }
  51.  
  52.  
  53. /* attempt defer and async script execution
  54.  *
  55.  * execute scripts using algorithm found in:
  56.  * http://www.whatwg.org/specs/web-apps/current-work/multipage/scripting-1.html#the-script-element
  57.  *
  58.  */
  59. bool html_scripts_exec(html_content *c)
  60. {
  61.         unsigned int i;
  62.         struct html_script *s;
  63.         script_handler_t *script_handler;
  64.  
  65.         if (c->jscontext == NULL)
  66.                 return false;
  67.  
  68.         for (i = 0, s = c->scripts; i != c->scripts_count; i++, s++) {
  69.                 if (s->already_started) {
  70.                         continue;
  71.                 }
  72.  
  73.                 if ((s->type == HTML_SCRIPT_ASYNC) ||
  74.                     (s->type == HTML_SCRIPT_DEFER)) {
  75.                         /* ensure script content is present */
  76.                         if (s->data.handle == NULL)
  77.                                 continue;
  78.  
  79.                         /* ensure script content fetch status is not an error */
  80.                         if (content_get_status(s->data.handle) ==
  81.                                         CONTENT_STATUS_ERROR)
  82.                                 continue;
  83.  
  84.                         /* ensure script handler for content type */
  85.                         script_handler = select_script_handler(
  86.                                         content_get_type(s->data.handle));
  87.                         if (script_handler == NULL)
  88.                                 continue; /* unsupported type */
  89.  
  90.                         if (content_get_status(s->data.handle) ==
  91.                                         CONTENT_STATUS_DONE) {
  92.                                 /* external script is now available */
  93.                                 const char *data;
  94.                                 unsigned long size;
  95.                                 data = content_get_source_data(
  96.                                                 s->data.handle, &size );
  97.                                 script_handler(c->jscontext, data, size);
  98.  
  99.                                 s->already_started = true;
  100.  
  101.                         }
  102.                 }
  103.         }
  104.  
  105.         return true;
  106. }
  107.  
  108. /* create new html script entry */
  109. static struct html_script *
  110. html_process_new_script(html_content *c,
  111.                         dom_string *mimetype,
  112.                         enum html_script_type type)
  113. {
  114.         struct html_script *nscript;
  115.         /* add space for new script entry */
  116.         nscript = realloc(c->scripts,
  117.                           sizeof(struct html_script) * (c->scripts_count + 1));
  118.         if (nscript == NULL) {
  119.                 return NULL;
  120.         }
  121.  
  122.         c->scripts = nscript;
  123.  
  124.         /* increment script entry count */
  125.         nscript = &c->scripts[c->scripts_count];
  126.         c->scripts_count++;
  127.  
  128.         nscript->already_started = false;
  129.         nscript->parser_inserted = false;
  130.         nscript->force_async = true;
  131.         nscript->ready_exec = false;
  132.         nscript->async = false;
  133.         nscript->defer = false;
  134.  
  135.         nscript->type = type;
  136.  
  137.         nscript->mimetype = dom_string_ref(mimetype); /* reference mimetype */
  138.  
  139.         return nscript;
  140. }
  141.  
  142. /**
  143.  * Callback for asyncronous scripts
  144.  */
  145. static nserror
  146. convert_script_async_cb(hlcache_handle *script,
  147.                           const hlcache_event *event,
  148.                           void *pw)
  149. {
  150.         html_content *parent = pw;
  151.         unsigned int i;
  152.         struct html_script *s;
  153.  
  154.         /* Find script */
  155.         for (i = 0, s = parent->scripts; i != parent->scripts_count; i++, s++) {
  156.                 if (s->type == HTML_SCRIPT_ASYNC && s->data.handle == script)
  157.                         break;
  158.         }
  159.  
  160.         assert(i != parent->scripts_count);
  161.  
  162.         switch (event->type) {
  163.         case CONTENT_MSG_LOADING:
  164.                 break;
  165.  
  166.         case CONTENT_MSG_READY:
  167.                 break;
  168.  
  169.         case CONTENT_MSG_DONE:
  170.                 LOG(("script %d done '%s'", i,
  171.                                 nsurl_access(hlcache_handle_get_url(script))));
  172.                 parent->base.active--;
  173.                 LOG(("%d fetches active", parent->base.active));
  174.  
  175.  
  176.  
  177.                 break;
  178.  
  179.         case CONTENT_MSG_ERROR:
  180.                 LOG(("script %s failed: %s",
  181.                                 nsurl_access(hlcache_handle_get_url(script)),
  182.                                 event->data.error));
  183.                 hlcache_handle_release(script);
  184.                 s->data.handle = NULL;
  185.                 parent->base.active--;
  186.                 LOG(("%d fetches active", parent->base.active));
  187.                 content_add_error(&parent->base, "?", 0);
  188.  
  189.                 break;
  190.  
  191.         case CONTENT_MSG_STATUS:
  192.                 break;
  193.  
  194.         default:
  195.                 assert(0);
  196.         }
  197.  
  198.         return NSERROR_OK;
  199. }
  200.  
  201. /**
  202.  * Callback for defer scripts
  203.  */
  204. static nserror
  205. convert_script_defer_cb(hlcache_handle *script,
  206.                           const hlcache_event *event,
  207.                           void *pw)
  208. {
  209.         html_content *parent = pw;
  210.         unsigned int i;
  211.         struct html_script *s;
  212.  
  213.         /* Find script */
  214.         for (i = 0, s = parent->scripts; i != parent->scripts_count; i++, s++) {
  215.                 if (s->type == HTML_SCRIPT_DEFER && s->data.handle == script)
  216.                         break;
  217.         }
  218.  
  219.         assert(i != parent->scripts_count);
  220.  
  221.         switch (event->type) {
  222.         case CONTENT_MSG_LOADING:
  223.                 break;
  224.  
  225.         case CONTENT_MSG_READY:
  226.                 break;
  227.  
  228.         case CONTENT_MSG_DONE:
  229.                 LOG(("script %d done '%s'", i,
  230.                                 nsurl_access(hlcache_handle_get_url(script))));
  231.                 parent->base.active--;
  232.                 LOG(("%d fetches active", parent->base.active));
  233.  
  234.                 break;
  235.  
  236.         case CONTENT_MSG_ERROR:
  237.                 LOG(("script %s failed: %s",
  238.                                 nsurl_access(hlcache_handle_get_url(script)),
  239.                                 event->data.error));
  240.                 hlcache_handle_release(script);
  241.                 s->data.handle = NULL;
  242.                 parent->base.active--;
  243.                 LOG(("%d fetches active", parent->base.active));
  244.                 content_add_error(&parent->base, "?", 0);
  245.  
  246.                 break;
  247.  
  248.         case CONTENT_MSG_STATUS:
  249.                 break;
  250.  
  251.         default:
  252.                 assert(0);
  253.         }
  254.  
  255.         /* if there are no active fetches remaining begin post parse
  256.          * conversion
  257.          */
  258.         if (parent->base.active == 0) {
  259.                 html_begin_conversion(parent);
  260.         }
  261.  
  262.         return NSERROR_OK;
  263. }
  264.  
  265. /**
  266.  * Callback for syncronous scripts
  267.  */
  268. static nserror
  269. convert_script_sync_cb(hlcache_handle *script,
  270.                           const hlcache_event *event,
  271.                           void *pw)
  272. {
  273.         html_content *parent = pw;
  274.         unsigned int i;
  275.         struct html_script *s;
  276.         script_handler_t *script_handler;
  277.         dom_hubbub_error err;
  278.  
  279.         /* Find script */
  280.         for (i = 0, s = parent->scripts; i != parent->scripts_count; i++, s++) {
  281.                 if (s->type == HTML_SCRIPT_SYNC && s->data.handle == script)
  282.                         break;
  283.         }
  284.  
  285.         assert(i != parent->scripts_count);
  286.  
  287.         switch (event->type) {
  288.         case CONTENT_MSG_LOADING:
  289.                 break;
  290.  
  291.         case CONTENT_MSG_READY:
  292.                 break;
  293.  
  294.         case CONTENT_MSG_DONE:
  295.                 LOG(("script %d done '%s'", i,
  296.                                 nsurl_access(hlcache_handle_get_url(script))));
  297.                 parent->base.active--;
  298.                 LOG(("%d fetches active", parent->base.active));
  299.  
  300.                 s->already_started = true;
  301.  
  302.                 /* attempt to execute script */
  303.                 script_handler = select_script_handler(content_get_type(s->data.handle));
  304.                 if (script_handler != NULL) {
  305.                         /* script has a handler */
  306.                         const char *data;
  307.                         unsigned long size;
  308.                         data = content_get_source_data(s->data.handle, &size );
  309.                         script_handler(parent->jscontext, data, size);
  310.                 }
  311.  
  312.                 /* continue parse */
  313.                 err = dom_hubbub_parser_pause(parent->parser, false);
  314.                 if (err != DOM_HUBBUB_OK) {
  315.                         LOG(("unpause returned 0x%x", err));
  316.                 }
  317.  
  318.                 break;
  319.  
  320.         case CONTENT_MSG_ERROR:
  321.                 LOG(("script %s failed: %s",
  322.                                 nsurl_access(hlcache_handle_get_url(script)),
  323.                                 event->data.error));
  324.  
  325.                 hlcache_handle_release(script);
  326.                 s->data.handle = NULL;
  327.                 parent->base.active--;
  328.  
  329.                 LOG(("%d fetches active", parent->base.active));
  330.                 content_add_error(&parent->base, "?", 0);
  331.  
  332.                 s->already_started = true;
  333.  
  334.                 /* continue parse */
  335.                 err = dom_hubbub_parser_pause(parent->parser, false);
  336.                 if (err != DOM_HUBBUB_OK) {
  337.                         LOG(("unpause returned 0x%x", err));
  338.                 }
  339.  
  340.                 break;
  341.  
  342.         case CONTENT_MSG_STATUS:
  343.                 break;
  344.  
  345.         default:
  346.                 assert(0);
  347.         }
  348.  
  349.         /* if there are no active fetches remaining begin post parse
  350.          * conversion
  351.          */
  352.         if (parent->base.active == 0) {
  353.                 html_begin_conversion(parent);
  354.         }
  355.  
  356.         return NSERROR_OK;
  357. }
  358.  
  359. /**
  360.  * process a script with a src tag
  361.  */
  362. static dom_hubbub_error
  363. exec_src_script(html_content *c,
  364.                 dom_node *node,
  365.                 dom_string *mimetype,
  366.                 dom_string *src)
  367. {
  368.         nserror ns_error;
  369.         nsurl *joined;
  370.         hlcache_child_context child;
  371.         struct html_script *nscript;
  372.         union content_msg_data msg_data;
  373.         bool async;
  374.         bool defer;
  375.         enum html_script_type script_type;
  376.         hlcache_handle_callback script_cb;
  377.         dom_hubbub_error ret = DOM_HUBBUB_OK;
  378.         dom_exception exc; /* returned by libdom functions */
  379.  
  380.         /* src url */
  381.         ns_error = nsurl_join(c->base_url, dom_string_data(src), &joined);
  382.         if (ns_error != NSERROR_OK) {
  383.                 msg_data.error = messages_get("NoMemory");
  384.                 content_broadcast(&c->base, CONTENT_MSG_ERROR, msg_data);
  385.                 return DOM_HUBBUB_NOMEM;
  386.         }
  387.  
  388.         LOG(("script %i '%s'", c->scripts_count, nsurl_access(joined)));
  389.  
  390.         /* there are three ways to process the script tag at this point:
  391.          *
  392.          * Syncronously  pause the parent parse and continue after
  393.          *                 the script has downloaded and executed. (default)
  394.          * Async         Start the script downloading and execute it when it
  395.          *                 becomes available.
  396.          * Defered       Start the script downloading and execute it when
  397.          *                 the page has completed parsing, may be set along
  398.          *                 with async where it is ignored.
  399.          */
  400.  
  401.         /* we interpret the presence of the async and defer attribute
  402.          * as true and ignore its value, technically only the empty
  403.          * value or the attribute name itself are valid. However
  404.          * various browsers interpret this in various ways the most
  405.          * compatible approach is to be liberal and accept any
  406.          * value. Note setting the values to "false" still makes them true!
  407.          */
  408.         exc = dom_element_has_attribute(node, corestring_dom_async, &async);
  409.         if (exc != DOM_NO_ERR) {
  410.                 return DOM_HUBBUB_OK; /* dom error */
  411.         }
  412.  
  413.         if (async) {
  414.                 /* asyncronous script */
  415.                 script_type = HTML_SCRIPT_ASYNC;
  416.                 script_cb = convert_script_async_cb;
  417.  
  418.         } else {
  419.                 exc = dom_element_has_attribute(node,
  420.                                                 corestring_dom_defer, &defer);
  421.                 if (exc != DOM_NO_ERR) {
  422.                         return DOM_HUBBUB_OK; /* dom error */
  423.                 }
  424.  
  425.                 if (defer) {
  426.                         /* defered script */
  427.                         script_type = HTML_SCRIPT_DEFER;
  428.                         script_cb = convert_script_defer_cb;
  429.                 } else {
  430.                         /* syncronous script */
  431.                         script_type = HTML_SCRIPT_SYNC;
  432.                         script_cb = convert_script_sync_cb;
  433.                 }
  434.         }
  435.  
  436.         nscript = html_process_new_script(c, mimetype, script_type);
  437.         if (nscript == NULL) {
  438.                 nsurl_unref(joined);
  439.                 msg_data.error = messages_get("NoMemory");
  440.                 content_broadcast(&c->base, CONTENT_MSG_ERROR, msg_data);
  441.                 return DOM_HUBBUB_NOMEM;
  442.         }
  443.  
  444.         /* set up child fetch encoding and quirks */
  445.         child.charset = c->encoding;
  446.         child.quirks = c->base.quirks;
  447.  
  448.         ns_error = hlcache_handle_retrieve(joined,
  449.                                            0,
  450.                                            content_get_url(&c->base),
  451.                                            NULL,
  452.                                            script_cb,
  453.                                            c,
  454.                                            &child,
  455.                                            CONTENT_SCRIPT,
  456.                                            &nscript->data.handle);
  457.  
  458.  
  459.         nsurl_unref(joined);
  460.  
  461.         if (ns_error != NSERROR_OK) {
  462.                 /* @todo Deal with fetch error better. currently assume
  463.                  * fetch never became active
  464.                  */
  465.                 /* mark duff script fetch as already started */
  466.                 nscript->already_started = true;
  467.                 LOG(("Fetch failed with error %d",ns_error));
  468.         } else {
  469.                 /* update base content active fetch count */
  470.                 c->base.active++;
  471.                 LOG(("%d fetches active", c->base.active));
  472.  
  473.                 switch (script_type) {
  474.                 case HTML_SCRIPT_SYNC:
  475.                         ret =  DOM_HUBBUB_HUBBUB_ERR | HUBBUB_PAUSED;
  476.  
  477.                 case HTML_SCRIPT_ASYNC:
  478.                         break;
  479.  
  480.                 case HTML_SCRIPT_DEFER:
  481.                         break;
  482.  
  483.                 default:
  484.                         assert(0);
  485.                 }
  486.         }
  487.  
  488.         return ret;
  489. }
  490.  
  491. static dom_hubbub_error
  492. exec_inline_script(html_content *c, dom_node *node, dom_string *mimetype)
  493. {
  494.         union content_msg_data msg_data;
  495.         dom_string *script;
  496.         dom_exception exc; /* returned by libdom functions */
  497.         struct lwc_string_s *lwcmimetype;
  498.         script_handler_t *script_handler;
  499.         struct html_script *nscript;
  500.  
  501.         /* does not appear to be a src so script is inline content */
  502.         exc = dom_node_get_text_content(node, &script);
  503.         if ((exc != DOM_NO_ERR) || (script == NULL)) {
  504.                 return DOM_HUBBUB_OK; /* no contents, skip */
  505.         }
  506.  
  507.         nscript = html_process_new_script(c, mimetype, HTML_SCRIPT_INLINE);
  508.         if (nscript == NULL) {
  509.                 dom_string_unref(script);
  510.  
  511.                 msg_data.error = messages_get("NoMemory");
  512.                 content_broadcast(&c->base, CONTENT_MSG_ERROR, msg_data);
  513.                 return DOM_HUBBUB_NOMEM;
  514.  
  515.         }
  516.  
  517.         nscript->data.string = script;
  518.         nscript->already_started = true;
  519.  
  520.         /* ensure script handler for content type */
  521.         dom_string_intern(mimetype, &lwcmimetype);
  522.         script_handler = select_script_handler(content_factory_type_from_mime_type(lwcmimetype));
  523.         lwc_string_unref(lwcmimetype);
  524.  
  525.         if (script_handler != NULL) {
  526.                 script_handler(c->jscontext,
  527.                                dom_string_data(script),
  528.                                dom_string_byte_length(script));
  529.         }
  530.         return DOM_HUBBUB_OK;
  531. }
  532.  
  533.  
  534. /**
  535.  * process script node parser callback
  536.  *
  537.  *
  538.  */
  539. dom_hubbub_error
  540. html_process_script(void *ctx, dom_node *node)
  541. {
  542.         html_content *c = (html_content *)ctx;
  543.         dom_exception exc; /* returned by libdom functions */
  544.         dom_string *src, *mimetype;
  545.         dom_hubbub_error err = DOM_HUBBUB_OK;
  546.  
  547.         /* ensure javascript context is available */
  548.         if (c->jscontext == NULL) {
  549.                 union content_msg_data msg_data;
  550.  
  551.                 msg_data.jscontext = &c->jscontext;
  552.                 content_broadcast(&c->base, CONTENT_MSG_GETCTX, msg_data);
  553.                 LOG(("javascript context %p ", c->jscontext));
  554.                 if (c->jscontext == NULL) {
  555.                         /* no context and it could not be created, abort */
  556.                         return DOM_HUBBUB_OK;
  557.                 }
  558.         }
  559.  
  560.         LOG(("content %p parser %p node %p", c, c->parser, node));
  561.  
  562.         exc = dom_element_get_attribute(node, corestring_dom_type, &mimetype);
  563.         if (exc != DOM_NO_ERR || mimetype == NULL) {
  564.                 mimetype = dom_string_ref(corestring_dom_text_javascript);
  565.         }
  566.  
  567.         exc = dom_element_get_attribute(node, corestring_dom_src, &src);
  568.         if (exc != DOM_NO_ERR || src == NULL) {
  569.                 err = exec_inline_script(c, node, mimetype);
  570.         } else {
  571.                 err = exec_src_script(c, node, mimetype, src);
  572.                 dom_string_unref(src);
  573.         }
  574.  
  575.         dom_string_unref(mimetype);
  576.  
  577.         return err;
  578. }
  579.  
  580. void html_free_scripts(html_content *html)
  581. {
  582.         unsigned int i;
  583.  
  584.         for (i = 0; i != html->scripts_count; i++) {
  585.                 if (html->scripts[i].mimetype != NULL) {
  586.                         dom_string_unref(html->scripts[i].mimetype);
  587.                 }
  588.  
  589.                 if ((html->scripts[i].type == HTML_SCRIPT_INLINE) &&
  590.                     (html->scripts[i].data.string != NULL)) {
  591.  
  592.                         dom_string_unref(html->scripts[i].data.string);
  593.  
  594.                 } else if ((html->scripts[i].type == HTML_SCRIPT_SYNC) &&
  595.                            (html->scripts[i].data.handle != NULL)) {
  596.  
  597.                         hlcache_handle_release(html->scripts[i].data.handle);
  598.  
  599.                 }
  600.         }
  601.         free(html->scripts);
  602. }
  603.