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

Commit 3d7c676

Browse files
committed
Fix issues with innerHTML in plaintext like elements
This also cleans up innerHTML escaping for attributes, text nodes and comments.
1 parent 492935d commit 3d7c676

2 files changed

Lines changed: 82 additions & 30 deletions

File tree

src/wrappers/HTMLElement.js

Lines changed: 61 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -19,51 +19,80 @@
1919
/////////////////////////////////////////////////////////////////////////////
2020
// innerHTML and outerHTML
2121

22-
var escapeRegExp = /&|<|"/g;
22+
// http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#escapingString
23+
var escapeAttrRegExp = /[&\u00A0"]/g;
24+
var escapeDataRegExp = /[&\u00A0<>]/g;
2325

2426
function escapeReplace(c) {
2527
switch (c) {
2628
case '&':
2729
return '&amp;';
2830
case '<':
2931
return '&lt;';
32+
case '>':
33+
return '&gt;';
3034
case '"':
3135
return '&quot;'
36+
case '\u00A0':
37+
return '&nbsp;';
3238
}
3339
}
3440

35-
function escape(s) {
36-
return s.replace(escapeRegExp, escapeReplace);
41+
function escapeAttr(s) {
42+
return s.replace(escapeAttrRegExp, escapeReplace);
43+
}
44+
45+
function escapeData(s) {
46+
return s.replace(escapeDataRegExp, escapeReplace);
47+
}
48+
49+
function makeSet(arr) {
50+
var set = {};
51+
for (var i = 0; i < arr.length; i++) {
52+
set[arr[i]] = true;
53+
}
54+
return set;
3755
}
3856

3957
// http://www.whatwg.org/specs/web-apps/current-work/#void-elements
40-
var voidElements = {
41-
'area': true,
42-
'base': true,
43-
'br': true,
44-
'col': true,
45-
'command': true,
46-
'embed': true,
47-
'hr': true,
48-
'img': true,
49-
'input': true,
50-
'keygen': true,
51-
'link': true,
52-
'meta': true,
53-
'param': true,
54-
'source': true,
55-
'track': true,
56-
'wbr': true
57-
};
58-
59-
function getOuterHTML(node) {
58+
var voidElements = makeSet([
59+
'area',
60+
'base',
61+
'br',
62+
'col',
63+
'command',
64+
'embed',
65+
'hr',
66+
'img',
67+
'input',
68+
'keygen',
69+
'link',
70+
'meta',
71+
'param',
72+
'source',
73+
'track',
74+
'wbr'
75+
]);
76+
77+
var plaintextParents = makeSet([
78+
'style',
79+
'script',
80+
'xmp',
81+
'iframe',
82+
'noembed',
83+
'noframes',
84+
'plaintext',
85+
'noscript'
86+
]);
87+
88+
function getOuterHTML(node, parentNode) {
6089
switch (node.nodeType) {
6190
case Node.ELEMENT_NODE:
6291
var tagName = node.tagName.toLowerCase();
6392
var s = '<' + tagName;
6493
var attrs = node.attributes;
6594
for (var i = 0, attr; attr = attrs[i]; i++) {
66-
s += ' ' + attr.name + '="' + escape(attr.value) + '"';
95+
s += ' ' + attr.name + '="' + escapeAttr(attr.value) + '"';
6796
}
6897
s += '>';
6998
if (voidElements[tagName])
@@ -72,10 +101,14 @@
72101
return s + getInnerHTML(node) + '</' + tagName + '>';
73102

74103
case Node.TEXT_NODE:
75-
return escape(node.nodeValue);
104+
var data = node.data;
105+
if (parentNode && plaintextParents[parentNode.localName])
106+
return data;
107+
return escapeData(data);
76108

77109
case Node.COMMENT_NODE:
78-
return '<!--' + escape(node.nodeValue) + '-->';
110+
return '<!--' + node.data + '-->';
111+
79112
default:
80113
console.error(node);
81114
throw new Error('not implemented');
@@ -85,7 +118,7 @@
85118
function getInnerHTML(node) {
86119
var s = '';
87120
for (var child = node.firstChild; child; child = child.nextSibling) {
88-
s += getOuterHTML(child);
121+
s += getOuterHTML(child, node);
89122
}
90123
return s;
91124
}
@@ -132,9 +165,7 @@
132165
},
133166

134167
get outerHTML() {
135-
// TODO(arv): This should fallback to HTMLElement_prototype.outerHTML if there
136-
// are no shadow trees below or above the context node.
137-
return getOuterHTML(this);
168+
return getOuterHTML(this, this.parentNode);
138169
},
139170
set outerHTML(value) {
140171
var p = this.parentNode;

test/js/HTMLElement.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,25 @@ suite('HTMLElement', function() {
8080
assert.equal(div.offsetWidth, 120);
8181
});
8282

83+
test('script innerHTML', function() {
84+
var script = document.createElement('script');
85+
var html = '<x>{{y}}</x>';
86+
script.innerHTML = html;
87+
assert.equal(script.innerHTML, html);
88+
});
89+
90+
test('script textContent', function() {
91+
var script = document.createElement('script');
92+
var html = '<x>{{y}}</x>';
93+
script.innerHTML = html;
94+
assert.equal(script.textContent, html);
95+
});
96+
97+
test('comment innerHTML', function() {
98+
var div = document.createElement('div');
99+
var comment = document.createComment('&\u00A0<>"');
100+
div.appendChild(comment);
101+
assert.equal(div.innerHTML, '<!--&\u00A0<>"-->');
102+
});
103+
83104
});

0 commit comments

Comments
 (0)