Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions tfjs-vis/src/render/heatmap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@ import {assert} from '../util/utils';

import {getDrawArea} from './render_utils';

/**
* Escapes HTML-significant characters so a string is rendered as text rather
* than markup. Mirrors vega-tooltip's default `escapeHTML` sanitizer, which is
* bypassed whenever a custom `sanitize` function is supplied to the tooltip.
*/
export function escapeHTML(value: string): string {
return value.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}

/**
* Renders a heatmap.
*
Expand Down Expand Up @@ -209,8 +222,11 @@ export async function heatmap(
//@ts-ignore
embedOpts.tooltip = {
sanitize: (value: string|number) => {
const valueString = String(value);
return valueString.replace(suffixRegex, '');
// Strip the internal index suffix, then re-apply the HTML escaping
// that vega-tooltip performs by default. Supplying a custom `sanitize`
// bypasses that default, so without this, tick-label strings would be
// interpreted as HTML in the tooltip.
return escapeHTML(String(value).replace(suffixRegex, ''));
}
};
}
Expand Down
18 changes: 17 additions & 1 deletion tfjs-vis/src/render/heatmap_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import * as tf from '@tensorflow/tfjs-core';

import {HeatmapData} from '../types';

import {heatmap} from './heatmap';
import {escapeHTML, heatmap} from './heatmap';

describe('renderHeatmap', () => {
let pixelRatio: number;
Expand Down Expand Up @@ -223,3 +223,19 @@ describe('renderHeatmap', () => {
expect(threw).toBe(true);
});
});

describe('escapeHTML', () => {
it('escapes HTML-significant characters', () => {
expect(escapeHTML('<img src=x onerror="alert(1)">'))
.toEqual(
'&lt;img src=x onerror=&quot;alert(1)&quot;&gt;');
});

it('escapes ampersands before other entities', () => {
expect(escapeHTML('a & b < c')).toEqual('a &amp; b &lt; c');
});

it('leaves plain tick labels unchanged', () => {
expect(escapeHTML('alpha')).toEqual('alpha');
});
});