diff --git a/src/wrappers/HTMLElement.js b/src/wrappers/HTMLElement.js index 9d5de61..0878894 100644 --- a/src/wrappers/HTMLElement.js +++ b/src/wrappers/HTMLElement.js @@ -19,7 +19,9 @@ ///////////////////////////////////////////////////////////////////////////// // innerHTML and outerHTML - var escapeRegExp = /&|<|"/g; + // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#escapingString + var escapeAttrRegExp = /[&\u00A0"]/g; + var escapeDataRegExp = /[&\u00A0<>]/g; function escapeReplace(c) { switch (c) { @@ -27,43 +29,70 @@ return '&'; case '<': return '<'; + case '>': + return '>'; case '"': return '"' + case '\u00A0': + return ' '; } } - function escape(s) { - return s.replace(escapeRegExp, escapeReplace); + function escapeAttr(s) { + return s.replace(escapeAttrRegExp, escapeReplace); + } + + function escapeData(s) { + return s.replace(escapeDataRegExp, escapeReplace); + } + + function makeSet(arr) { + var set = {}; + for (var i = 0; i < arr.length; i++) { + set[arr[i]] = true; + } + return set; } // http://www.whatwg.org/specs/web-apps/current-work/#void-elements - var voidElements = { - 'area': true, - 'base': true, - 'br': true, - 'col': true, - 'command': true, - 'embed': true, - 'hr': true, - 'img': true, - 'input': true, - 'keygen': true, - 'link': true, - 'meta': true, - 'param': true, - 'source': true, - 'track': true, - 'wbr': true - }; - - function getOuterHTML(node) { + var voidElements = makeSet([ + 'area', + 'base', + 'br', + 'col', + 'command', + 'embed', + 'hr', + 'img', + 'input', + 'keygen', + 'link', + 'meta', + 'param', + 'source', + 'track', + 'wbr' + ]); + + var plaintextParents = makeSet([ + 'style', + 'script', + 'xmp', + 'iframe', + 'noembed', + 'noframes', + 'plaintext', + 'noscript' + ]); + + function getOuterHTML(node, parentNode) { switch (node.nodeType) { case Node.ELEMENT_NODE: var tagName = node.tagName.toLowerCase(); var s = '<' + tagName; var attrs = node.attributes; for (var i = 0, attr; attr = attrs[i]; i++) { - s += ' ' + attr.name + '="' + escape(attr.value) + '"'; + s += ' ' + attr.name + '="' + escapeAttr(attr.value) + '"'; } s += '>'; if (voidElements[tagName]) @@ -72,10 +101,14 @@ return s + getInnerHTML(node) + ''; case Node.TEXT_NODE: - return escape(node.nodeValue); + var data = node.data; + if (parentNode && plaintextParents[parentNode.localName]) + return data; + return escapeData(data); case Node.COMMENT_NODE: - return ''; + return ''; + default: console.error(node); throw new Error('not implemented'); @@ -85,7 +118,7 @@ function getInnerHTML(node) { var s = ''; for (var child = node.firstChild; child; child = child.nextSibling) { - s += getOuterHTML(child); + s += getOuterHTML(child, node); } return s; } @@ -132,9 +165,7 @@ }, get outerHTML() { - // TODO(arv): This should fallback to HTMLElement_prototype.outerHTML if there - // are no shadow trees below or above the context node. - return getOuterHTML(this); + return getOuterHTML(this, this.parentNode); }, set outerHTML(value) { var p = this.parentNode; diff --git a/test/js/HTMLElement.js b/test/js/HTMLElement.js index 66f6166..fe73ea7 100644 --- a/test/js/HTMLElement.js +++ b/test/js/HTMLElement.js @@ -80,4 +80,25 @@ suite('HTMLElement', function() { assert.equal(div.offsetWidth, 120); }); + test('script innerHTML', function() { + var script = document.createElement('script'); + var html = '{{y}}'; + script.innerHTML = html; + assert.equal(script.innerHTML, html); + }); + + test('script textContent', function() { + var script = document.createElement('script'); + var html = '{{y}}'; + script.innerHTML = html; + assert.equal(script.textContent, html); + }); + + test('comment innerHTML', function() { + var div = document.createElement('div'); + var comment = document.createComment('&\u00A0<>"'); + div.appendChild(comment); + assert.equal(div.innerHTML, ''); + }); + });