Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,132 @@
'use strict';
suite('L10n bootstrap', function() {
suite('buildLocaleList', function() {
var buildLocaleList,
defaultLocale,
realGaiaVersion;
suiteSetup(function() {
var L10n = navigator.mozL10n._getInternalAPI();
defaultLocale = navigator.mozL10n.ctx.defaultLocale;
buildLocaleList = L10n.buildLocaleList.bind(navigator.mozL10n);
realGaiaVersion = navigator.mozL10n._config.appVersion;
navigator.mozL10n._config.appVersion = '2.2';
});
suiteTeardown(function() {
navigator.mozL10n._config.appVersion = realGaiaVersion;
realGaiaVersion = undefined;
defaultLocale = undefined;
buildLocaleList = undefined;
});
test('no meta, no extra', function() {
var meta = {};
var res = buildLocaleList(meta);
assert.equal(res[0], defaultLocale);
var obj = {};
obj[defaultLocale] = 'app';
assert.deepEqual(res[1], obj);
});
test('meta with just defaultLanguage, no extra', function() {
var meta = {defaultLanguage: 'ab-CD'};
var res = buildLocaleList(meta);
assert.equal(res[0], 'ab-CD');
assert.deepEqual(res[1], {'ab-CD': 'app'});
});
test('full meta, simple availableLanguages, no extra', function() {
var meta = {
defaultLanguage: 'ab-CD',
availableLanguages: {'lang1': NaN}
};
var res = buildLocaleList(meta);
assert.equal(res[0], 'ab-CD');
assert.deepEqual(res[1], {'ab-CD': 'app', 'lang1': 'app'});
});
test('full meta, no extra', function() {
var meta = {
defaultLanguage: 'ab-CD',
availableLanguages: {'lang1': 201501151000}
};
var res = buildLocaleList(meta);
assert.equal(res[0], 'ab-CD');
assert.deepEqual(res[1], {'ab-CD': 'app', 'lang1': 'app'});
});
test('full meta, extra, matching target', function() {
var meta = {
defaultLanguage: 'ab-CD',
availableLanguages: {'lang1': 201501151000}
};
var extraLangs = {
'lang2': [
{'revision': 201501151213, 'target': '2.2'}
]
};
var res = buildLocaleList(meta, extraLangs);
assert.equal(res[0], 'ab-CD');
assert.deepEqual(res[1], {
'ab-CD': 'app',
'lang1': 'app',
'lang2': 'extra'
});
});
test('full meta, extra, target not matching', function() {
var meta = {
defaultLanguage: 'ab-CD',
availableLanguages: {'lang1': 201501151000}
};
var extraLangs = {
'lang2': [
{'revision': 201501151213, 'target': '2.3'}
]
};
var res = buildLocaleList(meta, extraLangs);
assert.equal(res[0], 'ab-CD');
assert.deepEqual(res[1], {'ab-CD': 'app', 'lang1': 'app'});
});
test('full meta, extra, updated locale', function() {
var meta = {
defaultLanguage: 'ab-CD',
availableLanguages: {'lang1': 201501151000}
};
var extraLangs = {
'lang1': [
{'revision': 201501151213, 'target': '2.2'}
]
};
var res = buildLocaleList(meta, extraLangs);
assert.equal(res[0], 'ab-CD');
assert.deepEqual(res[1], {'ab-CD': 'app', 'lang1': 'extra'});
});
test('full meta, extra, updated locale obsolete', function() {
var meta = {
defaultLanguage: 'ab-CD',
availableLanguages: {'lang1': 201501161000}
};
var extraLangs = {
'lang1': [
{'revision': 201501151213, 'target': '2.2'}
]
};
var res = buildLocaleList(meta, extraLangs);
assert.equal(res[0], 'ab-CD');
assert.deepEqual(res[1], {'ab-CD': 'app', 'lang1': 'app'});
});
});
});

View file

