diff --git a/src/lib/notifier.js b/src/lib/notifier.js
index 8590f03f62c..e7443afd7a0 100644
--- a/src/lib/notifier.js
+++ b/src/lib/notifier.js
@@ -63,7 +63,12 @@ module.exports = function(text, displayLength) {
note.transition().call(killNote);
});
- note.append('p').html(thisText);
+ var p = note.append('p');
+ var lines = thisText.split(/
/g);
+ for(var i = 0; i < lines.length; i++) {
+ if(i) p.append('br');
+ p.append('span').text(lines[i]);
+ }
note.transition()
.duration(700)
diff --git a/src/lib/svg_text_utils.js b/src/lib/svg_text_utils.js
index 413f1f20ef0..10df69e8679 100644
--- a/src/lib/svg_text_utils.js
+++ b/src/lib/svg_text_utils.js
@@ -48,6 +48,7 @@ exports.html_entity_decode = function(s) {
var replaced = s.replace(/(&[^;]*;)/gi, function(d) {
if(d === '<') { return '<'; } // special handling for brackets
if(d === '&rt;') { return '>'; }
+ if(d.indexOf('<') !== -1 || d.indexOf('>') !== -1) { return ''; }
return hiddenDiv.html(d).text(); // everything else, let the browser decode it to unicode
});
hiddenDiv.remove();
diff --git a/src/plots/cartesian/graph_interact.js b/src/plots/cartesian/graph_interact.js
index 909b10d6411..5c361729a28 100644
--- a/src/plots/cartesian/graph_interact.js
+++ b/src/plots/cartesian/graph_interact.js
@@ -952,12 +952,8 @@ function createHoverText(hoverData, opts) {
if(d.name && d.zLabelVal === undefined) {
- // strip out any html elements from d.name (if it exists at all)
- // Note that this isn't an XSS vector, only because it never gets
- // attached to the DOM
- var tmp = document.createElement('p');
- tmp.innerHTML = d.name;
- name = tmp.textContent || '';
+ // strip out our pseudo-html elements from d.name (if it exists at all)
+ name = svgTextUtils.plainText(d.name || '');
if(name.length > 15) name = name.substr(0, 12) + '...';
}
diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js
index 618551cdaa1..064025b7449 100644
--- a/test/jasmine/tests/hover_label_test.js
+++ b/test/jasmine/tests/hover_label_test.js
@@ -158,6 +158,45 @@ describe('hover info', function() {
});
});
+ describe('hover info with bad name', function() {
+ var mockCopy = Lib.extendDeep({}, mock);
+
+ mockCopy.data[0].text = [];
+ mockCopy.data[0].text[17] = 'hover text';
+ mockCopy.data[0].hoverinfo = 'all';
+ mockCopy.data[0].name = '
';
+ mockCopy.data.push({
+ x: [0.002, 0.004],
+ y: [12.5, 16.25],
+ mode: 'lines+markers',
+ name: 'another trace'
+ });
+
+ beforeEach(function(done) {
+ Plotly.plot(createGraphDiv(), mockCopy.data, mockCopy.layout).then(done);
+ });
+
+ it('cleans the name', function() {
+ var gd = document.getElementById('graph');
+ Fx.hover('graph', evt, 'xy');
+
+ var hoverTrace = gd._hoverdata[0];
+
+ expect(hoverTrace.curveNumber).toEqual(0);
+ expect(hoverTrace.pointNumber).toEqual(17);
+ expect(hoverTrace.x).toEqual(0.388);
+ expect(hoverTrace.y).toEqual(1);
+
+ expect(d3.selectAll('g.axistext').size()).toEqual(1);
+ expect(d3.selectAll('g.hovertext').size()).toEqual(1);
+ expect(d3.selectAll('g.axistext').select('text').html()).toEqual('0.388');
+ expect(d3.selectAll('g.hovertext').select('text.nums').selectAll('tspan').size()).toEqual(2);
+ expect(d3.selectAll('g.hovertext').selectAll('tspan')[0][0].innerHTML).toEqual('1');
+ expect(d3.selectAll('g.hovertext').selectAll('tspan')[0][1].innerHTML).toEqual('hover text');
+ expect(d3.selectAll('g.hovertext').selectAll('text.name').node().innerHTML).toEqual('<img src=x o...');
+ });
+ });
+
describe('hover info y+text', function() {
var mockCopy = Lib.extendDeep({}, mock);