Skip to content

Commit

Permalink
Bug 994357 - Use DOM overlays for safely setting node content. r=gandalf
Browse files Browse the repository at this point in the history
  • Loading branch information
stasm committed Mar 29, 2015
1 parent 67ad91f commit 7716352
Show file tree
Hide file tree
Showing 13 changed files with 950 additions and 173 deletions.
21 changes: 14 additions & 7 deletions apps/sharedtest/test/unit/l10n/bindings/l10n_test.js
Expand Up @@ -96,7 +96,7 @@ suite('L10n bindings', function() {
var elem;

suiteSetup(function() {
elem = document.createElement('div');
elem = document.createElement('input');
elem.setAttribute('data-l10n-id', 'input');
});

Expand Down Expand Up @@ -211,6 +211,7 @@ suite('L10n bindings', function() {
var elem, frag, h1, h2;

suiteSetup(function() {
lang = 'lang2';
elem = document.createElement('div');
frag = document.createElement('div');
h1 = document.createElement('h1');
Expand All @@ -224,13 +225,19 @@ suite('L10n bindings', function() {
document.body.appendChild(frag);
});

suiteTeardown(function() {
suiteTeardown(function(done) {
document.body.removeChild(frag);
lang = 'lang1';
navigator.mozL10n.ctx.addEventListener('ready', function onReady() {
navigator.mozL10n.ctx.removeEventListener('ready', onReady);
done();
});
navigator.mozL10n.ctx.requestLocales(lang);
});

test('retranslation', function(done) {
lang = 'lang2';
navigator.mozL10n.once(function() {
navigator.mozL10n.ctx.addEventListener('ready', function onReady() {
navigator.mozL10n.ctx.removeEventListener('ready', onReady);
setTimeout(function() {
assert.equal(elem.textContent, 'Crop Lang2');
assert.equal(h1.textContent, 'Header 1 Lang2');
Expand All @@ -240,7 +247,7 @@ suite('L10n bindings', function() {
});
});

navigator.mozL10n.language.code = 'lang2';
navigator.mozL10n.ctx.requestLocales(lang);
});
});
});
Expand All @@ -250,8 +257,8 @@ suite('L10n bindings', function() {
var elem = document.createElement('div');
elem.setAttribute('data-l10n-id', 'bad');
navigator.mozL10n.translateFragment(elem);
assert.equal(elem.getAttribute('title', 'alert(1)'));
assert.equal(elem.getAttribute('onclick', undefined));
assert.equal(elem.getAttribute('title'), 'alert(1)');
assert.equal(elem.getAttribute('onclick'), undefined);
});
});

Expand Down
261 changes: 261 additions & 0 deletions apps/sharedtest/test/unit/l10n/bindings/overlay_test.js
@@ -0,0 +1,261 @@
'use strict';

var strings = {
'lang1': [
'text-em = Hello, <em title="WORLD">world</em>!',
'text-em-sup = Hello, <em title="WORLD">world</em><sup>(foo)</sup>!',
'text-nbsp = Hello,&nbsp;world!',

'struct-a = Read <a title="MORE">more</a>.',
'struct-em-a = <em>Read</em> <a>more</a>.',
'struct-a-a = Read <a>more</a> or <a>cancel</a>.',
'struct-button = <button>Submit</button>',
'struct-button-a = <button>Submit</button> or <a>cancel</a>.',
'struct-a-button = <a>Cancel</a> or <button>submit</button>.',
'struct-input-input = <input placeholder="Type here"> and ' +
'<input value="send">.',

'filter-button = Hello, <button title="WORLD">world</button>!',
'filter-em-onclick = Hello, <em onclick="alert(2)">world</em>!',
'filter-button-onclick = Hello, <button onclick="alert(2)">world</button>!',
'filter-href = Read <a href="#B">more</a>.',
'filter-input-value = <input value="Other value">',
'filter-input-type = <input type="submit" value="Submit"> the form.',
],
'lang2': [
'struct-a = Czytaj <a>więcej</a>.'
]
};

suite('L10n DOM overlays', function() {
var xhr;
var lang;

// Do not begin tests until the test locale has been loaded.
suiteSetup(function(done) {
xhr = sinon.useFakeXMLHttpRequest();

xhr.onCreate = function(request) {
setTimeout(function() {
request.respond(200, {}, strings[lang].join('\n'));
}, 0);
};

// en-US has already been loaded in setup.js and l10n.js is smart enough
// not to re-fetch resources; hence, set the lang to something new
lang = 'lang1';

navigator.mozL10n.ctx.registerLocales('en-US', ['lang1', 'lang2']);

navigator.mozL10n.language.code = lang;
navigator.mozL10n.once(done);
});

suiteTeardown(function() {
xhr.restore();
});

suite('text-level semantics', function() {
var elem;

setup(function() {
elem = document.createElement('div');
});

test('em is allowed', function() {
navigator.mozL10n.setAttributes(elem, 'text-em');
navigator.mozL10n.translateFragment(elem);
assert.equal(elem.innerHTML, 'Hello, <em title="WORLD">world</em>!');
});

test('em and sup are both allowed', function() {
navigator.mozL10n.setAttributes(elem, 'text-em-sup');
navigator.mozL10n.translateFragment(elem);
assert.equal(
elem.innerHTML, 'Hello, <em title="WORLD">world</em><sup>(foo)</sup>!');
});

test('nbsp is allowed', function() {
navigator.mozL10n.setAttributes(elem, 'text-nbsp');
navigator.mozL10n.translateFragment(elem);
assert.equal(elem.innerHTML, 'Hello,&nbsp;world!');
});

});

suite('structural overlay', function() {
var elem;

setup(function() {
elem = document.createElement('div');
});

test('href is overlaid', function() {
elem.innerHTML = '<a href="#A" title="TITLE">A</a>';
navigator.mozL10n.setAttributes(elem, 'struct-a');
navigator.mozL10n.translateFragment(elem);
assert.equal(
elem.innerHTML,
'Read <a href="#A" title="MORE">more</a>.');
});

test('href is overlaid onto a', function() {
elem.innerHTML = '<a href="#A" title="TITLE">A</a>';
navigator.mozL10n.setAttributes(elem, 'struct-em-a');
navigator.mozL10n.translateFragment(elem);
assert.equal(
elem.innerHTML,
'<em>Read</em> <a href="#A" title="TITLE">more</a>.');
});

test('hrefs are overlaid onto two a\'s', function() {
elem.innerHTML = '<a href="#A">A</a> <a href="#B">B</a>';
navigator.mozL10n.setAttributes(elem, 'struct-a-a');
navigator.mozL10n.translateFragment(elem);
assert.equal(
elem.innerHTML,
'Read <a href="#A">more</a> or <a href="#B">cancel</a>.');
});

test('button is overlaid', function() {
elem.innerHTML = '<button onclick="alert(1)">BUTTON</button>';
navigator.mozL10n.setAttributes(elem, 'struct-button');
navigator.mozL10n.translateFragment(elem);
assert.equal(
elem.innerHTML,
'<button onclick="alert(1)">Submit</button>');
});

test('button and a are overlaid', function() {
elem.innerHTML =
'<button onclick="alert(1)">BUTTON</button> <a href="#A">A</a>';
navigator.mozL10n.setAttributes(elem, 'struct-button-a');
navigator.mozL10n.translateFragment(elem);
assert.equal(
elem.innerHTML,
'<button onclick="alert(1)">Submit</button> or ' +
'<a href="#A">cancel</a>.');
});

test('a and button are overlaid', function() {
elem.innerHTML =
'<button onclick="alert(1)">BUTTON</button> <a href="#A">A</a>';
navigator.mozL10n.setAttributes(elem, 'struct-a-button');
navigator.mozL10n.translateFragment(elem);
assert.equal(
elem.innerHTML,
'<a href="#A">Cancel</a> or ' +
'<button onclick="alert(1)">submit</button>.');
});

test('inputs are overlaid', function() {
elem.innerHTML =
'<input type="text"> <input type="submit" onclick="alert(1)">';
navigator.mozL10n.setAttributes(elem, 'struct-input-input');
navigator.mozL10n.translateFragment(elem);
assert.equal(
elem.innerHTML,
'<input placeholder="Type here" type="text"> and ' +
'<input value="send" onclick="alert(1)" type="submit">.');
});

});

suite('filtering', function() {
var elem;

setup(function() {
elem = document.createElement('div');
});

test('button is not allowed', function() {
navigator.mozL10n.setAttributes(elem, 'filter-button');
navigator.mozL10n.translateFragment(elem);
assert.equal(elem.innerHTML, 'Hello, world!');
});

test('onclick on em is not allowed', function() {
navigator.mozL10n.setAttributes(elem, 'filter-em-onclick');
navigator.mozL10n.translateFragment(elem);
assert.equal(elem.innerHTML, 'Hello, <em>world</em>!');
});

test('onclick on button is not allowed', function() {
elem.innerHTML = '<button onclick="alert(1)"></button>';
navigator.mozL10n.setAttributes(elem, 'filter-button-onclick');
navigator.mozL10n.translateFragment(elem);
assert.equal(
elem.innerHTML,
'Hello, <button onclick="alert(1)">world</button>!');
});

test('href on a is not allowed', function() {
elem.innerHTML = '<a href="#A">A</a>';
navigator.mozL10n.setAttributes(elem, 'filter-href');
navigator.mozL10n.translateFragment(elem);
assert.equal(
elem.innerHTML,
'Read <a href="#A">more</a>.');
});

test('value on text input not allowed', function() {
elem.innerHTML = '<input type="text" value="INPUT">';
navigator.mozL10n.setAttributes(elem, 'filter-input-value');
navigator.mozL10n.translateFragment(elem);
assert.equal(
elem.innerHTML,
'<input value="INPUT" type="text">');
});

test('type on input not allowed', function() {
elem.innerHTML = '<input type="text" placeholder="INPUT">';
navigator.mozL10n.setAttributes(elem, 'filter-input-type');
navigator.mozL10n.translateFragment(elem);
assert.equal(
elem.innerHTML,
'<input placeholder="INPUT" type="text"> the form.');
});

});

suite('Language change', function() {
var elem;

suiteSetup(function() {
lang = 'lang2';
elem = document.createElement('div');
elem.innerHTML = '<a href="#A"></a>';
navigator.mozL10n.setAttributes(elem, 'struct-a');
});

suiteTeardown(function(done) {
lang = 'lang1';
navigator.mozL10n.ctx.addEventListener('ready', function onReady() {
navigator.mozL10n.ctx.removeEventListener('ready', onReady);
done();
});
navigator.mozL10n.ctx.requestLocales(lang);
});

test('retranslation', function(done) {
navigator.mozL10n.translateFragment(elem);
assert.equal(
elem.innerHTML,
'Read <a title="MORE" href="#A">more</a>.');

navigator.mozL10n.ctx.addEventListener('ready', function onReady() {
navigator.mozL10n.ctx.removeEventListener('ready', onReady);
navigator.mozL10n.translateFragment(elem);
// XXX test known bad behavior; the title attribute leaks from lang1
// into lang2; see https://bugzil.la/922577
assert.equal(
elem.innerHTML,
'Czytaj <a title="MORE" href="#A">więcej</a>.');
done();
});

navigator.mozL10n.ctx.requestLocales(lang);
});
});

});

0 comments on commit 7716352

Please sign in to comment.