@ -0,0 +1,265 @@
'use strict';
var strings = {
'lang1': [
'cropimage = Crop',
'cropimage2 = Crop2',
'input.placeholder = Placeholder',
'input2.placeholder = Placeholder 2',
'header1 = Header 1',
'header11 = Header 1.1',
'header2 = Header 2',
'header2.title = Header 2 Title',
'unreadEmails = You have {{ emails }} unread emails',
'bad.title = alert(1)',
'bad.onclick = alert(1)',
],
'lang2': [
'cropimage = Crop Lang2',
'cropimage2 = Crop2 Lang2',
'input = Foo Lang2',
'input.placeholder = Placeholder Lang2',
'header1 = Header 1 Lang2',
'header11 = Header 1.1 Lang2',
'header2 = Header 2 Lang2',
'header2.title = Header 2 Title Lang2',
]
};
suite('L10n bindings', 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('mutations', function() {
suite('element', function() {
var elem;
suiteSetup(function() {
elem = document.createElement('div');
elem.setAttribute('data-l10n-id', 'cropimage');
});
suiteTeardown(function() {
document.body.removeChild(elem);
});
test('insert', function(done) {
document.body.appendChild(elem);
setTimeout(function() {
assert.equal(elem.textContent, 'Crop');
done();
});
});
test('change l10n-id', function(done) {
elem.setAttribute('data-l10n-id', 'cropimage2');
setTimeout(function() {
assert.equal(elem.textContent, 'Crop2');
done();
});
});
test('remove l10n-id', function(done) {
elem.removeAttribute('data-l10n-id');
setTimeout(function() {
assert.equal(elem.textContent, 'Crop2');
done();
});
});
});
suite('attribute', function() {
var elem;
suiteSetup(function() {
elem = document.createElement('input');
elem.setAttribute('data-l10n-id', 'input');
});
suiteTeardown(function() {
document.body.removeChild(elem);
});
test('insert', function(done) {
document.body.appendChild(elem);
setTimeout(function() {
assert.equal(elem.getAttribute('placeholder'), 'Placeholder');
done();
});
});
test('change l10n-id', function(done) {
elem.setAttribute('data-l10n-id', 'input2');
setTimeout(function() {
assert.equal(elem.getAttribute('placeholder'), 'Placeholder 2');
done();
});
});
test('remove l10n-id', function(done) {
elem.removeAttribute('data-l10n-id');
setTimeout(function() {
assert.equal(elem.getAttribute('placeholder'), 'Placeholder 2');
done();
});
});
});
suite('l10n args', function() {
var elem;
suiteSetup(function() {
elem = document.createElement('div');
elem.setAttribute('data-l10n-args', JSON.stringify({
emails: 5
}));
elem.setAttribute('data-l10n-id', 'unreadEmails');
});
suiteTeardown(function() {
document.body.removeChild(elem);
});
test('insert', function(done) {
document.body.appendChild(elem);
setTimeout(function() {
assert.equal(elem.textContent, 'You have 5 unread emails');
done();
});
});
test('change l10n-args', function(done) {
elem.setAttribute('data-l10n-args', JSON.stringify({
emails: 6
}));
setTimeout(function() {
assert.equal(elem.textContent, 'You have 6 unread emails');
done();
});
});
});
suite('DOMFragment', function() {
var frag, h1, h2;
suiteSetup(function() {
frag = document.createElement('div');
h1 = document.createElement('h1');
h2 = document.createElement('h2');
h1.setAttribute('data-l10n-id', 'header1');
h2.setAttribute('data-l10n-id', 'header2');
frag.appendChild(h1);
frag.appendChild(h2);
});
suiteTeardown(function() {
document.body.removeChild(frag);
});
test('insert', function(done) {
document.body.appendChild(frag);
setTimeout(function() {
assert.equal(h1.textContent, 'Header 1');
assert.equal(h2.textContent, 'Header 2');
assert.equal(h2.title, 'Header 2 Title');
done();
});
});
test('change l10n-id', function(done) {
h1.setAttribute('data-l10n-id', 'header11');
setTimeout(function() {
assert.equal(h1.textContent, 'Header 1.1');
done();
});
});
test('remove l10n-id', function(done) {
h2.removeAttribute('data-l10n-id');
setTimeout(function() {
assert.equal(h2.textContent, 'Header 2');
done();
});
});
});
suite('Language change', function() {
var elem, frag, h1, h2;
suiteSetup(function() {
lang = 'lang2';
elem = document.createElement('div');
frag = document.createElement('div');
h1 = document.createElement('h1');
h2 = document.createElement('h2');
elem.setAttribute('data-l10n-id', 'cropimage');
h1.setAttribute('data-l10n-id', 'header1');
h2.setAttribute('data-l10n-id', 'header2');
frag.appendChild(h1);
frag.appendChild(h2);
document.body.appendChild(elem);
document.body.appendChild(frag);
});
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) {
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');
assert.equal(h2.textContent, 'Header 2 Lang2');
assert.equal(h2.title, 'Header 2 Title Lang2');
done();
});
});
navigator.mozL10n.ctx.requestLocales(lang);
});
});
});
suite('whitelisting', function() {
test('does not allow attributes not on the whitelist', 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);
});
});
});

View file

@ -0,0 +1,285 @@
'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-entity-name = Hello &amp; world',
'text-entity-nnnn = Hello &#0038; world',
'text-entity-hhhh = Hello &#x0026; 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.',
'filter-nested-button = <em>No <button>button</button>.</em>',
],
'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('entity name is allowed', function() {
navigator.mozL10n.setAttributes(elem, 'text-entity-name');
navigator.mozL10n.translateFragment(elem);
assert.equal(elem.innerHTML, 'Hello &amp; world');
});
test('entity decimal codepoint is allowed', function() {
navigator.mozL10n.setAttributes(elem, 'text-entity-nnnn');
navigator.mozL10n.translateFragment(elem);
assert.equal(elem.innerHTML, 'Hello &amp; world');
});
test('entity hexadecimal codepoint is allowed', function() {
navigator.mozL10n.setAttributes(elem, 'text-entity-hhhh');
navigator.mozL10n.translateFragment(elem);
assert.equal(elem.innerHTML, 'Hello &amp; 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.');
});
test('nested button is not allowed', function() {
elem.innerHTML = '';
navigator.mozL10n.setAttributes(elem, 'filter-nested-button');
navigator.mozL10n.translateFragment(elem);
assert.equal(
elem.innerHTML,
'<em>No button.</em>');
});
});
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);
});
});
});