"use strict"; const { Preferences } = ChromeUtils.importESModule( "resource://gre/modules/Preferences.sys.mjs" ); // ExtensionContent.jsm needs to know when it's running from xpcshell, // to use the right timeout for content scripts executed at document_idle. ExtensionTestUtils.mockAppInfo(); const server = createHttpServer(); server.registerDirectory("/data/", do_get_file("data")); const BASE_URL = `http://localhost:${server.identity.primaryPort}/data`; var originalReqLocales = Services.locale.requestedLocales; registerCleanupFunction(() => { Preferences.reset("intl.accept_languages"); Services.locale.requestedLocales = originalReqLocales; }); add_task(async function test_i18n() { function runTests(assertEq) { let _ = browser.i18n.getMessage.bind(browser.i18n); let url = browser.runtime.getURL("/"); assertEq( url, `moz-extension://${_("@@extension_id")}/`, "@@extension_id builtin message" ); assertEq("Foo.", _("Foo"), "Simple message in selected locale."); assertEq("(bar)", _("bar"), "Simple message fallback in default locale."); assertEq("", _("some-unknown-locale-string"), "Unknown locale string."); assertEq("", _("@@unknown_builtin_string"), "Unknown built-in string."); assertEq( "", _("@@bidi_unknown_builtin_string"), "Unknown built-in bidi string." ); assertEq("Føo.", _("Föo"), "Multi-byte message in selected locale."); let substitutions = []; substitutions[4] = "5"; substitutions[13] = "14"; assertEq( "'$0' '14' '' '5' '$$$$' '$'.", _("basic_substitutions", substitutions), "Basic numeric substitutions" ); assertEq( "'$0' '' 'just a string' '' '$$$$' '$'.", _("basic_substitutions", "just a string"), "Basic numeric substitutions, with non-array value" ); let values = _("named_placeholder_substitutions", [ "(subst $1 $2)", "(2 $1 $2)", ]).split("\n"); assertEq( "_foo_ (subst $1 $2) _bar_", values[0], "Named and numeric substitution" ); assertEq( "(2 $1 $2)", values[1], "Numeric substitution amid named placeholders" ); assertEq("$bad name$", values[2], "Named placeholder with invalid key"); assertEq("", values[3], "Named placeholder with an invalid value"); assertEq( "Accepted, but shouldn't break.", values[4], "Named placeholder with a strange content value" ); assertEq("$foo", values[5], "Non-placeholder token that should be ignored"); } let extension = ExtensionTestUtils.loadExtension({ manifest: { default_locale: "jp", content_scripts: [ { matches: ["http://*/*/file_sample.html"], js: ["content.js"] }, ], }, files: { "_locales/en_US/messages.json": { foo: { message: "Foo.", description: "foo", }, föo: { message: "Føo.", description: "foo", }, basic_substitutions: { message: "'$0' '$14' '$1' '$5' '$$$$$' '$$'.", description: "foo", }, Named_placeholder_substitutions: { message: "$Foo$\n$2\n$bad name$\n$bad_value$\n$bad_content_value$\n$foo", description: "foo", placeholders: { foO: { content: "_foo_ $1 _bar_", description: "foo", }, "bad name": { content: "Nope.", description: "bad name", }, bad_value: "Nope.", bad_content_value: { content: ["Accepted, but shouldn't break."], description: "bad value", }, }, }, broken_placeholders: { message: "$broken$", description: "broken placeholders", placeholders: "foo.", }, }, "_locales/jp/messages.json": { foo: { message: "(foo)", description: "foo", }, bar: { message: "(bar)", description: "bar", }, }, "content.js": "new " + function (runTestsFn) { runTestsFn((...args) => { browser.runtime.sendMessage(["assertEq", ...args]); }); browser.runtime.sendMessage(["content-script-finished"]); } + `(${runTests})`, }, background: "new " + function (runTestsFn) { browser.runtime.onMessage.addListener(([msg, ...args]) => { if (msg == "assertEq") { browser.test.assertEq(...args); } else { browser.test.sendMessage(msg, ...args); } }); runTestsFn(browser.test.assertEq.bind(browser.test)); } + `(${runTests})`, }); await extension.startup(); let contentPage = await ExtensionTestUtils.loadContentPage( `${BASE_URL}/file_sample.html` ); await extension.awaitMessage("content-script-finished"); await contentPage.close(); await extension.unload(); }); add_task(async function test_i18n_negotiation() { function runTests(expected) { let _ = browser.i18n.getMessage.bind(browser.i18n); browser.test.assertEq(expected, _("foo"), "Got expected message"); } let extensionData = { manifest: { default_locale: "en_US", content_scripts: [ { matches: ["http://*/*/file_sample.html"], js: ["content.js"] }, ], }, files: { "_locales/en_US/messages.json": { foo: { message: "English.", description: "foo", }, }, "_locales/jp/messages.json": { foo: { message: "\u65e5\u672c\u8a9e", description: "foo", }, }, "content.js": "new " + function (runTestsFn) { browser.test.onMessage.addListener(expected => { runTestsFn(expected); browser.test.sendMessage("content-script-finished"); }); browser.test.sendMessage("content-ready"); } + `(${runTests})`, }, background: "new " + function (runTestsFn) { browser.test.onMessage.addListener(expected => { runTestsFn(expected); browser.test.sendMessage("background-script-finished"); }); } + `(${runTests})`, }; // At the moment extension language negotiation is tied to Firefox language // negotiation result. That means that to test an extension in `fr`, we need // to mock `fr` being available in Firefox and then request it. // // In the future, we should provide some way for tests to decouple their // language selection from that of Firefox. Services.locale.availableLocales = ["en-US", "fr", "jp"]; let contentPage = await ExtensionTestUtils.loadContentPage( `${BASE_URL}/file_sample.html` ); for (let [lang, msg] of [ ["en-US", "English."], ["jp", "\u65e5\u672c\u8a9e"], ]) { Services.locale.requestedLocales = [lang]; let extension = ExtensionTestUtils.loadExtension(extensionData); await extension.startup(); await extension.awaitMessage("content-ready"); extension.sendMessage(msg); await extension.awaitMessage("background-script-finished"); await extension.awaitMessage("content-script-finished"); await extension.unload(); } Services.locale.requestedLocales = originalReqLocales; await contentPage.close(); }); add_task(async function test_get_accept_languages() { function checkResults(source, results, expected) { browser.test.assertEq( expected.length, results.length, `got expected number of languages in ${source}` ); results.forEach((lang, index) => { browser.test.assertEq( expected[index], lang, `got expected language in ${source}` ); }); } function background(checkResultsFn) { browser.test.onMessage.addListener(([msg, expected]) => { browser.i18n.getAcceptLanguages().then(results => { checkResultsFn("background", results, expected); browser.test.sendMessage("background-done"); }); }); } function content(checkResultsFn) { browser.test.onMessage.addListener(([msg, expected]) => { browser.i18n.getAcceptLanguages().then(results => { checkResultsFn("contentScript", results, expected); browser.test.sendMessage("content-done"); }); }); browser.test.sendMessage("content-loaded"); } let extension = ExtensionTestUtils.loadExtension({ manifest: { content_scripts: [ { matches: ["http://*/*/file_sample.html"], run_at: "document_start", js: ["content_script.js"], }, ], }, background: `(${background})(${checkResults})`, files: { "content_script.js": `(${content})(${checkResults})`, }, }); let contentPage = await ExtensionTestUtils.loadContentPage( `${BASE_URL}/file_sample.html` ); await extension.startup(); await extension.awaitMessage("content-loaded"); // TODO bug 1765375: ", en" is missing on Android. let expectedLangs = AppConstants.platform == "android" ? ["en-US"] : ["en-US", "en"]; extension.sendMessage(["expect-results", expectedLangs]); await extension.awaitMessage("background-done"); await extension.awaitMessage("content-done"); expectedLangs = ["en-US", "en", "fr-CA", "fr"]; Preferences.set("intl.accept_languages", expectedLangs.toString()); extension.sendMessage(["expect-results", expectedLangs]); await extension.awaitMessage("background-done"); await extension.awaitMessage("content-done"); Preferences.reset("intl.accept_languages"); await contentPage.close(); await extension.unload(); }); add_task(async function test_get_ui_language() { function getResults() { return { getUILanguage: browser.i18n.getUILanguage(), getMessage: browser.i18n.getMessage("@@ui_locale"), }; } function checkResults(source, results, expected) { browser.test.assertEq( expected, results.getUILanguage, `Got expected getUILanguage result in ${source}` ); browser.test.assertEq( expected, results.getMessage, `Got expected getMessage result in ${source}` ); } function background(getResultsFn, checkResultsFn) { browser.test.onMessage.addListener(([msg, expected]) => { checkResultsFn("background", getResultsFn(), expected); browser.test.sendMessage("background-done"); }); } function content(getResultsFn, checkResultsFn) { browser.test.onMessage.addListener(([msg, expected]) => { checkResultsFn("contentScript", getResultsFn(), expected); browser.test.sendMessage("content-done"); }); browser.test.sendMessage("content-loaded"); } let extension = ExtensionTestUtils.loadExtension({ manifest: { content_scripts: [ { matches: ["http://*/*/file_sample.html"], run_at: "document_start", js: ["content_script.js"], }, ], }, background: `(${background})(${getResults}, ${checkResults})`, files: { "content_script.js": `(${content})(${getResults}, ${checkResults})`, }, }); let contentPage = await ExtensionTestUtils.loadContentPage( `${BASE_URL}/file_sample.html` ); await extension.startup(); await extension.awaitMessage("content-loaded"); extension.sendMessage(["expect-results", "en-US"]); await extension.awaitMessage("background-done"); await extension.awaitMessage("content-done"); // We don't currently have a good way to mock this. if (false) { Services.locale.requestedLocales = ["he"]; extension.sendMessage(["expect-results", "he"]); await extension.awaitMessage("background-done"); await extension.awaitMessage("content-done"); } await contentPage.close(); await extension.unload(); }); add_task(async function test_detect_language() { const af_string = " aam skukuza die naam beteken hy wat skoonvee of hy wat alles onderstebo keer wysig " + "bosveldkampe boskampe is kleiner afgeleë ruskampe wat oor min fasiliteite beskik daar is geen restaurante " + "of winkels nie en slegs oornagbesoekers word toegelaat bateleur"; // String with intermixed French/English text const fr_en_string = "France is the largest country in Western Europe and the third-largest in Europe as a whole. " + "A accès aux chiens et aux frontaux qui lui ont été il peut consulter et modifier ses collections et exporter " + "Cet article concerne le pays européen aujourd’hui appelé République française. Pour d’autres usages du nom France, " + "Pour une aide rapide et effective, veuiller trouver votre aide dans le menu ci-dessus." + "Motoring events began soon after the construction of the first successful gasoline-fueled automobiles. The quick brown fox jumped over the lazy dog"; function checkResult(source, result, expected) { browser.test.assertEq( expected.isReliable, result.isReliable, "result.confident is true" ); browser.test.assertEq( expected.languages.length, result.languages.length, `result.languages contains the expected number of languages in ${source}` ); expected.languages.forEach((lang, index) => { browser.test.assertEq( lang.percentage, result.languages[index].percentage, `element ${index} of result.languages array has the expected percentage in ${source}` ); browser.test.assertEq( lang.language, result.languages[index].language, `element ${index} of result.languages array has the expected language in ${source}` ); }); } function backgroundScript(checkResultFn) { browser.test.onMessage.addListener(([msg, expected]) => { browser.i18n.detectLanguage(msg).then(result => { checkResultFn("background", result, expected); browser.test.sendMessage("background-done"); }); }); } function content(checkResultFn) { browser.test.onMessage.addListener(([msg, expected]) => { browser.i18n.detectLanguage(msg).then(result => { checkResultFn("contentScript", result, expected); browser.test.sendMessage("content-done"); }); }); browser.test.sendMessage("content-loaded"); } let extension = ExtensionTestUtils.loadExtension({ manifest: { content_scripts: [ { matches: ["http://*/*/file_sample.html"], run_at: "document_start", js: ["content_script.js"], }, ], }, background: `(${backgroundScript})(${checkResult})`, files: { "content_script.js": `(${content})(${checkResult})`, }, }); let contentPage = await ExtensionTestUtils.loadContentPage( `${BASE_URL}/file_sample.html` ); await extension.startup(); await extension.awaitMessage("content-loaded"); let expected = { isReliable: true, languages: [ { language: "fr", percentage: 67, }, { language: "en", percentage: 32, }, ], }; extension.sendMessage([fr_en_string, expected]); await extension.awaitMessage("background-done"); await extension.awaitMessage("content-done"); expected = { isReliable: true, languages: [ { language: "af", percentage: 99, }, ], }; extension.sendMessage([af_string, expected]); await extension.awaitMessage("background-done"); await extension.awaitMessage("content-done"); await contentPage.close(); await extension.unload(); });