Skip to content
This repository has been archived by the owner on Mar 13, 2018. It is now read-only.

Commit

Permalink
Fix issues with innerHTML in plaintext like elements
Browse files Browse the repository at this point in the history
This also cleans up innerHTML escaping for attributes, text nodes
and comments.
  • Loading branch information
arv committed Jan 14, 2014
1 parent 492935d commit 3d7c676
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 30 deletions.
91 changes: 61 additions & 30 deletions src/wrappers/HTMLElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,51 +19,80 @@
/////////////////////////////////////////////////////////////////////////////
// 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) {
case '&':
return '&amp;';
case '<':
return '&lt;';
case '>':
return '&gt;';
case '"':
return '&quot;'
case '\u00A0':
return '&nbsp;';
}
}

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])
Expand All @@ -72,10 +101,14 @@
return s + getInnerHTML(node) + '</' + tagName + '>';

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 '<!--' + escape(node.nodeValue) + '-->';
return '<!--' + node.data + '-->';

default:
console.error(node);
throw new Error('not implemented');
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
Expand Down
21 changes: 21 additions & 0 deletions test/js/HTMLElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,25 @@ suite('HTMLElement', function() {
assert.equal(div.offsetWidth, 120);
});

test('script innerHTML', function() {
var script = document.createElement('script');
var html = '<x>{{y}}</x>';
script.innerHTML = html;
assert.equal(script.innerHTML, html);
});

test('script textContent', function() {
var script = document.createElement('script');
var html = '<x>{{y}}</x>';
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, '<!--&\u00A0<>"-->');
});

});

0 comments on commit 3d7c676

Please sign in to comment.