/[suikacvs]/markup/html/scripting-parser/parser.html
Suika

Diff of /markup/html/scripting-parser/parser.html

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 1.5 by wakaba, Fri Apr 25 11:40:56 2008 UTC revision 1.12 by wakaba, Sun Apr 27 11:21:09 2008 UTC
# Line 1  Line 1 
1  <!DOCTYPE HTML>  <!DOCTYPE HTML>
2  <html lang=en>  <html lang=en>
3  <head>  <head>
4  <title>Demo of HTML5 Parsing Algorithm with Scripting Enabled</title>  <title>Live Scripting HTML Parser</title>
5  <style>  <style>
6      h1, h2 {
7        margin: 0;
8        font-size: 100%;
9      }
10      p, pre {
11        margin: 0;
12      }
13    textarea {    textarea {
14       display: block;      width: 100%;
15       width: 80%;      -width: 99%;
16       margin-left: auto;      height: 10em;
      margin-right: auto;  
      min-height: 20em;  
17    }    }
18    output {    output {
19      display: block;      display: block;
# Line 18  Line 23 
23    }    }
24  </style>  </style>
25  <script>  <script>
26      var delayedUpdater = 0;
27    
28    function update () {    function update () {
29      document.logElement.textContent = '';      if (delayedUpdater) {
30      var p = new Parser (new InputStream (document.sourceElement.value));        clearTimeout (delayedUpdater);
31      var doc = p.doc;        delayedUpdater = 0;
32      p.parse ();      }
33      log (dumpTree (doc, ''));      delayedUpdater = setTimeout (update2, 100);
34    } // update    } // update
35    
36      function update2 () {
37        var v = document.sourceElement.value;
38        if (v != document.previousSourceText) {
39          document.previousSourceText = v;
40          document.links['permalink'].href
41              = location.pathname + '?s=' + encodeURIComponent (v);
42          document.links['ldvlink'].href
43              = 'http://software.hixie.ch/utilities/js/live-dom-viewer/?'
44              + encodeURIComponent (v);
45    
46          document.logElement.textContent = '';
47          var p = new Parser (new InputStream (v));
48          var doc = p.doc;
49          p.parse ();
50          
51          log (dumpTree (doc, ''));
52          
53          if (p.hasAsyncScript) {
54            log ('Some script codes are executed asynchronously; it means that the document might be rendered in different ways depending on the network condition and other factors');
55          }
56        }
57      } // update2
58    
59      var logIndentLevel = 0;
60    function log (s) {    function log (s) {
61        for (var i = 0; i < logIndentLevel; i++) {
62          s = '  ' + s;
63        }
64      document.logElement.appendChild (document.createTextNode (s + "\n"));      document.logElement.appendChild (document.createTextNode (s + "\n"));
65    } // log    } // log
66    
# Line 42  Line 76 
76      }      }
77      this.doc = doc;      this.doc = doc;
78      this.openElements = [doc];      this.openElements = [doc];
79      this.in = i;      this.input = i;
80      this.scriptsExecutedAfterParsing = [];      this.scriptsExecutedAfterParsing = [];
81        this.scriptsExecutedSoon = [];
82        this.scriptsExecutedAsynchronously = [];
83    } // Parser    } // Parser
84    
85    Parser.prototype.getNextToken = function () {    Parser.prototype.getNextToken = function () {
86      var p = this;      var p = this;
87      var i = this.in;      var i = this.input;
88      if (this.parseMode == 'script') {      if (this.parseMode == 'script') {
89        var token;        var token;
90        if (p.insertionPoint <= 0) {        if (p.insertionPoint <= 0) {
# Line 121  Line 157 
157          tagName = v.toLowerCase ();          tagName = v.toLowerCase ();
158          return '';          return '';
159        });        });
160        e = e.replace (/^\s*(\S+)\s*(?:=\s*"([^"]*)"|'([^']*)'|([^"']+))?/,        while (true) {
161        function (x, attrName, attrValue1, attrValue2, attrValue3) {          var m = false;
162          attrs[attrName] = attrValue1 || attrValue2 || attrValue3;          e = e.replace (/^\s*([^\s=]+)\s*(?:=\s*(?:"([^"]*)"|'([^']*)'|([^"'\s]*)))?/,
163          return '';          function (x, attrName, attrValue1, attrValue2, attrValue3) {
164        });            v = attrValue1 || attrValue2 || attrValue3;
165              v = v.replace (/&quot;/g, '"').replace (/&apos;/g, "'")
166                  .replace (/&amp;/g, '&');
167              attrs[attrName.toLowerCase ()] = v;
168              m = true;
169              return '';
170            });
171            if (!m) break;
172          }
173          if (e.length) {
174            log ('Broken start tag: "' + e + '"');
175          }
176        token = {type: 'start-tag', value: tagName, attrs: attrs};        token = {type: 'start-tag', value: tagName, attrs: attrs};
177        p.insertionPoint -= s.length;        p.insertionPoint -= s.length;
178        return '';        return '';
# Line 156  Line 203 
203    } // getNextToken    } // getNextToken
204    
205    Parser.prototype.parse = function () {    Parser.prototype.parse = function () {
206      log ('start parsing');      logIndentLevel++;
207        log ('parse: start');
208    
209      while (true) {      while (true) {
210        var token = this.getNextToken ();        var token = this.getNextToken ();
# Line 224  Line 272 
272            this.openElements[this.openElements.length - 1].appendChild (el);            this.openElements[this.openElements.length - 1].appendChild (el);
273    
274            // 11. Let the insertion point have the value of the old ...            // 11. Let the insertion point have the value of the old ...
275    
276            oldInsertionPoint += this.insertionPoint;            oldInsertionPoint += this.insertionPoint;
277            this.setInsertionPoint (oldInsertionPoint);            this.setInsertionPoint (oldInsertionPoint);
278    
279            // 12. If there is a script that will execute as soon as ...            // 12. If there is a script that will execute as soon as ...
280                        while (this.scriptExecutedWhenParserResumes) {
281                // 12.1. If the tree construction stage is being called reentrantly
282                if (this.reentrant) {
283                  log ('parse: abort (reentrance)');
284                  logIndentLevel--;
285                  return;
286    
287                // 12.2. Otherwise
288                } else {
289                  // 1.
290                  var script = this.scriptExecutedWhenParserResumes;
291                  this.scriptExecutedWhenParserResumes = null;
292    
293                  // 2. Pause until the script has completed loading.
294                  //
295    
296                  // 3. Let the insertion point to just before the next input char.
297                  this.setInsertionPoint (0);
298    
299                  // 4. Execute the script.
300                  executeScript (this.doc, script);
301    
302                  // 5. Let the insertion point be undefined again.
303                  this.setInsertionPoint (undefined);
304    
305                  // 6. If there is once again a script that will execute ...
306                  //
307                }
308              }
309          } else {          } else {
310            var el = new JSElement (this.doc, token.value);            var el = new JSElement (this.doc, token.value);
311            this.openElements[this.openElements.length - 1].appendChild (el);            this.openElements[this.openElements.length - 1].appendChild (el);
# Line 249  Line 325 
325          break;          break;
326        } else if (token.type == 'abort') {        } else if (token.type == 'abort') {
327          log ('parse: abort');          log ('parse: abort');
328            logIndentLevel--;
329          return;          return;
330        }        }
331      }      }
# Line 259  Line 336 
336    
337      // "When a script completes loading" rules start applying.      // "When a script completes loading" rules start applying.
338    
339      // TODO: Handles "list of scripts that will execute as soon as possible"      while (this.scriptsExecutedSoon.length > 0 ||
340      // and "list of scripts that will execute asynchronously"             this.scriptsExecutedAsynchronously.length > 0) {
341          // Handle "list of scripts that will execute as soon as possible".
342          while (this.scriptsExecutedSoon.length > 0) {
343            var e = this.scriptsExecutedSoon.shift ();
344      
345            // If it has completed loading
346            log ('Execute an external script not inserted by parser...');
347            executeScript (this.doc, e);
348    
349            // NOTE: It MAY be executed before the end of the parsing, according
350            // to the spec.
351            this.hasAsyncScript = true;
352          }
353    
354          // Handle "list of scripts that will execute asynchronously".
355          while (this.scriptsExecutedAsynchronously.length > 0) {
356            var e = this.scriptsExecutedAsynchronously.shift ();
357    
358            // Step 1.
359            // We assume that all scripts have been loaded at this time.
360      
361            // Step 2.
362            log ('Execute an asynchronous script...');
363            executeScript (this.doc, e);
364    
365            // Step 3.
366            //
367    
368            // Step 4.
369            //
370    
371            this.hasAsyncScript = true;
372          }
373        }
374    
375      // Handle "list of scripts that will execute when the document has finished      // Handle "list of scripts that will execute when the document has finished
376      // parsing".      // parsing".
# Line 283  Line 393 
393      // "delays tha load event" things has completed:      // "delays tha load event" things has completed:
394      // readyState = 'complete'      // readyState = 'complete'
395      log ('load event fired');      log ('load event fired');
396    
397        logIndentLevel--;
398    } // parse    } // parse
399    
400    Parser.prototype.setInsertionPoint = function (ip) {    Parser.prototype.setInsertionPoint = function (ip) {
401      if (ip == undefined || ip == null || isNaN (ip)) {      if (ip == undefined || ip == null || isNaN (ip)) {
402        log ('insertion point: set to undefined');        log ('insertion point: set to undefined');
403        this.insertionPoint = undefined;        this.insertionPoint = undefined;
404      } else if (ip == this.in.s.length) {      } else if (ip == this.input.s.length) {
405        log ('insertion point: end of file');        log ('insertion point: end of file');
406        this.insertionPoint = ip;        this.insertionPoint = ip;
407      } else {      } else {
408        log ('insertion point: set to ' + ip +        log ('insertion point: set to ' + ip +
409             ' (before "' + this.in.s.substring (0, 10) + '")');             ' (before "' + this.input.s.substring (0, 10) + '")');
410        this.insertionPoint = ip;        this.insertionPoint = ip;
411      }      }
412    }; // setInsertionPoint    }; // setInsertionPoint
# Line 316  Line 428 
428      e.parentNode = this;      e.parentNode = this;
429    
430      if (e.localName == 'script') {      if (e.localName == 'script') {
431          logIndentLevel++;
432        log ('Running a script: start');        log ('Running a script: start');
433    
434        var doc = this.ownerDocument || this;        var doc = this.ownerDocument || this;
# Line 334  Line 447 
447        if (e.manakaiAlreadyExecuted) {        if (e.manakaiAlreadyExecuted) {
448          // 2.5. Abort these steps at this point.          // 2.5. Abort these steps at this point.
449          log ('Running a script: aborted');          log ('Running a script: aborted');
450            logIndentLevel--;
451          return e;          return e;
452        }        }
453    
# Line 351  Line 465 
465          p.scriptsExecutedAfterParsing.push (e);          p.scriptsExecutedAfterParsing.push (e);
466          log ('Running a script: aborted (defer)');          log ('Running a script: aborted (defer)');
467        } else if (e.async && e.src != null) {        } else if (e.async && e.src != null) {
468          // TODO          p.scriptsExecutedAsynchronously.push (e);
469        } else if (e.async && e.src == null          log ('Running a script: aborted (async src)');
470                   /* && list of scripts that will execute asynchronously is not empty */) {        } else if (e.async && e.src == null &&
471          // TODO                   p.scriptsExecutedAsynchronously.length > 0) {
472            p.scriptsExecutedAsynchronously.push (e);
473            log ('Running a script: aborted (async)');
474            // ISSUE: What is the difference with the case above?
475        } else if (e.src != null && e.manakaiParserInserted) {        } else if (e.src != null && e.manakaiParserInserted) {
476          // TODO          if (p.scriptExecutedWhenParserResumes) {
477              log ('Error: There is a script that will execute as soon as the parser resumes.');
478            }
479            p.scriptExecutedWhenParserResumes = e;
480            log ('Running a script: aborted (src parser-inserted)');
481        } else if (e.src != null) {        } else if (e.src != null) {
482          // TODO          p.scriptsExecutedSoon.push (e);
483            log ('Running a script: aborted (src)');
484        } else {        } else {
485          executeScript (doc, e); // even if other scripts are already executing.          executeScript (doc, e); // even if other scripts are already executing.
486        }        }
487    
488        log ('Running a script: end');        log ('Running a script: end');
489          logIndentLevel--;
490      }      }
491    
492      return e;      return e;
# Line 372  Line 495 
495    function executeScript (doc, e) {    function executeScript (doc, e) {
496      log ('executing a script block: start');      log ('executing a script block: start');
497    
498      // If the load resulted in an error, then ... firing an error event ...      var s;
499        if (e.src != null) {
500          s = getExternalScript (e.src);
501    
502          // If the load resulted in an error, then ... firing an error event ...
503          if (s == null) {
504            log ('error event fired at the script element');
505            return;
506          }
507    
508          log ('External script loaded: "' + s + '"');
509        } else {
510          s = e.text;
511        }
512    
513      // If the load was successful      // If the load was successful
514      log ('load event fired at the script element');      log ('load event fired at the script element');
# Line 381  Line 517 
517      // Scripting is enabled, Document.designMode is disabled,      // Scripting is enabled, Document.designMode is disabled,
518      // Document is the active document in its browsing context      // Document is the active document in its browsing context
519    
       var s;  
       if (e.src != null) {  
         // TODO: from external file  
       } else {  
         s = e.text;  
       }  
   
520        parseAndRunScript (doc, s);        parseAndRunScript (doc, s);
521      }      }
522    
523      log ('executing a script block: end');      log ('executing a script block: end');
524    } // executeScript    } // executeScript
525    
526      function getExternalScript (uri) {
527        if (uri.match (/^javascript:/i)) {
528          var m;
529          if (m = uri.match (/^javascript:\s*(?:'([^']*)'|"([^"]+)")\s*$/i)) {
530            if (m[1]) {
531              return unescapeJSLiteral (m[1]);
532            } else if (m[2]) {
533              return unescapeJSLiteral (m[2]);
534            } else {
535              return null;
536            }
537          } else {
538            log ('Complex javascript: URI is not supported: <' + uri + '>');
539            return null;
540          }
541        } else {
542          log ('URI scheme not supported: <' + uri + '>');
543          return null;
544        }
545      } // getExternalScript
546    
547    function parseAndRunScript (doc, s) {    function parseAndRunScript (doc, s) {
548      while (true) {      while (true) {
549        var matched = false;        var matched = false;