--- markup/html/scripting-parser/parser.html 2008/04/29 02:50:00 1.14 +++ markup/html/scripting-parser/parser.html 2008/09/20 13:32:45 1.20 @@ -65,9 +65,11 @@ var logIndentLevel = 0; function log (s) { + var indent = ''; for (var i = 0; i < logIndentLevel; i++) { - s = ' ' + s; + indent += ' '; } + s = indent + s.replace (/\n/g, "\n" + indent); document.logElement.appendChild (document.createTextNode (s + "\n")); } // log @@ -81,6 +83,7 @@ doc = new JSDocument (this); doc.manakaiIsHTML = true; } + this.nextToken = []; this.doc = doc; this.openElements = [doc]; this.input = i; @@ -90,6 +93,10 @@ } // Parser Parser.prototype.getNextToken = function () { + if (this.nextToken.length) { + return this.nextToken.shift (); + } + var p = this; var i = this.input; if (this.parseMode == 'cdata') { @@ -144,7 +151,7 @@ i.s = i.s.replace (/^<\/([^>]+)(?:>|$)/, function (s, e) { if (p.insertionPoint < s.length || (p.insertionPoint <= s.length && - s.substring (s.length - 1, 1) != '>')) { + s.substring (s.length - 1, s.length) != '>')) { token = {type: 'abort'}; return s; } @@ -156,7 +163,7 @@ i.s = i.s.replace (/^<([^>]+)(?:>|$)/, function (s, e) { if (p.insertionPoint < s.length || (p.insertionPoint <= s.length && - s.substring (s.length - 1, 1) != '>')) { + s.substring (s.length - 1, s.length) != '>')) { token = {type: 'abort'}; return s; } @@ -219,6 +226,24 @@ var token = this.getNextToken (); log ('token: ' + token.type + ' "' + token.value + '"'); + if (this.cdataEndTagRequired) { + // Generic CDATA parsing algorithm + + if (token.type != 'abort') { + // 7. + if (token.type == 'end-tag' && token.value == this.endTagName) { + // 7.1. Ignores it. + // + } else { + // 7.2. Parse error. + log ('Parse error: no '); + this.nextToken.unshift (token); + } + this.cdataEndTagRequired = false; + continue; + } + } + if (token.type == 'start-tag') { if (token.value == 'script') { // 1. Create an element for the token in the HTML namespace. @@ -255,6 +280,7 @@ if (!(token.type == 'end-tag' && token.value == 'script')) { // 7.2. This is a parse error. log ('Parse error: no '); + this.nextToken.unshift (token); // 7.3. Mark the script element as "already executed". el.manakaiAlreadyExecuted = true; @@ -269,7 +295,7 @@ // 8.1. If the parser were originally created for the ... if (this.fragmentParsingMode) { // 8.2. Mark the script element as "already executed" and ... - el.alreadyExecuted = true; + el.manakaiAlreadyExecuted = true; continue; } @@ -286,8 +312,8 @@ oldInsertionPoint += this.insertionPoint; this.setInsertionPoint (oldInsertionPoint); - // 12. If there is a script that will execute as soon as ... - while (this.scriptExecutedWhenParserResumes) { + // 12. If there is a pending external script + while (this.pendingExternalScript) { // 12.1. If the tree construction stage is being called reentrantly if (this.reentrant) { log ('parse: abort (reentrance)'); @@ -297,8 +323,8 @@ // 12.2. Otherwise } else { // 1. - var script = this.scriptExecutedWhenParserResumes; - this.scriptExecutedWhenParserResumes = null; + var script = this.pendingExternalScript; + this.pendingExternalScript = null; // 2. Pause until the script has completed loading. // @@ -346,11 +372,17 @@ // 6. Switched back to the PCDATA state. this.parseMode = 'pcdata'; + if (token.type == 'abort') { + this.cdataEndTagRequired = true; + break; + } + // 7.1. If the next token is not an end tag token with ... if (!(token.type == 'end-tag' && token.value == this.endTagName)) { // 7.2. This is a parse error. log ('Parse error: no '); + this.nextToken.unshift (token); // 7.3. Mark the script element as "already executed". el.manakaiAlreadyExecuted = true; @@ -489,54 +521,60 @@ var doc = this.ownerDocument || this; var p = doc._parser; - // 1. Script type + // 1.The script's type + // + + // 2. The cript's character encoding // - // 2.1. If scripting is disabled + // 3.1. If without script // // 2.2. If the script element was created by an XML ... innerHTML ... // // 2.3. If the user agent does not support the scripting language ... // - // 2.4. If the script element has its "already executed" flag set - if (e.manakaiAlreadyExecuted) { + if (false) { // 2.5. Abort these steps at this point. - log ('Running a script: aborted'); + log ('Running a script: aborted (noscript)'); logIndentLevel--; return e; } - // 3. Set the element's "already executed" flag. + // 4. Set the element's "already executed" flag. e.manakaiAlreadyExecuted = true; - // 4. If the element has a src attribute, then a load for ... + // 5. If the element has a src attribute, then a load for ... // TODO: load an external resource // 5. The first of the following options: - // 5.1. if (/* TODO: If the document is still being parsed && */ e.defer && !e.async) { + // 6.1. p.scriptsExecutedAfterParsing.push (e); log ('Running a script: aborted (defer)'); } else if (e.async && e.src != null) { + // 6.2. p.scriptsExecutedAsynchronously.push (e); log ('Running a script: aborted (async src)'); } else if (e.async && e.src == null && p.scriptsExecutedAsynchronously.length > 0) { + // 6.3. p.scriptsExecutedAsynchronously.push (e); log ('Running a script: aborted (async)'); - // ISSUE: What is the difference with the case above? } else if (e.src != null && e.manakaiParserInserted) { - if (p.scriptExecutedWhenParserResumes) { - log ('Error: There is a script that will execute as soon as the parser resumes.'); + // 6.4. + if (p.pendingExternalScript) { + log ('Error: There is a pending external script.'); } - p.scriptExecutedWhenParserResumes = e; + p.pendingExternalScript = e; log ('Running a script: aborted (src parser-inserted)'); } else if (e.src != null) { + // 6.5. p.scriptsExecutedSoon.push (e); log ('Running a script: aborted (src)'); } else { + // 6.6. executeScript (doc, e); // even if other scripts are already executing. } @@ -566,7 +604,6 @@ } // If the load was successful - log ('load event fired at the script element'); if (true) { // Scripting is enabled, Document.designMode is disabled, @@ -575,6 +612,8 @@ parseAndRunScript (doc, s); } + log ('load event fired at the script element'); + log ('executing a script block: end'); } // executeScript @@ -612,11 +651,22 @@ doc.write.apply (doc, args); return ''; }); - s = s.replace (/^\s*var\s+s\s*=\s*document\.createElement\s*\(\s*['"]script['"]\s*\)\s*;\s*s\.src\s*=\s*(?:'(javascript:[^']*)'|"(javascript:[^"]*)")\s*;\s*document\.documentElement\.appendChild\s*\(\s*s\s*\)\s*;\s*/, + var noDocumentElement = false; + s = s.replace (/^\s*var\s+s\s*=\s*document\.createElement\s*\(\s*['"]script['"]\s*\)\s*;\s*s\.src\s*=\s*(?:'([^']*)'|"([^"]*)")\s*;\s*document\.documentElement\.appendChild\s*\(\s*s\s*\)\s*;\s*/, function (s, t, u) { matched = true; var args = [unescapeJSLiteral (t ? t : u)]; - doc._insertExternalScript.apply (doc, args); + noDocumentElement = !doc._insertExternalScript.apply (doc, args); + return ''; + }); + if (noDocumentElement) { + log ('Script error: documentElement is null'); + break; + } + s = s.replace (/^\s*w\s*\(\s*document\.documentElement\.innerHTML\s*\)\s*;\s*/, + function (s, t) { + matched = true; + log (dumpTree (doc, '')); return ''; }); if (s == '') break; @@ -701,6 +751,7 @@ }; // document.open JSDocument.prototype.write = function () { + log ('document.write: start'); logIndentLevel++; var p = this._parser; @@ -720,10 +771,11 @@ + p.input.s.substring (p.insertionPoint, p.input.s.length); p.insertionPoint += s.length; - // 3. If there is a script that will execute as soon as the parser resumes - if (p.scriptExecutedAfterParserResumes) { + // 3. If there is a pending external script + if (p.pendingExternalScript) { log ('document.write: processed later (there is an unprocessed