diff options
Diffstat (limited to '')
54 files changed, 3248 insertions, 0 deletions
diff --git a/dom/l10n/tests/gtest/TestL10nOverlays.cpp b/dom/l10n/tests/gtest/TestL10nOverlays.cpp new file mode 100644 index 0000000000..c6c9da9939 --- /dev/null +++ b/dom/l10n/tests/gtest/TestL10nOverlays.cpp @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" +#include "mozilla/dom/L10nOverlays.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/L10nOverlaysBinding.h" +#include "mozilla/dom/Element.h" +#include "mozilla/NullPrincipal.h" +#include "nsNetUtil.h" + +using mozilla::NullPrincipal; +using namespace mozilla::dom; + +static already_AddRefed<Document> SetUpDocument() { + nsCOMPtr<nsIURI> uri; + NS_NewURI(getter_AddRefs(uri), "about:blank"); + nsCOMPtr<nsIPrincipal> principal = + NullPrincipal::CreateWithoutOriginAttributes(); + nsCOMPtr<Document> document; + nsresult rv = NS_NewDOMDocument(getter_AddRefs(document), + u""_ns, // aNamespaceURI + u""_ns, // aQualifiedName + nullptr, // aDoctype + uri, uri, principal, + false, // aLoadedAsData + nullptr, // aEventObject + DocumentFlavorHTML); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + return document.forget(); +} + +/** + * This test verifies that the basic C++ DOM L10nOverlays API + * works correctly. + */ +TEST(DOM_L10n_Overlays, Initial) +{ + mozilla::ErrorResult rv; + + // 1. Set up an HTML document. + nsCOMPtr<Document> doc = SetUpDocument(); + + // 2. Create a simple Element with a child. + // + // <div> + // <a data-l10n-name="link" href="https://www.mozilla.org"></a> + // </div> + // + RefPtr<Element> elem = doc->CreateHTMLElement(nsGkAtoms::div); + RefPtr<Element> span = doc->CreateHTMLElement(nsGkAtoms::a); + span->SetAttribute(u"data-l10n-name"_ns, u"link"_ns, rv); + span->SetAttribute(u"href"_ns, u"https://www.mozilla.org"_ns, rv); + elem->AppendChild(*span, rv); + + // 3. Create an L10nMessage with a translation for the element. + L10nMessage translation; + translation.mValue.AssignLiteral( + "Hello <a data-l10n-name=\"link\">World</a>."); + + // 4. Translate the element. + nsTArray<L10nOverlaysError> errors; + L10nOverlays::TranslateElement(*elem, translation, errors, rv); + + nsAutoString textContent; + elem->GetInnerHTML(textContent, rv); + + // 5. Verify that the innerHTML matches the expectations. + ASSERT_STREQ(NS_ConvertUTF16toUTF8(textContent).get(), + "Hello <a data-l10n-name=\"link\" " + "href=\"https://www.mozilla.org\">World</a>."); +} diff --git a/dom/l10n/tests/gtest/moz.build b/dom/l10n/tests/gtest/moz.build new file mode 100644 index 0000000000..0e1e2173a6 --- /dev/null +++ b/dom/l10n/tests/gtest/moz.build @@ -0,0 +1,11 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +UNIFIED_SOURCES += [ + "TestL10nOverlays.cpp", +] + +FINAL_LIBRARY = "xul-gtest" diff --git a/dom/l10n/tests/mochitest/browser.ini b/dom/l10n/tests/mochitest/browser.ini new file mode 100644 index 0000000000..966be56661 --- /dev/null +++ b/dom/l10n/tests/mochitest/browser.ini @@ -0,0 +1,6 @@ +[document_l10n/non-system-principal/browser_resource_uri.js] +support-files = + document_l10n/non-system-principal/test.html + document_l10n/non-system-principal/localization/test.ftl +skip-if = + os == "win" && os_version == "6.1" # Skip on Azure - frequent failure diff --git a/dom/l10n/tests/mochitest/chrome.ini b/dom/l10n/tests/mochitest/chrome.ini new file mode 100644 index 0000000000..68f3d89d40 --- /dev/null +++ b/dom/l10n/tests/mochitest/chrome.ini @@ -0,0 +1,47 @@ +[l10n_overlays/test_attributes.html] +[l10n_overlays/test_functional_children.html] +[l10n_overlays/test_text_children.html] +[l10n_overlays/test_extra_text_markup.html] +[l10n_overlays/test_l10n_overlays.xhtml] +[l10n_overlays/test_same_id.html] +[l10n_overlays/test_same_id_args.html] +[l10n_overlays/test_title.html] + +[l10n_mutations/test_append_content_post_dcl.html] +[l10n_mutations/test_append_content_pre_dcl.html] +[l10n_mutations/test_append_fragment_post_dcl.html] +[l10n_mutations/test_disconnectedRoot_webcomponent.html] +[l10n_mutations/test_set_attributes.html] +[l10n_mutations/test_pause_observing.html] +[l10n_mutations/test_template.html] +[l10n_mutations/test_remove_element.html] +[l10n_mutations/test_remove_fragment.html] + +[dom_localization/test_attr_sanitized.html] +[dom_localization/test_getAttributes.html] +[dom_localization/test_setAttributes.html] +[dom_localization/test_translateElements.html] +[dom_localization/test_translateFragment.html] +[dom_localization/test_connectRoot.html] +[dom_localization/test_connectRoot_webcomponent.html] +[dom_localization/test_disconnectRoot.html] +[dom_localization/test_repeated_l10nid.html] +[dom_localization/test_translateRoots.html] +[dom_localization/test_l10n_mutations.html] +[dom_localization/test_overlay.html] +[dom_localization/test_overlay_repeated.html] +[dom_localization/test_overlay_missing_all.html] +[dom_localization/test_overlay_missing_children.html] +[dom_localization/test_overlay_sanitized.html] +[dom_localization/test_domloc.xhtml] + + +[document_l10n/test_docl10n.xhtml] +[document_l10n/test_docl10n.html] +[document_l10n/test_docl10n_sync.html] +[document_l10n/test_docl10n_ready_rejected.html] +[document_l10n/test_docl10n_removeResourceIds.html] +[document_l10n/test_docl10n_lazy.html] +[document_l10n/test_connectRoot_webcomponent.html] +[document_l10n/test_connectRoot_webcomponent_lazy.html] +[document_l10n/test_telemetry.html] diff --git a/dom/l10n/tests/mochitest/document_l10n/README.txt b/dom/l10n/tests/mochitest/document_l10n/README.txt new file mode 100644 index 0000000000..b798a5039a --- /dev/null +++ b/dom/l10n/tests/mochitest/document_l10n/README.txt @@ -0,0 +1,3 @@ +Tests in this directory cover support for DocumentL10n +WebIDL API across different use cases such as +processes, principals and so on. diff --git a/dom/l10n/tests/mochitest/document_l10n/non-system-principal/README.txt b/dom/l10n/tests/mochitest/document_l10n/non-system-principal/README.txt new file mode 100644 index 0000000000..d0cc074166 --- /dev/null +++ b/dom/l10n/tests/mochitest/document_l10n/non-system-principal/README.txt @@ -0,0 +1,3 @@ +Tests in this directory cover the functionality +of DocumentL10n WebIDL API in non-system-principal +scenario. diff --git a/dom/l10n/tests/mochitest/document_l10n/non-system-principal/browser_resource_uri.js b/dom/l10n/tests/mochitest/document_l10n/non-system-principal/browser_resource_uri.js new file mode 100644 index 0000000000..a658e88bec --- /dev/null +++ b/dom/l10n/tests/mochitest/document_l10n/non-system-principal/browser_resource_uri.js @@ -0,0 +1,109 @@ +let uri = + "chrome://mochitests/content/browser/dom/l10n/tests/mochitest//document_l10n/non-system-principal/"; +let protocol = Services.io + .getProtocolHandler("resource") + .QueryInterface(Ci.nsIResProtocolHandler); + +protocol.setSubstitution("l10n-test", Services.io.newURI(uri)); + +// Since we want the mock source to work with all locales, we're going +// to register it for currently used locales, and we'll put the path that +// doesn't use the `{locale}` component to make it work irrelevant of +// what locale the mochitest is running in. +// +// Notice: we're using a `chrome://` protocol here only for convenience reasons. +// Real sources should use `resource://` protocol. +let locales = Services.locale.appLocalesAsBCP47; + +// This source is actually using a real `FileSource` instead of a mocked one, +// because we want to test that fetching real I/O out of the `uri` works in non-system-principal. +let source = new L10nFileSource("test", "app", locales, `${uri}localization/`); +L10nRegistry.getInstance().registerSources([source]); + +registerCleanupFunction(() => { + protocol.setSubstitution("l10n-test", null); + L10nRegistry.getInstance().removeSources(["test"]); + SpecialPowers.pushPrefEnv({ + set: [["dom.ipc.processPrelaunch.enabled", true]], + }); +}); + +const kChildPage = getRootDirectory(gTestPath) + "test.html"; + +const kAboutPagesRegistered = Promise.all([ + BrowserTestUtils.registerAboutPage( + registerCleanupFunction, + "test-about-l10n-child", + kChildPage, + Ci.nsIAboutModule.URI_MUST_LOAD_IN_CHILD | + Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT | + Ci.nsIAboutModule.ALLOW_SCRIPT + ), +]); + +add_task(async () => { + // Bug 1640333 - windows fails (sometimes) to ever get document.l10n.ready + // if e10s process caching is enabled + await SpecialPowers.pushPrefEnv({ + set: [ + ["dom.ipc.processPrelaunch.enabled", false], + ["dom.security.skip_about_page_has_csp_assert", true], + ], + }); + await kAboutPagesRegistered; + await BrowserTestUtils.withNewTab( + "about:test-about-l10n-child", + async browser => { + await SpecialPowers.spawn(browser, [], async function () { + let document = content.document; + let window = document.defaultView; + + await document.testsReadyPromise; + + let principal = SpecialPowers.wrap(document).nodePrincipal; + is( + principal.spec, + "about:test-about-l10n-child", + "correct content principal" + ); + + let desc = document.getElementById("main-desc"); + + // We can test here for a particular value because we're + // using a mock file source which is locale independent. + // + // If you're writing a test that verifies that a UI + // widget got real localization, you should not rely on + // the particular value, but rather on the content not + // being empty (to keep the test pass in non-en-US locales). + is(desc.textContent, "This is a mock page title"); + + // Test for l10n.getAttributes + let label = document.getElementById("label1"); + let l10nArgs = document.l10n.getAttributes(label); + is(l10nArgs.id, "subtitle"); + is(l10nArgs.args.name, "Firefox"); + + // Test for manual value formatting + let customMsg = document.getElementById("customMessage").textContent; + is(customMsg, "This is a custom message formatted from JS."); + + // Since we applied the `data-l10n-id` attribute + // on `label` in this microtask, we have to wait for + // the next paint to verify that the MutationObserver + // applied the translation. + await new Promise(resolve => { + let verifyL10n = () => { + if (!label.textContent.includes("Firefox")) { + window.requestAnimationFrame(verifyL10n); + } else { + resolve(); + } + }; + + window.requestAnimationFrame(verifyL10n); + }); + }); + } + ); +}); diff --git a/dom/l10n/tests/mochitest/document_l10n/non-system-principal/localization/test.ftl b/dom/l10n/tests/mochitest/document_l10n/non-system-principal/localization/test.ftl new file mode 100644 index 0000000000..a5da5a8f00 --- /dev/null +++ b/dom/l10n/tests/mochitest/document_l10n/non-system-principal/localization/test.ftl @@ -0,0 +1,4 @@ +page-title = This is a mock page title +subtitle = This is a label for { $name } + +custom-message = This is a custom message formatted from JS. diff --git a/dom/l10n/tests/mochitest/document_l10n/non-system-principal/test.html b/dom/l10n/tests/mochitest/document_l10n/non-system-principal/test.html new file mode 100644 index 0000000000..5d91f3da46 --- /dev/null +++ b/dom/l10n/tests/mochitest/document_l10n/non-system-principal/test.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test DocumentL10n in HTML environment</title> + <link rel="localization" href="test.ftl"/> + <script type="text/javascript"> + document.testsReadyPromise = new Promise((resolve) => { + // The test is in this file to ensure that we're testing + // the behavior in a non-system principal. + document.addEventListener("DOMContentLoaded", async () => { + await document.l10n.ready; + + // Assign the localization from JS + let label = document.getElementById("label1"); + document.l10n.setAttributes( + label, + "subtitle", + { + name: "Firefox", + } + ); + + const customMsg = await document.l10n.formatValue("custom-message"); + document.getElementById("customMessage").textContent = customMsg; + resolve(); + }, {once: true}); + }); + </script> +</head> +<body> + <h1 id="main-desc" data-l10n-id="page-title"></h1> + + <p id="label1"></p> + <p id="customMessage"></p> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/document_l10n/test_connectRoot_webcomponent.html b/dom/l10n/tests/mochitest/document_l10n/test_connectRoot_webcomponent.html new file mode 100644 index 0000000000..3f2def3547 --- /dev/null +++ b/dom/l10n/tests/mochitest/document_l10n/test_connectRoot_webcomponent.html @@ -0,0 +1,90 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test Web Component connecting into Document's l10n</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <link rel="localization" href="browser/preferences/preferences.ftl"></link> + <script type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + + // In this test we are introducing two widgets. The only difference between them + // is that the first one is using `connectRoot` with the `aTranslate` argument set to `true`, + // and the other one to `false`. + // + // In this test, we will inject both of them into the DOM for parsing. + // For a test that verifies the behavior when they're injected lazily, see + // `test_connectRoot_webcomponent_lazy.html` test. + // + // Since both widgets get injected into DOM during parsing, we expect both of them + // to get translated before `document.l10n.ready` is resolved. + + let passedTests = 0; + + class FluentWidget extends HTMLElement { + constructor() { + super(); + const shadowRoot = this.attachShadow({mode: "open"}); + const t = document.querySelector("#fluent-widget-template"); + const instance = t.content.cloneNode(true); + shadowRoot.appendChild(instance); + } + async connectedCallback() { + MozXULElement.insertFTLIfNeeded("browser/preferences/preferences.ftl"); + + document.l10n.connectRoot(this.shadowRoot, true); + + let label = this.shadowRoot.getElementById("label"); + + await document.l10n.ready; + is(label.textContent, "Learn more", "localization content applied to element"); + passedTests++; + if (passedTests == 2) { + SimpleTest.finish(); + } + } + } + + class FluentWidget2 extends HTMLElement { + constructor() { + super(); + const shadowRoot = this.attachShadow({mode: "open"}); + const t = document.querySelector("#fluent-widget-template"); + const instance = t.content.cloneNode(true); + shadowRoot.appendChild(instance); + } + async connectedCallback() { + MozXULElement.insertFTLIfNeeded("browser/preferences/preferences.ftl"); + + document.l10n.connectRoot(this.shadowRoot, false); + + let label = this.shadowRoot.getElementById("label"); + + await document.l10n.ready; + is(label.textContent, "Learn more", "localization content applied to element"); + passedTests++; + if (passedTests == 2) { + SimpleTest.finish(); + } + } + } + + customElements.define("fluent-widget", FluentWidget); + customElements.define("fluent-widget2", FluentWidget2); + </script> +</head> +<body> + <template id="fluent-widget-template"> + <div> + <button id="label" data-l10n-id="do-not-track-learn-more"></button> + </div> + </template> + <fluent-widget></fluent-widget> + <fluent-widget2></fluent-widget2> + <script> + // This trick makes sure that we connect the widgets before parsing is completed. + document.write(""); + </script> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/document_l10n/test_connectRoot_webcomponent_lazy.html b/dom/l10n/tests/mochitest/document_l10n/test_connectRoot_webcomponent_lazy.html new file mode 100644 index 0000000000..b74bbc00c8 --- /dev/null +++ b/dom/l10n/tests/mochitest/document_l10n/test_connectRoot_webcomponent_lazy.html @@ -0,0 +1,98 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test Web Component connecting into Document's l10n</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + + // In this test we are introducing two widgets. The only difference between them + // is that the first one is using `connectRoot` with the `aTranslate` argument set to `true`, + // and the other one to `false`. + // + // In this test, we will inject both of them lazily, after initial parsing is completed. + // For a test that verifies the behavior when they're injected during parsing, see + // `test_connectRoot_webcomponent.html` test. + // + // The expected difference is that when both get lazily injected into the DOM, the first one + // will get translated, while the other will not. + // The latter behavior will be used by widgets that will want to translate the initial DOM on their + // own before connecting the root. + + let firstWidgetTranslated = false; + + class FluentWidget extends HTMLElement { + constructor() { + super(); + const shadowRoot = this.attachShadow({mode: "open"}); + const t = document.querySelector("#fluent-widget-template"); + const instance = t.content.cloneNode(true); + shadowRoot.appendChild(instance); + } + connectedCallback() { + MozXULElement.insertFTLIfNeeded("browser/preferences/preferences.ftl"); + + document.l10n.connectRoot(this.shadowRoot, true); + + let label = this.shadowRoot.getElementById("label"); + + let verifyL10n = () => { + if (label.textContent.length) { + window.removeEventListener("MozAfterPaint", verifyL10n); + is(label.textContent, "Learn more", "localization content applied to element"); + firstWidgetTranslated = true; + } + }; + window.addEventListener("MozAfterPaint", verifyL10n); + } + } + + class FluentWidget2 extends HTMLElement { + constructor() { + super(); + const shadowRoot = this.attachShadow({mode: "open"}); + const t = document.querySelector("#fluent-widget-template"); + const instance = t.content.cloneNode(true); + shadowRoot.appendChild(instance); + } + connectedCallback() { + MozXULElement.insertFTLIfNeeded("browser/preferences/preferences.ftl"); + + document.l10n.connectRoot(this.shadowRoot, false); + + let label = this.shadowRoot.getElementById("label"); + + let verifyL10n = () => { + if (firstWidgetTranslated) { + is(label.textContent.length, 0, "This widget should remain untranslated."); + window.removeEventListener("MozAfterPaint", verifyL10n); + SimpleTest.finish(); + } + }; + window.addEventListener("MozAfterPaint", verifyL10n); + } + } + + customElements.define("fluent-widget", FluentWidget); + customElements.define("fluent-widget2", FluentWidget2); + + window.addEventListener("load", () => { + window.requestIdleCallback(async () => { + let widget = document.createElement("fluent-widget"); + document.body.appendChild(widget); + let widget2 = document.createElement("fluent-widget2"); + document.body.appendChild(widget2); + }); + }, { once: true }); + </script> +</head> +<body> + <template id="fluent-widget-template"> + <div> + <button id="label" data-l10n-id="do-not-track-learn-more"></button> + </div> + </template> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/document_l10n/test_docl10n.html b/dom/l10n/tests/mochitest/document_l10n/test_docl10n.html new file mode 100644 index 0000000000..12ff623f5c --- /dev/null +++ b/dom/l10n/tests/mochitest/document_l10n/test_docl10n.html @@ -0,0 +1,66 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test DocumentL10n in HTML environment</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <link rel="localization" href="crashreporter/aboutcrashes.ftl"/> + <script> + "use strict"; + SimpleTest.waitForExplicitFinish(); + + is(document.l10n.ready && document.l10n.ready.then !== undefined, true, + "document.l10n.ready exists and is a Promise"); + + (async function() { + await document.l10n.ready; + + const desc = document.getElementById("main-desc"); + is(!!desc.textContent.length, true, "initial localization is applied"); + + const msg = await document.l10n.formatValue("id-heading"); + is(!!msg.length, true, "value is formatted manually"); + + const label = document.getElementById("label1"); + let l10nArgs = document.l10n.getAttributes(label); + is(l10nArgs.id, null, "id is null if not set"); + + SimpleTest.doesThrow( + () => { + const bad = {}; + bad.bad = bad; + document.l10n.setAttributes(label, "date-crashed-heading", bad); + }, + "an error is thrown for invalid args", + ); + + l10nArgs = document.l10n.getAttributes(label); + is(l10nArgs.id, null, "id is not set if args are invalid"); + + document.l10n.setAttributes( + label, + "date-crashed-heading", + { + name: "John", + } + ); + ok(document.hasPendingL10nMutations, "Should have pending mutations"); + l10nArgs = document.l10n.getAttributes(label); + is(l10nArgs.id, "date-crashed-heading", "id is set by setAttributes"); + is(l10nArgs.args.name, "John", "args are set by setAttributes"); + // Test for mutations applied. + document.addEventListener("L10nMutationsFinished", function() { + ok(!!label.textContent.length, "Should've applied translation"); + ok(!document.hasPendingL10nMutations, "Should have no more pending mutations"); + SimpleTest.finish(); + }, { once: true }); + })(); + </script> +</head> +<body> + <h1 id="main-desc" data-l10n-id="crash-reports-title"></h1> + + <p id="label1"></p> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/document_l10n/test_docl10n.xhtml b/dom/l10n/tests/mochitest/document_l10n/test_docl10n.xhtml new file mode 100644 index 0000000000..2d51d8689e --- /dev/null +++ b/dom/l10n/tests/mochitest/document_l10n/test_docl10n.xhtml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<!DOCTYPE html> + +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <meta charset="utf-8"></meta> + <title>Test DocumentL10n in HTML environment</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"></link> + <link rel="localization" href="crashreporter/aboutcrashes.ftl"/> + <script type="application/javascript"> + "use strict"; + SimpleTest.waitForExplicitFinish(); + + document.addEventListener("DOMContentLoaded", async function() { + await document.l10n.ready; + + // Test for initial localization applied. + let desc = document.getElementById("main-desc"); + is(!!desc.textContent.length, true); + + // Test for manual value formatting + let msg = await document.l10n.formatValue("id-heading"); + is(!!msg.length, true); + + // Test for mutations applied. + let verifyL10n = () => { + if (label.textContent.length) { + window.removeEventListener("MozAfterPaint", verifyL10n); + SimpleTest.finish(); + } + }; + window.addEventListener("MozAfterPaint", verifyL10n); + + let label = document.getElementById("label1"); + document.l10n.setAttributes( + label, + "date-crashed-heading", + { + name: "John", + } + ); + + // Test for l10n.getAttributes + let l10nArgs = document.l10n.getAttributes(label); + is(l10nArgs.id, "date-crashed-heading"); + is(l10nArgs.args.name, "John"); + }, { once: true}); + </script> +</head> +<body> + <h1 id="main-desc" data-l10n-id="crash-reports-title"></h1> + + <p id="label1" /> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/document_l10n/test_docl10n_lazy.html b/dom/l10n/tests/mochitest/document_l10n/test_docl10n_lazy.html new file mode 100644 index 0000000000..6c3ddb73ed --- /dev/null +++ b/dom/l10n/tests/mochitest/document_l10n/test_docl10n_lazy.html @@ -0,0 +1,44 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test Lazy DocumentL10n in HTML environment</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript"> + "use strict"; + SimpleTest.waitForExplicitFinish(); + + is(document.l10n, null, "document.l10n is null."); + + window.addEventListener("load", async () => { + is(document.l10n, null, "document.l10n is null after load."); + + let desc = document.getElementById("main-desc"); + is(desc.textContent.length, 0, "main-desc is not translated"); + + let link = document.createElement("link"); + link.setAttribute("rel", "localization"); + link.setAttribute("href", "crashreporter/aboutcrashes.ftl"); + document.head.appendChild(link); + + // Verify now that `l10n.ready` exists and is fulfilled. + await document.l10n.ready; + + // Lazy initialized localization should translate the document. + is(!!desc.textContent.length, true, "main-desc is translated"); + + document.head.removeChild(link); + + is(document.l10n, null, "document.l10n is null"); + + SimpleTest.finish(); + }, { once: true}); + </script> +</head> +<body> + <h1 id="main-desc" data-l10n-id="crash-reports-title"></h1> + + <p id="label1"></p> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/document_l10n/test_docl10n_ready_rejected.html b/dom/l10n/tests/mochitest/document_l10n/test_docl10n_ready_rejected.html new file mode 100644 index 0000000000..63e18f802c --- /dev/null +++ b/dom/l10n/tests/mochitest/document_l10n/test_docl10n_ready_rejected.html @@ -0,0 +1,29 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test mozIDOMLocalization.ready rejected state</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <link rel="localization" href="path/to_non_existing.ftl"/> + <script type="application/javascript"> + "use strict"; + SimpleTest.waitForExplicitFinish(); + + document.addEventListener("DOMContentLoaded", async function() { + /** + * Even when we fail to localize all elements, we will + * still resolve the `ready` promise to communicate that + * the initial translation phase is now completed. + */ + document.l10n.ready.then(() => { + is(1, 1, "the ready should resolve"); + SimpleTest.finish(); + }); + }); + </script> +</head> +<body> + <h1 data-l10n-id="non-existing-id"></h1> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/document_l10n/test_docl10n_removeResourceIds.html b/dom/l10n/tests/mochitest/document_l10n/test_docl10n_removeResourceIds.html new file mode 100644 index 0000000000..8ccaa04614 --- /dev/null +++ b/dom/l10n/tests/mochitest/document_l10n/test_docl10n_removeResourceIds.html @@ -0,0 +1,59 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test DocumentL10n::RemoveResourceIds</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <link rel="localization" href="toolkit/about/aboutAddons.ftl"/> + <link rel="localization" href="toolkit/about/aboutSupport.ftl"/> + <script type="application/javascript"> + "use strict"; + /* eslint-disable mozilla/prefer-formatValues */ + + SimpleTest.waitForExplicitFinish(); + + window.onload = async function() { + { + // 1. An example message from aboutAddons should be available. + let value = await document.l10n.formatValue("shortcuts-browserAction2"); + is(!!value.length, true, "localized value retrieved"); + } + + { + // 2. Remove aboutAddons.ftl + let link = document.head.querySelector("link[href*=aboutAddons]"); + document.head.removeChild(link); + } + + { + // 3. An example message from aboutSupport should still be available. + let value = await document.l10n.formatValue("features-version"); + is(!!value.length, true, "localized value retrieved"); + + // 4. An example message from aboutAddons should not be available. + await document.l10n.formatValue("shortcuts-browserAction").then( + () => { + ok(false, "localization should not be available"); + }, + () => { + ok(true, "localization should not be available"); + }); + } + + { + // 5. Remove aboutSupport.ftl + let link = document.head.querySelector("link[href*=aboutSupport]"); + document.head.removeChild(link); + + // 6. document.l10n should be null. + is(document.l10n, null, "document.l10n should be null"); + + SimpleTest.finish(); + } + }; + </script> +</head> +<body> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/document_l10n/test_docl10n_sync.html b/dom/l10n/tests/mochitest/document_l10n/test_docl10n_sync.html new file mode 100644 index 0000000000..ea44d1afe1 --- /dev/null +++ b/dom/l10n/tests/mochitest/document_l10n/test_docl10n_sync.html @@ -0,0 +1,54 @@ +<!DOCTYPE HTML> +<html data-l10n-sync> +<head> + <meta charset="utf-8"> + <title>Test DocumentL10n in HTML environment</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <link rel="localization" href="crashreporter/aboutcrashes.ftl"/> + <script type="application/javascript"> + "use strict"; + SimpleTest.waitForExplicitFinish(); + + document.addEventListener("DOMContentLoaded", async function() { + await document.l10n.ready; + + // Test for initial localization applied. + let desc = document.getElementById("main-desc"); + is(!!desc.textContent.length, true); + + // Test for manual value formatting. + let msg = await document.l10n.formatValue("id-heading"); + is(!!msg.length, true); + + // Test for mutations applied. + let verifyL10n = () => { + if (label.textContent.length) { + window.removeEventListener("MozAfterPaint", verifyL10n); + SimpleTest.finish(); + } + }; + window.addEventListener("MozAfterPaint", verifyL10n); + + let label = document.getElementById("label1"); + document.l10n.setAttributes( + label, + "date-crashed-heading", + { + name: "John", + } + ); + + // Test for l10n.getAttributes + let l10nArgs = document.l10n.getAttributes(label); + is(l10nArgs.id, "date-crashed-heading"); + is(l10nArgs.args.name, "John"); + }, { once: true}); + </script> +</head> +<body> + <h1 id="main-desc" data-l10n-id="crash-reports-title"></h1> + + <p id="label1"></p> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/document_l10n/test_telemetry.html b/dom/l10n/tests/mochitest/document_l10n/test_telemetry.html new file mode 100644 index 0000000000..b528ebc7ea --- /dev/null +++ b/dom/l10n/tests/mochitest/document_l10n/test_telemetry.html @@ -0,0 +1,83 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test DocumentL10n Telemetry</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript"> + "use strict"; + const { BrowserTestUtils } = ChromeUtils.import( + "resource://testing-common/BrowserTestUtils.jsm" + ); + + SimpleTest.waitForExplicitFinish(); + + function countValues(snapshot, key) { + if (!snapshot.hasOwnProperty(key)) { + return 0; + } + let values = snapshot[key].values; + return Object.values(values).reduce((sum, n) => sum + n, 0); + } + + (async function() { + let histogram = Services.telemetry + .getKeyedHistogramById("L10N_DOCUMENT_INITIAL_TRANSLATION_TIME_US"); + let snapshot = histogram.snapshot(); + + // In some cases the test runs before first window is localized. + // We just want to ensure that we didn't register more than 1 + // first window telemetry. + let firstWindowCount = countValues(snapshot, "browser_first_window"); + is(firstWindowCount < 2, true); + + histogram.clear(); + + // Open a new window + let win = await BrowserTestUtils.openNewBrowserWindow({ + waitForTabURL: "about:blank", + }); + + // Telemetry in testing is flaky and when landing this test + // we saw cases where the snapshot did not contain the new + // window telemetry at this moment. + // + // We're going to use `waitForCondition` to test for + // the telemetry to be eventually recorded. + BrowserTestUtils.waitForCondition(() => { + snapshot = histogram.snapshot(); + + // We want to make sure that since we cleared histogram + // just one new window of either type has been opened. + let browserWindowsCount = + countValues(snapshot, "browser_new_window") + + countValues(snapshot, "browser_first_window"); + return browserWindowsCount == 1; + }); + + // Open preferences in a new tab + let tab = BrowserTestUtils.addTab( + win.gBrowser, + "about:preferences" + ); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + // Similarly to the above, we've seen cases where the telemetry + // was not present right after the tab was opened, so + // we'll use `waitForCondition` here. + BrowserTestUtils.waitForCondition(() => { + snapshot = histogram.snapshot(); + + return countValues(snapshot, "about:preferences") == 1; + }); + + await BrowserTestUtils.closeWindow(win); + + SimpleTest.finish(); + })(); + </script> +</head> +<body> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/document_l10n/test_unpriv_iframe.html b/dom/l10n/tests/mochitest/document_l10n/test_unpriv_iframe.html new file mode 100644 index 0000000000..4f4b29c500 --- /dev/null +++ b/dom/l10n/tests/mochitest/document_l10n/test_unpriv_iframe.html @@ -0,0 +1,26 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Ensure unprivilaged document cannot access document.l10n in an iframe</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="application/javascript"> + "use strict"; + SimpleTest.waitForExplicitFinish(); + + addLoadEvent(function() { + let frame = document.getElementById("frame"); + let frame2 = document.getElementById("frame2"); + + is("l10n" in frame.contentDocument, false); + is("l10n" in frame2.contentDocument, false); + }); + addLoadEvent(SimpleTest.finish); + </script> +</head> +<body> + <iframe id="frame" src="about:blank"></iframe> + <iframe id="frame2" src="about:crashes"></iframe> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/dom_localization/test_attr_sanitized.html b/dom/l10n/tests/mochitest/dom_localization/test_attr_sanitized.html new file mode 100644 index 0000000000..a9244c9891 --- /dev/null +++ b/dom/l10n/tests/mochitest/dom_localization/test_attr_sanitized.html @@ -0,0 +1,49 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test DOMLocalization's attr sanitization functionality</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript"> + "use strict"; + const l10nReg = new L10nRegistry(); + const fs = [ + { path: "/localization/en-US/mock.ftl", source: ` +key1 = Value for Key 1 + +key2 = Value for <a>Key 2<a/>. +` }, + ]; + const source = L10nFileSource.createMock("test", "app", ["en-US"], "/localization/{locale}", fs); + l10nReg.registerSources([source]); + + SimpleTest.waitForExplicitFinish(); + addLoadEvent(async () => { + const domLoc = new DOMLocalization( + ["/mock.ftl"], + false, + l10nReg, + ["en-US"], + ); + + await domLoc.translateFragment(document.body); + + const elem1 = document.querySelector("#elem1"); + const elem2 = document.querySelector("#elem2"); + + ok(elem1.textContent.includes("Value for")); + ok(!elem1.hasAttribute("title")); + + ok(elem2.textContent.includes("Value for")); + ok(!elem2.hasAttribute("title")); + + SimpleTest.finish(); + }); + </script> +</head> +<body> + <p id="elem1" title="Old Translation" data-l10n-id="key1"></p> + <p id="elem2" title="Old Translation" data-l10n-id="key2"></p> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/dom_localization/test_connectRoot.html b/dom/l10n/tests/mochitest/dom_localization/test_connectRoot.html new file mode 100644 index 0000000000..83b83757f1 --- /dev/null +++ b/dom/l10n/tests/mochitest/dom_localization/test_connectRoot.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test DOMLocalization.prototype.connectRoot</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript"> + "use strict"; + const l10nReg = new L10nRegistry(); + const fs = [ + { path: "/localization/en-US/mock.ftl", source: ` +key1 = Value for Key 1 +` }, + ]; + const source = L10nFileSource.createMock("test", "app", ["en-US"], "/localization/{locale}", fs); + l10nReg.registerSources([source]); + + window.onload = async function() { + SimpleTest.waitForExplicitFinish(); + + const p1 = document.getElementById("p1"); + + const domLoc = new DOMLocalization( + ["/mock.ftl"], + false, + l10nReg, + ["en-US"], + ); + + await domLoc.translateRoots(); + is(!p1.textContent.length, true); + const body = document.body; + domLoc.connectRoot(body); + await domLoc.translateRoots(); + is(!!p1.textContent.length, true); + + SimpleTest.finish(); + }; + </script> +</head> +<body> + <p id="p1" data-l10n-id="key1"></p> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/dom_localization/test_connectRoot_webcomponent.html b/dom/l10n/tests/mochitest/dom_localization/test_connectRoot_webcomponent.html new file mode 100644 index 0000000000..1254bd814f --- /dev/null +++ b/dom/l10n/tests/mochitest/dom_localization/test_connectRoot_webcomponent.html @@ -0,0 +1,72 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test DOMLocalization.prototype.connectRoot with Web Components</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + + class FluentWidget extends HTMLElement { + constructor() { + super(); + const shadowRoot = this.attachShadow({mode: "open"}); + const t = document.querySelector("#fluent-widget-template"); + const instance = t.content.cloneNode(true); + shadowRoot.appendChild(instance); + } + connectedCallback() { + document.domLoc.connectRoot(this.shadowRoot); + ok(true); + + let label = this.shadowRoot.getElementById("label"); + + // Test for mutations applied. + let verifyL10n = () => { + if (label.textContent.length) { + window.removeEventListener("MozAfterPaint", verifyL10n); + // Notice: In normal tests we do not want to test against any particular + // value as per https://firefox-source-docs.mozilla.org/l10n/fluent/tutorial.html#testing + // But in this particular test, since we do not rely on actual real + // localization, but instead we mock it in the test, we can test + // against the actual value safely. + is(label.textContent, "Value for Key 1", "localization content applied to element"); + SimpleTest.finish(); + } + }; + window.addEventListener("MozAfterPaint", verifyL10n); + + document.domLoc.setAttributes(label, "key1"); + } + } + customElements.define("fluent-widget", FluentWidget); + </script> + <script type="application/javascript"> + "use strict"; + const l10nReg = new L10nRegistry(); + const fs = [ + { path: "/localization/en-US/mock.ftl", source: ` +key1 = Value for Key 1 +` }, + ]; + const source = L10nFileSource.createMock("test", "app", ["en-US"], "/localization/{locale}", fs); + l10nReg.registerSources([source]); + + document.domLoc = new DOMLocalization( + ["/mock.ftl"], + false, + l10nReg, + ["en-US"], + ); + </script> +</head> +<body> + <template id="fluent-widget-template"> + <div> + <p id="label"></p> + </div> + </template> + <fluent-widget id="widget1"></fluent-widget> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/dom_localization/test_disconnectRoot.html b/dom/l10n/tests/mochitest/dom_localization/test_disconnectRoot.html new file mode 100644 index 0000000000..fd7344c88e --- /dev/null +++ b/dom/l10n/tests/mochitest/dom_localization/test_disconnectRoot.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test DOMLocalization.prototype.disconnectRoot</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript"> + "use strict"; + const l10nReg = new L10nRegistry(); + const fs = [ + { path: "/localization/en-US/mock.ftl", source: ` +key1 = Value for Key 1 +key2 = Value for Key 2 +` }, + ]; + const source = L10nFileSource.createMock("test", "app", ["en-US"], "/localization/{locale}", fs); + l10nReg.registerSources([source]); + + window.onload = async function() { + SimpleTest.waitForExplicitFinish(); + + const p1 = document.getElementById("p1"); + + const domLoc = new DOMLocalization( + ["/mock.ftl"], + false, + l10nReg, + ["en-US"], + ); + + await domLoc.translateRoots(); + is(!p1.textContent.length, true); + + const body = document.body; + + domLoc.connectRoot(body); + await domLoc.translateRoots(); + is(p1.textContent.includes("Key 1"), true); + is(p1.textContent.includes("Key 2"), false); + + domLoc.disconnectRoot(body); + domLoc.setAttributes(p1, "key2"); + await domLoc.translateRoots(); + is(p1.textContent.includes("Key 1"), true); + is(p1.textContent.includes("Key 2"), false); + + domLoc.connectRoot(body); + await domLoc.translateRoots(); + is(p1.textContent.includes("Key 1"), false); + is(p1.textContent.includes("Key 2"), true); + + SimpleTest.finish(); + }; + </script> +</head> +<body> + <p id="p1" data-l10n-id="key1"></p> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/dom_localization/test_domloc.xhtml b/dom/l10n/tests/mochitest/dom_localization/test_domloc.xhtml new file mode 100644 index 0000000000..a4d4fcc506 --- /dev/null +++ b/dom/l10n/tests/mochitest/dom_localization/test_domloc.xhtml @@ -0,0 +1,68 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Testing DOMLocalization in XUL environment"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript"> + <![CDATA[ + const l10nReg = new L10nRegistry(); + const fs = [ + { path: "/localization/en-US/mock.ftl", source: ` +file-menu = + .label = File + .accesskey = F +new-tab = + .label = New Tab + .accesskey = N +container = Some text with an <image data-l10n-name="foo"> inside it. +` }, + ]; + const source = L10nFileSource.createMock("test", "app", ["en-US"], "/localization/{locale}", fs); + l10nReg.registerSources([source]); + + SimpleTest.waitForExplicitFinish(); + + + const domLoc = new DOMLocalization( + [], + false, + l10nReg, + ["en-US"], + ); + + async function foo() { + domLoc.addResourceIds(["/mock.ftl"]); + domLoc.connectRoot(document.documentElement); + await domLoc.translateRoots(); + + is(document.getElementById('file-menu').getAttribute('label'), 'File'); + is(document.getElementById('file-menu').getAttribute('accesskey'), 'F'); + + is(document.getElementById('new-tab').getAttribute('label'), 'New Tab'); + is(document.getElementById('new-tab').getAttribute('accesskey'), 'N'); + + ok(document.querySelector("image"), + "Image should still be present after localization."); + SimpleTest.finish(); + } + + window.onload = foo; + + ]]> + </script> + <description data-l10n-id="container"><image data-l10n-name="foo"/></description> + + <menubar id="main-menubar"> + <menu id="file-menu" data-l10n-id="file-menu"> + <menupopup id="menu_FilePopup"> + <menuitem id="new-tab" data-l10n-id="new-tab"> + </menuitem> + </menupopup> + </menu> + </menubar> +</window> diff --git a/dom/l10n/tests/mochitest/dom_localization/test_getAttributes.html b/dom/l10n/tests/mochitest/dom_localization/test_getAttributes.html new file mode 100644 index 0000000000..11996d132a --- /dev/null +++ b/dom/l10n/tests/mochitest/dom_localization/test_getAttributes.html @@ -0,0 +1,49 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test DOMLocalization.prototype.getAttributes</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript"> + "use strict"; + + window.onload = function() { + SimpleTest.waitForExplicitFinish(); + + const domLoc = new DOMLocalization( + [], + false, + ); + + const p1 = document.querySelectorAll("p")[0]; + const p2 = document.querySelectorAll("p")[1]; + const p3 = document.querySelectorAll("p")[2]; + const attrs1 = domLoc.getAttributes(p1); + const attrs2 = domLoc.getAttributes(p2); + const attrs3 = domLoc.getAttributes(p3); + isDeeply(attrs1, { + id: null, + args: null, + }); + isDeeply(attrs2, { + id: "id1", + args: null, + }); + isDeeply(attrs3, { + id: "id2", + args: { + userName: "John", + }, + }); + + SimpleTest.finish(); + }; + </script> +</head> +<body> + <p /> + <p data-l10n-id="id1" /> + <p data-l10n-id="id2" data-l10n-args='{"userName": "John"}' /> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/dom_localization/test_l10n_mutations.html b/dom/l10n/tests/mochitest/dom_localization/test_l10n_mutations.html new file mode 100644 index 0000000000..278baa15dd --- /dev/null +++ b/dom/l10n/tests/mochitest/dom_localization/test_l10n_mutations.html @@ -0,0 +1,57 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test DOMLocalization's MutationObserver</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript"> + "use strict"; + const l10nReg = new L10nRegistry(); + const fs = [ + { path: "/localization/en-US/mock.ftl", source: ` +title = Hello World +title2 = Hello Another World +` }, + ]; + const source = L10nFileSource.createMock("test", "app", ["en-US"], "/localization/{locale}", fs); + l10nReg.registerSources([source]); + + window.onload = async function() { + SimpleTest.waitForExplicitFinish(); + + const domLoc = new DOMLocalization( + [], + false, + l10nReg, + ["en-US"], + ); + + const h1 = document.querySelectorAll("h1")[0]; + + domLoc.addResourceIds(["/mock.ftl"]); + domLoc.connectRoot(document.body); + + await domLoc.translateRoots(); + + is(h1.textContent, "Hello World"); + + + const mo = new MutationObserver(function onMutations(mutations) { + is(h1.textContent, "Hello Another World"); + mo.disconnect(); + SimpleTest.finish(); + }); + + mo.observe(h1, { childList: true, characterData: true }); + + domLoc.setAttributes(h1, "title2"); + }; + </script> +</head> +<body> + <div> + <h1 data-l10n-id="title"></h1> + </div> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/dom_localization/test_overlay.html b/dom/l10n/tests/mochitest/dom_localization/test_overlay.html new file mode 100644 index 0000000000..2c8c219bb2 --- /dev/null +++ b/dom/l10n/tests/mochitest/dom_localization/test_overlay.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test DOMLocalization's DOMOverlay functionality</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript"> + "use strict"; + const l10nReg = new L10nRegistry(); + const fs = [ + { path: "/localization/en-US/mock.ftl", source: ` +title = <strong>Hello</strong> World +title2 = This is <a data-l10n-name="link">a link</a>! +` }, + ]; + const source = L10nFileSource.createMock("test", "app", ["en-US"], "/localization/{locale}", fs); + l10nReg.registerSources([source]); + + window.onload = async function() { + SimpleTest.waitForExplicitFinish(); + + const domLoc = new DOMLocalization( + ["/mock.ftl"], + false, + l10nReg, + ["en-US"], + ); + + const p1 = document.querySelectorAll("p")[0]; + const p2 = document.querySelectorAll("p")[1]; + const a = p2.querySelector("a"); + // We want to test that the event listener persists after + // translateFragment(). + a.addEventListener("click", function(e) { + SimpleTest.finish(); + // We cannot connect to non-local connections on automation, so prevent + // the navigation. + e.preventDefault(); + }); + + await domLoc.translateFragment(document.body); + + + is(p1.querySelector("strong").textContent, "Hello"); + + is(p2.querySelector("a").getAttribute("href"), "http://www.mozilla.org"); + is(p2.querySelector("a").textContent, "a link"); + + a.click(); + }; + </script> +</head> +<body> + <p data-l10n-id="title" /> + <p data-l10n-id="title2"> + <a href="http://www.mozilla.org" data-l10n-name="link"></a> + </p> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/dom_localization/test_overlay_missing_all.html b/dom/l10n/tests/mochitest/dom_localization/test_overlay_missing_all.html new file mode 100644 index 0000000000..c6f285aa38 --- /dev/null +++ b/dom/l10n/tests/mochitest/dom_localization/test_overlay_missing_all.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test DOMLocalization's DOMOverlay functionality</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript"> + "use strict"; + const l10nReg = new L10nRegistry(); + + SimpleTest.waitForExplicitFinish(); + addLoadEvent(async () => { + const domLoc = new DOMLocalization( + [], + false, + l10nReg, + ["en-US"], + ); + + await domLoc.translateFragment(document.body).then(() => { + ok(false, "Expected translateFragment to throw on missing l10n-id"); + }, () => { + ok(true, "Expected translateFragment to throw on missing l10n-id"); + }); + SimpleTest.finish(); + }); + </script> +</head> +<body> + <p data-l10n-id="title"> + <a href="http://www.mozilla.org"></a> + <a href="http://www.firefox.com"></a> + <a href="http://www.w3.org"></a> + </p> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/dom_localization/test_overlay_missing_children.html b/dom/l10n/tests/mochitest/dom_localization/test_overlay_missing_children.html new file mode 100644 index 0000000000..9e4fec1ce2 --- /dev/null +++ b/dom/l10n/tests/mochitest/dom_localization/test_overlay_missing_children.html @@ -0,0 +1,53 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test DOMLocalization's DOMOverlay functionality</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript"> + "use strict"; + const l10nReg = new L10nRegistry(); + const fs = [ + { path: "/localization/en-US/mock.ftl", source: ` +title = Visit <a data-l10n-name="mozilla-link">Mozilla</a> or <a data-l10n-name="firefox-link">Firefox</a> website! +` }, + ]; + const source = L10nFileSource.createMock("test", "app", ["en-US"], "/localization/{locale}", fs); + l10nReg.registerSources([source]); + + window.onload = async function() { + SimpleTest.waitForExplicitFinish(); + + const domLoc = new DOMLocalization( + ["/mock.ftl"], + false, + l10nReg, + ["en-US"], + ); + + await domLoc.translateFragment(document.body); + + const p1 = document.querySelectorAll("p")[0]; + const linkList = p1.querySelectorAll("a"); + + + is(linkList[0].getAttribute("href"), "http://www.mozilla.org"); + is(linkList[0].textContent, "Mozilla"); + is(linkList[1].getAttribute("href"), "http://www.firefox.com"); + is(linkList[1].textContent, "Firefox"); + + is(linkList.length, 2, "There should be exactly two links in the result."); + + SimpleTest.finish(); + }; + </script> +</head> +<body> + <p data-l10n-id="title"> + <a href="http://www.mozilla.org" data-l10n-name="mozilla-link"></a> + <a href="http://www.firefox.com" data-l10n-name="firefox-link"></a> + <a href="http://www.w3.org" data-l10n-name="w3-link"></a> + </p> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/dom_localization/test_overlay_repeated.html b/dom/l10n/tests/mochitest/dom_localization/test_overlay_repeated.html new file mode 100644 index 0000000000..a169c27fa0 --- /dev/null +++ b/dom/l10n/tests/mochitest/dom_localization/test_overlay_repeated.html @@ -0,0 +1,50 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test DOMLocalization's DOMOverlay functionality</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript"> + "use strict"; + const l10nReg = new L10nRegistry(); + const fs = [ + { path: "/localization/en-US/mock.ftl", source: ` +title = Visit <a data-l10n-name="mozilla-link">Mozilla</a> or <a data-l10n-name="firefox-link">Firefox</a> website! +` }, + ]; + const source = L10nFileSource.createMock("test", "app", ["en-US"], "/localization/{locale}", fs); + l10nReg.registerSources([source]); + + window.onload = async function() { + SimpleTest.waitForExplicitFinish(); + + const domLoc = new DOMLocalization( + ["/mock.ftl"], + false, + l10nReg, + ["en-US"], + ); + + await domLoc.translateFragment(document.body); + + const p1 = document.querySelectorAll("p")[0]; + const linkList = p1.querySelectorAll("a"); + + + is(linkList[0].getAttribute("href"), "http://www.mozilla.org"); + is(linkList[0].textContent, "Mozilla"); + is(linkList[1].getAttribute("href"), "http://www.firefox.com"); + is(linkList[1].textContent, "Firefox"); + + SimpleTest.finish(); + }; + </script> +</head> +<body> + <p data-l10n-id="title"> + <a href="http://www.mozilla.org" data-l10n-name="mozilla-link"></a> + <a href="http://www.firefox.com" data-l10n-name="firefox-link"></a> + </p> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/dom_localization/test_overlay_sanitized.html b/dom/l10n/tests/mochitest/dom_localization/test_overlay_sanitized.html new file mode 100644 index 0000000000..fcc201edb6 --- /dev/null +++ b/dom/l10n/tests/mochitest/dom_localization/test_overlay_sanitized.html @@ -0,0 +1,52 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test DOMLocalization's DOMOverlay functionality</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript"> + "use strict"; + const l10nReg = new L10nRegistry(); + const fs = [ + { path: "/localization/en-US/mock.ftl", source: ` +key1 = + .href = https://www.hacked.com + +key2 = + .href = https://pl.wikipedia.org +` }, + ]; + const source = L10nFileSource.createMock("test", "app", ["en-US"], "/localization/{locale}", fs); + l10nReg.registerSources([source]); + + async function test() { + const domLoc = new DOMLocalization( + ["/mock.ftl"], + false, + l10nReg, + ["en-US"], + ); + + await domLoc.translateFragment(document.body); + + const key1Elem = document.querySelector("[data-l10n-id=key1]"); + const key2Elem = document.querySelector("[data-l10n-id=key2]"); + + + is(key1Elem.hasAttribute("href"), false, "href translation should not be allowed"); + is(key2Elem.getAttribute("href"), "https://pl.wikipedia.org", + "href translation should be allowed"); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addLoadEvent(test); + </script> +</head> +<body> + <a data-l10n-id="key1"></a> + <a data-l10n-id="key2" data-l10n-attrs="href"></a> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/dom_localization/test_repeated_l10nid.html b/dom/l10n/tests/mochitest/dom_localization/test_repeated_l10nid.html new file mode 100644 index 0000000000..64d585f0a0 --- /dev/null +++ b/dom/l10n/tests/mochitest/dom_localization/test_repeated_l10nid.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test DOMLocalization's matching l10nIds functionality</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript"> + "use strict"; + const l10nReg = new L10nRegistry(); + const fs = [ + { path: "/localization/en-US/mock.ftl", source: ` +key1 = Translation For Key 1 + +key2 = Visit <a data-l10n-name="link">this link<a/>. +` }, + ]; + const source = L10nFileSource.createMock("test", "app", ["en-US"], "/localization/{locale}", fs); + l10nReg.registerSources([source]); + + SimpleTest.waitForExplicitFinish(); + addLoadEvent(async () => { + const domLoc = new DOMLocalization( + ["/mock.ftl"], + false, + l10nReg, + ["en-US"], + ); + + await domLoc.translateFragment(document.body); + + ok(document.querySelector("#elem1").textContent.includes("Key 1")); + ok(document.querySelector("#elem2").textContent.includes("Key 1")); + + const elem3 = document.querySelector("#elem3"); + const elem4 = document.querySelector("#elem4"); + + ok(elem3.textContent.includes("Visit")); + is(elem3.querySelector("a").getAttribute("href"), "http://www.mozilla.org"); + + ok(elem4.textContent.includes("Visit")); + is(elem4.querySelector("a").getAttribute("href"), "http://www.firefox.com"); + + SimpleTest.finish(); + }); + </script> +</head> +<body> + <h1 id="elem1" data-l10n-id="key1"></h1> + <h2 id="elem2" data-l10n-id="key1"></h2> + + <p id="elem3" data-l10n-id="key2"> + <a href="http://www.mozilla.org" data-l10n-name="link"></a> + </p> + + <p id="elem4" data-l10n-id="key2"> + <a href="http://www.firefox.com" data-l10n-name="link"></a> + </p> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/dom_localization/test_setAttributes.html b/dom/l10n/tests/mochitest/dom_localization/test_setAttributes.html new file mode 100644 index 0000000000..508a15dcd3 --- /dev/null +++ b/dom/l10n/tests/mochitest/dom_localization/test_setAttributes.html @@ -0,0 +1,79 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test DOMLocalization.prototype.setAttributes and DOMLocalization.prototype.setArgs</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript"> + "use strict"; + const l10nReg = new L10nRegistry(); + + window.onload = function() { + SimpleTest.waitForExplicitFinish(); + + const domLoc = new DOMLocalization( + [], + false, + l10nReg, + ); + + const p1 = document.querySelectorAll("p")[0]; + + domLoc.setAttributes(p1, "title"); + is( + p1.getAttribute("data-l10n-id"), + "title", + "The data-l10n-id can be set by setAttributes." + ); + is( + p1.getAttribute("data-l10n-args"), + null, + "The data-l10n-args is unset." + ); + + + domLoc.setAttributes(p1, "title2", {userName: "John"}); + is( + p1.getAttribute("data-l10n-id"), + "title2", + "The data-l10n-id can be set by setAttributes." + ); + is( + p1.getAttribute("data-l10n-args"), + JSON.stringify({userName: "John"}), + "The data-l10n-args can be set by setAttributes." + ); + + domLoc.setArgs(p1, {userName: "Jane"}); + is( + p1.getAttribute("data-l10n-id"), + "title2", + "The data-l10n-id is unchanged by setArgs." + ); + is( + p1.getAttribute("data-l10n-args"), + JSON.stringify({userName: "Jane"}), + "The data-l10n-args can by set by setArgs." + ); + + domLoc.setArgs(p1); + is( + p1.getAttribute("data-l10n-id"), + "title2", + "The data-l10n-id is unchanged by setArgs." + ); + is( + p1.getAttribute("data-l10n-args"), + null, + "The data-l10n-args be unset by setArgs." + ); + + SimpleTest.finish(); + }; + </script> +</head> +<body> + <p /> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/dom_localization/test_translateElements.html b/dom/l10n/tests/mochitest/dom_localization/test_translateElements.html new file mode 100644 index 0000000000..7893309ce4 --- /dev/null +++ b/dom/l10n/tests/mochitest/dom_localization/test_translateElements.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test DOMLocalization.prototype.translateElements</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript"> + "use strict"; + const l10nReg = new L10nRegistry(); + const fs = [ + { path: "/localization/en-US/mock.ftl", source: ` +title = Hello World +link = + .title = Click me +` }, + ]; + const source = L10nFileSource.createMock("test", "app", ["en-US"], "/localization/{locale}", fs); + l10nReg.registerSources([source]); + + window.onload = async function() { + SimpleTest.waitForExplicitFinish(); + + const domLoc = new DOMLocalization( + ["/mock.ftl"], + false, + l10nReg, + ["en-US"], + ); + + const p1 = document.querySelectorAll("p")[0]; + const link1 = document.querySelectorAll("a")[0]; + + await domLoc.translateElements([p1, link1]); + + is(p1.textContent, "Hello World"); + is(link1.getAttribute("title"), "Click me"); + + SimpleTest.finish(); + }; + </script> +</head> +<body> + <p data-l10n-id="title" /> + <a data-l10n-id="link" /> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/dom_localization/test_translateFragment.html b/dom/l10n/tests/mochitest/dom_localization/test_translateFragment.html new file mode 100644 index 0000000000..c5ccfd996a --- /dev/null +++ b/dom/l10n/tests/mochitest/dom_localization/test_translateFragment.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test DOMLocalization.prototype.translateFragment</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript"> + "use strict"; + const l10nReg = new L10nRegistry(); + const fs = [ + { path: "/localization/en-US/mock.ftl", source: ` +title = Hello World +subtitle = Welcome to FluentBundle +` }, + ]; + const source = L10nFileSource.createMock("test", "app", ["en-US"], "/localization/{locale}", fs); + l10nReg.registerSources([source]); + + window.onload = async function() { + SimpleTest.waitForExplicitFinish(); + + const domLoc = new DOMLocalization( + ["/mock.ftl"], + false, + l10nReg, + ["en-US"], + ); + + const frag = document.querySelectorAll("div")[0]; + const h1 = document.querySelectorAll("h1")[0]; + const p1 = document.querySelectorAll("p")[0]; + + await domLoc.translateFragment(frag); + is(h1.textContent, "Hello World"); + is(p1.textContent, "Welcome to FluentBundle"); + + SimpleTest.finish(); + }; + </script> +</head> +<body> + <div> + <h1 data-l10n-id="title" /> + <p data-l10n-id="subtitle" /> + </div> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/dom_localization/test_translateRoots.html b/dom/l10n/tests/mochitest/dom_localization/test_translateRoots.html new file mode 100644 index 0000000000..58bfb044b0 --- /dev/null +++ b/dom/l10n/tests/mochitest/dom_localization/test_translateRoots.html @@ -0,0 +1,56 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test DOMLocalization.prototype.translateRoots</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript"> + "use strict"; + const l10nReg = new L10nRegistry(); + const fs = [ + { path: "/localization/en-US/mock.ftl", source: ` +title = Hello World +title2 = Hello Another World +` }, + ]; + const source = L10nFileSource.createMock("test", "app", ["en-US"], "/localization/{locale}", fs); + l10nReg.registerSources([source]); + + window.onload = async function() { + SimpleTest.waitForExplicitFinish(); + + const domLoc = new DOMLocalization( + [], + false, + l10nReg, + ["en-US"], + ); + + const frag1 = document.querySelectorAll("div")[0]; + const frag2 = document.querySelectorAll("div")[1]; + const h1 = document.querySelectorAll("h1")[0]; + const h2 = document.querySelectorAll("h2")[0]; + + domLoc.addResourceIds(["/mock.ftl"]); + domLoc.connectRoot(frag1); + domLoc.connectRoot(frag2); + + await domLoc.translateRoots(); + + is(h1.textContent, "Hello World"); + is(h2.textContent, "Hello Another World"); + + SimpleTest.finish(); + }; + </script> +</head> +<body> + <div> + <h1 data-l10n-id="title"></h1> + </div> + <div> + <h2 data-l10n-id="title2"></h2> + </div> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/l10n_mutations/test_append_content_post_dcl.html b/dom/l10n/tests/mochitest/l10n_mutations/test_append_content_post_dcl.html new file mode 100644 index 0000000000..e3a9819728 --- /dev/null +++ b/dom/l10n/tests/mochitest/l10n_mutations/test_append_content_post_dcl.html @@ -0,0 +1,30 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test L10n Mutations for ContentAppended after DOMContentLoaded</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <link rel="localization" href="crashreporter/aboutcrashes.ftl"/> + <script type="application/javascript"> + "use strict"; + SimpleTest.waitForExplicitFinish(); + + document.addEventListener("DOMContentLoaded", function() { + let elem = document.createElement("div"); + document.l10n.setAttributes(elem, "crash-reports-title"); + is(elem.textContent.length, 0); + let verifyL10n = () => { + if (elem.textContent.length) { + window.removeEventListener("MozAfterPaint", verifyL10n); + SimpleTest.finish(); + } + }; + window.addEventListener("MozAfterPaint", verifyL10n); + document.body.appendChild(elem); + }); + </script> +</head> +<body> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/l10n_mutations/test_append_content_pre_dcl.html b/dom/l10n/tests/mochitest/l10n_mutations/test_append_content_pre_dcl.html new file mode 100644 index 0000000000..825464e7b8 --- /dev/null +++ b/dom/l10n/tests/mochitest/l10n_mutations/test_append_content_pre_dcl.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test L10n Mutations for ContentAppended before DOMContentLoaded</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <link rel="localization" href="crashreporter/aboutcrashes.ftl"/> +</head> +<body> + <script type="application/javascript"> + "use strict"; + SimpleTest.waitForExplicitFinish(); + + let elem = document.createElement("div"); + document.l10n.setAttributes(elem, "crash-reports-title"); + is(elem.textContent.length, 0); + let verifyL10n = () => { + if (elem.textContent.length) { + window.removeEventListener("MozAfterPaint", verifyL10n); + SimpleTest.finish(); + } + }; + window.addEventListener("MozAfterPaint", verifyL10n); + document.body.appendChild(elem); + </script> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/l10n_mutations/test_append_fragment_post_dcl.html b/dom/l10n/tests/mochitest/l10n_mutations/test_append_fragment_post_dcl.html new file mode 100644 index 0000000000..8efb3203c6 --- /dev/null +++ b/dom/l10n/tests/mochitest/l10n_mutations/test_append_fragment_post_dcl.html @@ -0,0 +1,39 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test L10n Mutations for appending a fragment after DOMContentLoaded</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <link rel="localization" href="crashreporter/aboutcrashes.ftl"/> + <script type="application/javascript"> + "use strict"; + SimpleTest.waitForExplicitFinish(); + + document.addEventListener("DOMContentLoaded", function() { + let frag = document.createDocumentFragment(); + let elem = document.createElement("div"); + document.l10n.setAttributes(elem, "crash-reports-title"); + frag.appendChild(elem); + + let elem2 = document.createElement("div"); + document.l10n.setAttributes(elem2, "crash-reports-title"); + frag.appendChild(elem2); + + is(elem.textContent.length, 0); + is(elem2.textContent.length, 0); + + let verifyL10n = () => { + if (elem.textContent.length && elem2.textContent.length) { + window.removeEventListener("MozAfterPaint", verifyL10n); + SimpleTest.finish(); + } + }; + window.addEventListener("MozAfterPaint", verifyL10n); + document.body.appendChild(frag); + }); + </script> +</head> +<body> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/l10n_mutations/test_disconnectedRoot_webcomponent.html b/dom/l10n/tests/mochitest/l10n_mutations/test_disconnectedRoot_webcomponent.html new file mode 100644 index 0000000000..bb9d9fc24d --- /dev/null +++ b/dom/l10n/tests/mochitest/l10n_mutations/test_disconnectedRoot_webcomponent.html @@ -0,0 +1,148 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test DOMLocalization.prototype.connectRoot with Web Components</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript"> + "use strict"; + const l10nReg = new L10nRegistry(); + const fs = [ + { path: "/localization/en-US/mock.ftl", source: ` +key1 = Key 1 +key2 = Key 2 +key3 = Key 3 +key4 = Key 4 +` }, + ]; + const source = L10nFileSource.createMock("test", "app", ["en-US"], "/localization/{locale}", fs); + l10nReg.registerSources([source]); + + document.domLoc = new DOMLocalization( + ["/mock.ftl"], + false, + l10nReg, + ["en-US"], + ); + document.domLoc.connectRoot(document.documentElement); + </script> + <script type="application/javascript"> + // In this test we're going to use two elements - `shadowLabel` and `lightLabel`. + // We create a new `DOMLocalization` and connect it to the document's root first. + // + // Then, we connect and disconnect it on root and element within the shadow DOM and + // apply new `data-l10n-id` onto both labels. + // Once the `lightLabel` get a new translation, we check what happened to the `shadowLabel` + // to ensure that depending on the status of connection between the shadow DOM and the `DOMLocalization` + // the `shadowLabel` either gets translated or not. + + SimpleTest.waitForExplicitFinish(); + + class FluentWidget extends HTMLElement { + constructor() { + super(); + const shadowRoot = this.attachShadow({mode: "open"}); + const t = document.querySelector("#fluent-widget-template"); + const instance = t.content.cloneNode(true); + shadowRoot.appendChild(instance); + } + connectedCallback() { + this.runTests(); + } + runTests() { + // First, let's verify that the mutation will not be applied since + // the shadow DOM is not connected to the `DOMLocalization`. + let shadowLabel = this.shadowRoot.getElementById("shadowLabel"); + let lightLabel = document.getElementById("lightLabel"); + + let verifyL10n = () => { + if (lightLabel.textContent == "Key 1") { + is(shadowLabel.textContent, "", "document.l10n content not applied to an element in the shadow DOM"); + window.removeEventListener("MozAfterPaint", verifyL10n); + this.testPart2(); + } + }; + window.addEventListener("MozAfterPaint", verifyL10n); + + document.domLoc.setAttributes(shadowLabel, "key1"); + document.domLoc.setAttributes(lightLabel, "key1"); + } + testPart2() { + // Next, we connect the shadow root to DOMLocalization and the next attribute + // change should result in a translation being applied. + document.domLoc.connectRoot(this.shadowRoot); + + let shadowLabel = this.shadowRoot.getElementById("shadowLabel"); + let lightLabel = document.getElementById("lightLabel"); + + // Test that mutation was applied. + let verifyL10n = () => { + if (lightLabel.textContent == "Key 2") { + is(shadowLabel.textContent, "Key 2", "document.l10n content applied to an element in the shadow DOM"); + window.removeEventListener("MozAfterPaint", verifyL10n); + this.testPart3(); + } + }; + window.addEventListener("MozAfterPaint", verifyL10n); + + document.domLoc.setAttributes(shadowLabel, "key2"); + document.domLoc.setAttributes(lightLabel, "key2"); + } + testPart3() { + // After we disconnect the shadow root, the mutations should + // not be applied onto the `shadowLabel`. + document.domLoc.disconnectRoot(this.shadowRoot); + + let shadowLabel = this.shadowRoot.getElementById("shadowLabel"); + let lightLabel = document.getElementById("lightLabel"); + + let verifyL10n = () => { + if (lightLabel.textContent == "Key 3") { + is(shadowLabel.textContent, "Key 2", "document.l10n content not applied to an element in the shadow DOM"); + window.removeEventListener("MozAfterPaint", verifyL10n); + this.testPart4(); + } + }; + window.addEventListener("MozAfterPaint", verifyL10n); + + document.domLoc.setAttributes(shadowLabel, "key3"); + document.domLoc.setAttributes(lightLabel, "key3"); + } + testPart4() { + // Finally, we'll connect it back, but this time, we'll connect + // not the shadow root, but an element within it. + // This should still result in the `shadowLabel` receiving a new translation. + document.domLoc.connectRoot(this.shadowRoot.getElementById("shadowDiv")); + + let shadowLabel = this.shadowRoot.getElementById("shadowLabel"); + let lightLabel = document.getElementById("lightLabel"); + + // Test that mutation was applied. + let verifyL10n = () => { + if (lightLabel.textContent == "Key 4") { + is(shadowLabel.textContent, "Key 4", "document.l10n content applied to an element in the shadow DOM"); + window.removeEventListener("MozAfterPaint", verifyL10n); + SimpleTest.finish(); + } + }; + window.addEventListener("MozAfterPaint", verifyL10n); + + document.domLoc.setAttributes(shadowLabel, "key4"); + document.domLoc.setAttributes(lightLabel, "key4"); + } + } + customElements.define("fluent-widget", FluentWidget); + </script> +</head> +<body> + <p id="lightLabel"></p> + + <template id="fluent-widget-template"> + <div id="shadowDiv"> + <p id="shadowLabel"></p> + </div> + </template> + <fluent-widget id="widget1"></fluent-widget> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/l10n_mutations/test_pause_observing.html b/dom/l10n/tests/mochitest/l10n_mutations/test_pause_observing.html new file mode 100644 index 0000000000..d225153418 --- /dev/null +++ b/dom/l10n/tests/mochitest/l10n_mutations/test_pause_observing.html @@ -0,0 +1,44 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test L10n Mutations for Pause/Resume Observing</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <link rel="localization" href="crashreporter/aboutcrashes.ftl"/> + <script type="application/javascript"> + "use strict"; + SimpleTest.waitForExplicitFinish(); + + document.addEventListener("DOMContentLoaded", function() { + let elem1 = document.createElement("div"); + let elem2 = document.createElement("div"); + let elem3 = document.createElement("div"); + is(elem1.textContent.length, 0); + is(elem2.textContent.length, 0); + is(elem3.textContent.length, 0); + + document.l10n.setAttributes(elem1, "crash-reports-title"); + document.l10n.setAttributes(elem2, "crash-reports-title"); + document.l10n.setAttributes(elem3, "crash-reports-title"); + + let verifyL10n = () => { + if (elem1.textContent.length && + !elem2.textContent.length && + elem3.textContent.length) { + window.removeEventListener("MozAfterPaint", verifyL10n); + SimpleTest.finish(); + } + }; + window.addEventListener("MozAfterPaint", verifyL10n); + document.body.appendChild(elem1); + document.l10n.pauseObserving(); + document.body.appendChild(elem2); + document.l10n.resumeObserving(); + document.body.appendChild(elem3); + }); + </script> +</head> +<body> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/l10n_mutations/test_remove_element.html b/dom/l10n/tests/mochitest/l10n_mutations/test_remove_element.html new file mode 100644 index 0000000000..347e858d52 --- /dev/null +++ b/dom/l10n/tests/mochitest/l10n_mutations/test_remove_element.html @@ -0,0 +1,68 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test L10n Mutations for removing element</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <link rel="localization" href="crashreporter/aboutcrashes.ftl"/> + <script type="application/javascript"> + "use strict"; + SimpleTest.waitForExplicitFinish(); + + document.addEventListener("DOMContentLoaded", async function() { + // This element will be added to DOM and expected to be localized. + let elem = document.createElement("div"); + + // This element will be added to DOM, then immediatelly removed and + // expected *not* to be localized. + let elem2 = document.createElement("div"); + + // This element will be added to DOM, then immediatelly removed and + // and then immediatelly re-added, and expected to be localized. + let elem3 = document.createElement("div"); + + // This element will be added to DOM, then immediatelly removed and + // and then re-added later, and expected to be localized. + let elem4 = document.createElement("div"); + + document.l10n.setAttributes(elem, "crash-reports-title"); + document.l10n.setAttributes(elem2, "crash-reports-title"); + document.l10n.setAttributes(elem3, "crash-reports-title"); + document.l10n.setAttributes(elem4, "crash-reports-title"); + + document.body.appendChild(elem); + document.body.appendChild(elem2); + document.body.appendChild(elem3); + document.body.appendChild(elem4); + + is(elem.textContent.length, 0); + is(elem2.textContent.length, 0); + is(elem3.textContent.length, 0); + is(elem4.textContent.length, 0); + + document.body.removeChild(elem2); + document.body.removeChild(elem3); + document.body.removeChild(elem4); + + document.body.appendChild(elem3); + + // 1. `elem` should be localized since it is in DOM. + await SimpleTest.waitForCondition(() => elem.textContent.length); + // 2. `elem2` was removed before l10n frame, so it should remain not localized. + is(elem2.textContent.length, 0); + // 3. `elem3` was added/removed/re-added so it should become localized. + await SimpleTest.waitForCondition(() => elem3.textContent.length); + // 4. `elem4` was not re-added, so it shouldn't be localized. + is(elem4.textContent.length, 0); + + document.body.appendChild(elem4); + // 5. Now we re-added `elem4` to DOM so it should get localized. + await SimpleTest.waitForCondition(() => elem4.textContent.length); + SimpleTest.finish(); + }); + </script> +</head> +<body> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/l10n_mutations/test_remove_fragment.html b/dom/l10n/tests/mochitest/l10n_mutations/test_remove_fragment.html new file mode 100644 index 0000000000..4f4d0fe1d8 --- /dev/null +++ b/dom/l10n/tests/mochitest/l10n_mutations/test_remove_fragment.html @@ -0,0 +1,67 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test L10n Mutations for removing fragment</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <link rel="localization" href="crashreporter/aboutcrashes.ftl"/> + <script type="application/javascript"> + "use strict"; + SimpleTest.waitForExplicitFinish(); + + document.addEventListener("DOMContentLoaded", async function() { + let div = document.createElement("div"); + let div2 = document.createElement("div"); + + let elem = document.createElement("p"); + let elem2 = document.createElement("p"); + let elem3 = document.createElement("p"); + let elem4 = document.createElement("p"); + + document.l10n.setAttributes(elem, "crash-reports-title"); + document.l10n.setAttributes(elem2, "crash-reports-title"); + document.l10n.setAttributes(elem3, "crash-reports-title"); + document.l10n.setAttributes(elem4, "crash-reports-title"); + + div.appendChild(elem); + div.appendChild(elem2); + div.appendChild(elem3); + div.appendChild(elem4); + + document.body.appendChild(div); + + is(elem.textContent.length, 0); + is(elem2.textContent.length, 0); + is(elem3.textContent.length, 0); + is(elem4.textContent.length, 0); + + document.body.removeChild(div); + + div2.appendChild(elem); + div2.appendChild(elem3); + + document.body.appendChild(div2); + + // 1. `elem` should be localized since it is in DOM. + await SimpleTest.waitForCondition(() => !!elem.textContent.length); + + // 2. `elem2` was removed before l10n frame, so it should remain not localized. + is(elem2.textContent.length, 0); + + // 3. `elem3` was added/removed/re-added so it should become localized. + await SimpleTest.waitForCondition(() => !!elem3.textContent.length); + + // 4. `elem4` was not re-added, so it shouldn't be localized. + is(elem4.textContent.length, 0); + + document.body.appendChild(div); + // 5. Now we re-added `elem4` to DOM so it should get localized. + await SimpleTest.waitForCondition(() => !!elem4.textContent.length); + SimpleTest.finish(); + }); + </script> +</head> +<body> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/l10n_mutations/test_set_attributes.html b/dom/l10n/tests/mochitest/l10n_mutations/test_set_attributes.html new file mode 100644 index 0000000000..762353a06c --- /dev/null +++ b/dom/l10n/tests/mochitest/l10n_mutations/test_set_attributes.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test L10n Mutations for AttributeChange after DOMContentLoaded</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <link rel="localization" href="crashreporter/aboutcrashes.ftl"/> + <link rel="localization" href="toolkit/about/aboutCompat.ftl"/> + <script type="application/javascript"> + "use strict"; + SimpleTest.waitForExplicitFinish(); + + document.addEventListener("DOMContentLoaded", async function() { + await document.l10n.ready; + let elem = document.getElementById("elem1"); + let elem2 = document.getElementById("elem2"); + is(elem.textContent.length, 0); + is(elem2.textContent.includes("Initial string"), true); + document.l10n.setAttributes(elem, "crash-reports-title"); + elem2.setAttribute("data-l10n-args", JSON.stringify({bug: "New string"})); + + let verifyL10n = () => { + if (elem.textContent.length && elem2.textContent.includes("New string")) { + window.removeEventListener("MozAfterPaint", verifyL10n); + SimpleTest.finish(); + } + }; + window.addEventListener("MozAfterPaint", verifyL10n); + }); + </script> +</head> +<body> + <div id="elem1"></div> + <div id="elem2" data-l10n-id="label-more-information" data-l10n-args='{"bug":"Initial string"}'></div> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/l10n_mutations/test_template.html b/dom/l10n/tests/mochitest/l10n_mutations/test_template.html new file mode 100644 index 0000000000..d02ecfcd4d --- /dev/null +++ b/dom/l10n/tests/mochitest/l10n_mutations/test_template.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test L10n Mutations in Template elements</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <link rel="localization" href="crashreporter/aboutcrashes.ftl"/> + <script type="application/javascript"> + "use strict"; + SimpleTest.waitForExplicitFinish(); + + document.addEventListener("DOMContentLoaded", async () => { + await document.l10n.ready; + let template = document.getElementById("template"); + let clone = document.importNode(template.content, true); + let span = clone.querySelector("span"); + is(span.textContent.length, 0, + "Element has not been translated while in template"); + document.body.appendChild(clone); + + let verifyL10n = () => { + if (span.textContent.length) { + window.removeEventListener("MozAfterPaint", verifyL10n); + SimpleTest.finish(); + } + }; + window.addEventListener("MozAfterPaint", verifyL10n); + }); + </script> +</head> +<body> + <template id="template"> + <span data-l10n-id="crash-reports-title"></span> + </template> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/l10n_overlays/test_attributes.html b/dom/l10n/tests/mochitest/l10n_overlays/test_attributes.html new file mode 100644 index 0000000000..3d1f6048b2 --- /dev/null +++ b/dom/l10n/tests/mochitest/l10n_overlays/test_attributes.html @@ -0,0 +1,86 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test L10nOverlays Top-level attributes</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript"> + /* global L10nOverlays */ + "use strict"; + + function elem(name) { + return function(str) { + const element = document.createElement(name); + // eslint-disable-next-line no-unsanitized/property + element.innerHTML = str; + return element; + }; + } + + const { translateElement } = L10nOverlays; + + { + // Allowed attribute + const element = elem("div")``; + const translation = { + value: null, + attributes: [ + {name: "title", value: "FOO"}, + ], + }; + translateElement(element, translation); + is(element.outerHTML, '<div title="FOO"></div>'); + } + + { + // Forbidden attribute + const element = elem("input")``; + const translation = { + value: null, + attributes: [ + {name: "disabled", value: "DISABLED"}, + ], + }; + translateElement(element, translation); + is(element.outerHTML, "<input>"); + } + + { + // Attributes do not leak on first translation + const element = elem("div")`Foo`; + element.setAttribute("title", "Title"); + + const translation = { + value: "FOO", + attributes: null, + }; + translateElement(element, translation); + is(element.outerHTML, "<div>FOO</div>"); + } + + { + // Attributes do not leak on retranslation + const element = elem("div")`Foo`; + + const translationA = { + value: "FOO A", + attributes: [ + {name: "title", value: "TITLE A"}, + ], + }; + + const translationB = { + value: "FOO B", + attributes: null, + }; + translateElement(element, translationA); + is(element.outerHTML, '<div title="TITLE A">FOO A</div>'); + translateElement(element, translationB); + is(element.outerHTML, "<div>FOO B</div>"); + } + </script> +</head> +<body> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/l10n_overlays/test_extra_text_markup.html b/dom/l10n/tests/mochitest/l10n_overlays/test_extra_text_markup.html new file mode 100644 index 0000000000..5f60599dfc --- /dev/null +++ b/dom/l10n/tests/mochitest/l10n_overlays/test_extra_text_markup.html @@ -0,0 +1,136 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test L10nOverlays Localized text markup</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript"> + /* global L10nOverlays */ + "use strict"; + + function elem(name) { + return function(str) { + const element = document.createElement(name); + // eslint-disable-next-line no-unsanitized/property + element.innerHTML = str; + return element; + }; + } + + const { translateElement } = L10nOverlays; + + // Localized text markup + { + // allowed element + const element = elem("div")`Foo`; + const translation = { + value: "FOO <em>BAR</em> BAZ", + attributes: null, + }; + + translateElement(element, translation); + is(element.innerHTML, "FOO <em>BAR</em> BAZ"); + } + + { + // forbidden element + const element = elem("div")`Foo`; + const translation = { + value: 'FOO <img src="img.png" />', + attributes: null, + }; + + translateElement(element, translation); + is(element.innerHTML, "FOO "); + } + + { + // forbiden element with text + const element = elem("div")`Foo`; + const translation = { + value: "FOO <a>a</a>", + attributes: null, + }; + + translateElement(element, translation); + is(element.innerHTML, "FOO a"); + } + + { + // nested HTML is forbidden + const element = elem("div")`Foo`; + const translation = { + value: "FOO <em><strong>BAR</strong></em> BAZ", + attributes: null, + }; + + translateElement(element, translation); + is(element.innerHTML, "FOO <em>BAR</em> BAZ"); + } + + // Attributes of localized text markup + { + // allowed attribute + const element = elem("div")`Foo Bar`; + const translation = { + value: 'FOO <em title="BAR">BAR</em>', + attributes: null, + }; + + translateElement(element, translation); + is(element.innerHTML, + 'FOO <em title="BAR">BAR</em>'); + } + + { + // forbidden attribute + const element = elem("div")`Foo Bar`; + const translation = { + value: 'FOO <em class="BAR" title="BAR">BAR</em>', + attributes: null, + }; + + translateElement(element, translation); + is(element.innerHTML, + 'FOO <em title="BAR">BAR</em>'); + } + + { + // attributes do not leak on first translation + const element = elem("div")` + <em title="Foo">Foo</a>`; + const translation = { + value: "<em>FOO</em>", + attributes: null, + }; + + translateElement(element, translation); + is(element.innerHTML, + "<em>FOO</em>"); + } + + { + // attributes do not leak on retranslation + const element = elem("div")``; + const translationA = { + value: '<em title="FOO A">FOO A</em>', + attributes: null, + }; + const translationB = { + value: "<em>FOO B</em>", + attributes: null, + }; + + translateElement(element, translationA); + is(element.innerHTML, + '<em title="FOO A">FOO A</em>'); + translateElement(element, translationB); + is(element.innerHTML, + "<em>FOO B</em>"); + } + </script> +</head> +<body> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/l10n_overlays/test_functional_children.html b/dom/l10n/tests/mochitest/l10n_overlays/test_functional_children.html new file mode 100644 index 0000000000..dba5b6e633 --- /dev/null +++ b/dom/l10n/tests/mochitest/l10n_overlays/test_functional_children.html @@ -0,0 +1,344 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test L10nOverlays functional children test</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript"> + /* global L10nOverlays */ + "use strict"; + + function elem(name) { + return function(str) { + const element = document.createElement(name); + // eslint-disable-next-line no-unsanitized/property + element.innerHTML = str; + return element; + }; + } + + const { translateElement } = L10nOverlays; + + // Child without name + { + // in source + const element = elem("div")` + <a>Foo</a>`; + const translation = { + value: "FOO", + attributes: null, + }; + translateElement(element, translation); + is(element.innerHTML, "FOO"); + } + + { + // in translation + const element = elem("div")`Foo`; + const translation = { + value: "<a>FOO</a>", + attributes: null, + }; + translateElement(element, translation); + is(element.innerHTML, "FOO"); + } + + { + // in both + const element = elem("div")` + <a>Foo</a>`; + const translation = { + value: "<a>FOO</a>", + attributes: null, + }; + translateElement(element, translation); + is(element.innerHTML, "FOO"); + } + + // Child with name + { + // in source + const element = elem("div")` + <a data-l10n-name="foo">Foo</a>`; + const translation = { + value: "<a>FOO</a>", + attributes: null, + }; + translateElement(element, translation); + is(element.innerHTML, "FOO"); + } + + { + // in translation + const element = elem("div")` + <a>Foo</a>`; + const translation = { + value: '<a data-l10n-name="foo">FOO</a>', + attributes: null, + }; + translateElement(element, translation); + is(element.innerHTML, "FOO"); + } + + { + // in both + const element = elem("div")` + <a data-l10n-name="foo">Foo</a>`; + const translation = { + value: '<a data-l10n-name="foo">FOO</a>', + attributes: null, + }; + translateElement(element, translation); + is(element.innerHTML, '<a data-l10n-name="foo">FOO</a>'); + } + + { + // translation without text content + const element = elem("div")` + <a data-l10n-name="foo">Foo</a>`; + const translation = { + value: '<a data-l10n-name="foo"></a>', + attributes: null, + }; + translateElement(element, translation); + is(element.innerHTML, '<a data-l10n-name="foo"></a>'); + } + + { + // different names + const element = elem("div")` + <a data-l10n-name="foo">Foo</a>`; + const translation = { + value: '<a data-l10n-name="bar">BAR</a>', + attributes: null, + }; + translateElement(element, translation); + is(element.innerHTML, "BAR"); + } + + { + // of different types + const element = elem("div")` + <a data-l10n-name="foo">Foo</a>`; + const translation = { + value: '<div data-l10n-name="foo">FOO</div>', + attributes: null, + }; + translateElement(element, translation); + is(element.innerHTML, "FOO"); + } + + { + // used twice + const element = elem("div")` + <a data-l10n-name="foo">Foo</a>`; + const translation = { + value: '<a data-l10n-name="foo">FOO 1</a> <a data-l10n-name="foo">FOO 2</a>', + attributes: null, + }; + translateElement(element, translation); + is(element.innerHTML, '<a data-l10n-name="foo">FOO 1</a> FOO 2'); + } + + // Two named children + { + // in order + const element = elem("div")` + <a data-l10n-name="foo">Foo</a> + <a data-l10n-name="bar">Bar</a>`; + const translation = { + value: '<a data-l10n-name="foo">FOO</a><a data-l10n-name="bar">BAR</a>', + attributes: null, + }; + translateElement(element, translation); + is(element.innerHTML, '<a data-l10n-name="foo">FOO</a><a data-l10n-name="bar">BAR</a>'); + } + + { + // out of order + const element = elem("div")` + <a data-l10n-name="foo">Foo</a> + <a data-l10n-name="bar">Bar</a>`; + const translation = { + value: '<a data-l10n-name="bar">BAR</a><a data-l10n-name="foo">FOO</a>', + attributes: null, + }; + translateElement(element, translation); + is(element.innerHTML, '<a data-l10n-name="bar">BAR</a><a data-l10n-name="foo">FOO</a>'); + } + + { + // nested in source + const element = elem("div")` + <a data-l10n-name="foo"> + Foo 1 + <a data-l10n-name="bar">Bar</a> + Foo 2 + </a>`; + const translation = { + value: '<a data-l10n-name="foo">FOO</a><a data-l10n-name="bar">BAR</a>', + attributes: null, + }; + translateElement(element, translation); + is( + element.innerHTML, + '<a data-l10n-name="foo">FOO</a><a data-l10n-name="bar">BAR</a>' + ); + } + + { + // nested in translation + const element = elem("div")` + <div data-l10n-name="foo">Foo</div> + <div data-l10n-name="bar">Bar</div>`; + const translation = { + value: '<div data-l10n-name="foo">FOO 1 <div data-l10n-name="bar">BAR</div> FOO 2</div>', + attributes: null, + }; + translateElement(element, translation); + is( + element.innerHTML, + '<div data-l10n-name="foo">FOO 1 BAR FOO 2</div>' + ); + } + + // Child attributes + { + // functional attribute in source + const element = elem("div")` + <a data-l10n-name="foo" class="foo">Foo</a>`; + const translation = { + value: '<a data-l10n-name="foo">FOO</a>', + attributes: null, + }; + + translateElement(element, translation); + is(element.innerHTML, + '<a data-l10n-name="foo" class="foo">FOO</a>'); + } + + { + // functional attribute in translation + const element = elem("div")` + <a data-l10n-name="foo">Foo</a>`; + const translation = { + value: '<a data-l10n-name="foo" class="bar">FOO</a>', + attributes: null, + }; + + translateElement(element, translation); + is(element.innerHTML, + '<a data-l10n-name="foo">FOO</a>'); + } + + { + // functional attribute in both + const element = elem("div")` + <a data-l10n-name="foo" class="foo">Foo</a>`; + const translation = { + value: '<a data-l10n-name="foo" class="bar">FOO</a>', + attributes: null, + }; + + translateElement(element, translation); + is(element.innerHTML, + '<a data-l10n-name="foo" class="foo">FOO</a>'); + } + + { + // localizable attribute in source + const element = elem("div")` + <a data-l10n-name="foo" title="Foo">Foo</a>`; + const translation = { + value: '<a data-l10n-name="foo">FOO</a>', + attributes: null, + }; + + translateElement(element, translation); + is(element.innerHTML, + '<a data-l10n-name="foo">FOO</a>'); + } + + { + // localizable attribute in translation + const element = elem("div")` + <a data-l10n-name="foo">Foo</a>`; + const translation = { + value: '<a data-l10n-name="foo" title="FOO">FOO</a>', + attributes: null, + }; + + translateElement(element, translation); + is(element.innerHTML, + '<a data-l10n-name="foo" title="FOO">FOO</a>'); + } + + { + // localizable attribute in both + const element = elem("div")` + <a data-l10n-name="foo" title="Foo">Foo</a>`; + const translation = { + value: '<a data-l10n-name="foo" title="BAR">FOO</a>', + attributes: null, + }; + + translateElement(element, translation); + is(element.innerHTML, + '<a data-l10n-name="foo" title="BAR">FOO</a>'); + } + + { + // localizable attribute does not leak on retranslation + const element = elem("div")` + <a data-l10n-name="foo">Foo</a>`; + const translationA = { + value: '<a data-l10n-name="foo" title="FOO A">FOO A</a>', + attributes: null, + }; + const translationB = { + value: '<a data-l10n-name="foo">FOO B</a>', + attributes: null, + }; + + translateElement(element, translationA); + is(element.innerHTML, + '<a data-l10n-name="foo" title="FOO A">FOO A</a>'); + translateElement(element, translationB); + is(element.innerHTML, + '<a data-l10n-name="foo">FOO B</a>'); + } + + // Child attributes overrides + { + // the source can override child's attributes + const element = elem("div")` + <a data-l10n-name="foo" data-l10n-attrs="class" class="foo">Foo</a>`; + const translation = { + value: '<a data-l10n-name="foo" class="FOO">FOO</a>', + attributes: null, + }; + + translateElement(element, translation); + is(element.innerHTML, + '<a data-l10n-name="foo" data-l10n-attrs="class" class="FOO">FOO</a>'); + } + + { + // the translation cannot override child's attributes + const element = elem("div")` + <a data-l10n-name="foo" class="foo">Foo</a>`; + const translation = { + value: '<a data-l10n-name="foo" data-l10n-attrs="class" class="FOO">FOO</a>', + attributes: null, + }; + + translateElement(element, translation); + is(element.innerHTML, + '<a data-l10n-name="foo" class="foo">FOO</a>'); + } + </script> +</head> +<body> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/l10n_overlays/test_l10n_overlays.xhtml b/dom/l10n/tests/mochitest/l10n_overlays/test_l10n_overlays.xhtml new file mode 100644 index 0000000000..494958c573 --- /dev/null +++ b/dom/l10n/tests/mochitest/l10n_overlays/test_l10n_overlays.xhtml @@ -0,0 +1,87 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + title="Testing DocumentL10n in XUL environment"> + + <linkset> + <html:link rel="localization" href="toolkit/about/aboutAddons.ftl"/> + </linkset> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript"> + <![CDATA[ + /* global L10nOverlays */ + + function elem(name) { + return function(str) { + const element = document.createXULElement(name); + // eslint-disable-next-line no-unsanitized/property + element.innerHTML = str; + return element; + }; + } + + const { translateElement } = L10nOverlays; + + SimpleTest.waitForExplicitFinish(); + + { + // Allowed attribute + const element = elem("description")``; + const translation = { + value: null, + attributes: [ + {name: "title", value: "FOO"}, + ], + }; + translateElement(element, translation); + is(element.outerHTML, '<description xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" title="FOO"/>'); + } + + document.addEventListener("DOMContentLoaded", () => { + { + // Handle HTML translation + const element = document.getElementById("test2"); + const translation = { + value: "This is <a data-l10n-name=\"link\">a link</a>.", + attributes: null, + }; + translateElement(element, translation); + is(element.innerHTML, 'This is <html:a xmlns:html="http://www.w3.org/1999/xhtml" data-l10n-name=\"link\" href="https://www.mozilla.org\">a link</html:a>.'); + } + + { + // Don't handle XUL translation + // + // Current iteration of L10nOverlays will replace + // XUL elements from translation with text. + // + // See bug 1545704 for details. + const element = document.getElementById("test3"); + const translation = { + value: "This is <description data-l10n-name=\"desc\">a desc</description>.", + attributes: null, + }; + translateElement(element, translation); + is(element.innerHTML, 'This is a desc.'); + } + SimpleTest.finish(); + }, {once: true}); + + ]]> + </script> + + <description id="test2"> + <html:a data-l10n-name="link" href="https://www.mozilla.org"/> + </description> + + <box id="test3"> + <description data-l10n-name="desc"/> + </box> +</window> diff --git a/dom/l10n/tests/mochitest/l10n_overlays/test_same_id.html b/dom/l10n/tests/mochitest/l10n_overlays/test_same_id.html new file mode 100644 index 0000000000..ecaefbd68d --- /dev/null +++ b/dom/l10n/tests/mochitest/l10n_overlays/test_same_id.html @@ -0,0 +1,57 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test Amount of mutations generated from DOM Overlays</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <link rel="localization" href="toolkit/about/aboutTelemetry.ftl"/> + <script type="application/javascript"> + "use strict"; + SimpleTest.waitForExplicitFinish(); + + let config = { + attributes: true, + attributeOldValue: true, + characterData: true, + characterDataOldValue: true, + childList: true, + subtree: true, + }; + let allMutations = []; + + document.addEventListener("DOMContentLoaded", async function() { + await document.l10n.ready; + + let inputElem = document.getElementById("search-input"); + + // Test for initial localization applied. + is(!!inputElem.getAttribute("placeholder").length, true); + + let observer = new MutationObserver((mutations) => { + for (let mutation of mutations) { + allMutations.push(mutation); + } + }); + observer.observe(inputElem, config); + + document.l10n.setAttributes(inputElem, "about-telemetry-filter-all-placeholder"); + + // Due to the async iteractions between nsINode.localize + // and DOMLocalization, we'll need to wait two frames + // to verify that no mutations happened. + requestAnimationFrame(() => { + requestAnimationFrame(() => { + // Since the l10n-id is the same as the previous one + // no mutation should happen in result. + is(allMutations.length, 0); + SimpleTest.finish(); + }); + }); + }, { once: true}); + </script> +</head> +<body> + <input id="search-input" data-l10n-id="about-telemetry-filter-all-placeholder"></input> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/l10n_overlays/test_same_id_args.html b/dom/l10n/tests/mochitest/l10n_overlays/test_same_id_args.html new file mode 100644 index 0000000000..e43c394970 --- /dev/null +++ b/dom/l10n/tests/mochitest/l10n_overlays/test_same_id_args.html @@ -0,0 +1,57 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test Amount of mutations generated from DOM Overlays</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <link rel="localization" href="toolkit/about/aboutTelemetry.ftl"/> + <script type="application/javascript"> + "use strict"; + SimpleTest.waitForExplicitFinish(); + + let config = { + attributes: true, + attributeOldValue: true, + characterData: true, + characterDataOldValue: true, + childList: true, + subtree: true, + }; + let allMutations = []; + + document.addEventListener("DOMContentLoaded", async function() { + await document.l10n.ready; + + let inputElem = document.getElementById("search-input"); + + // Test for initial localization applied. + is(!!inputElem.getAttribute("placeholder").length, true); + + let observer = new MutationObserver((mutations) => { + for (let mutation of mutations) { + allMutations.push(mutation); + } + }); + observer.observe(inputElem, config); + + document.l10n.setAttributes(inputElem, "about-telemetry-filter-placeholder", {selectedTitle: "Test"}); + + // Due to the async iteractions between nsINode.localize + // and DOMLocalization, we'll need to wait two frames + // to verify that no mutations happened. + requestAnimationFrame(() => { + requestAnimationFrame(() => { + // Since the l10n-id is the same as the previous one + // no mutation should happen in result. + is(allMutations.length, 0); + SimpleTest.finish(); + }); + }); + }, { once: true}); + </script> +</head> +<body> + <input id="search-input" data-l10n-id="about-telemetry-filter-placeholder" data-l10n-args='{"selectedTitle":"Test"}'></input> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/l10n_overlays/test_text_children.html b/dom/l10n/tests/mochitest/l10n_overlays/test_text_children.html new file mode 100644 index 0000000000..1c2fab7ade --- /dev/null +++ b/dom/l10n/tests/mochitest/l10n_overlays/test_text_children.html @@ -0,0 +1,74 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test L10nOverlays Text-semantic argument elements</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript"> + /* global L10nOverlays */ + "use strict"; + + function elem(name) { + return function(str) { + const element = document.createElement(name); + // eslint-disable-next-line no-unsanitized/property + element.innerHTML = str; + return element; + }; + } + + const { translateElement } = L10nOverlays; + + { + // without data-l10n-name + const element = elem("div")` + <em class="bar"></em>`; + const translation = { + value: '<em title="FOO">FOO</em>', + attributes: null, + }; + + translateElement(element, translation); + is( + element.innerHTML, + '<em title="FOO">FOO</em>' + ); + } + + { + // mismatched types + const element = elem("div")` + <button data-l10n-name="foo"></button>`; + const translation = { + value: '<em data-l10n-name="foo" title="FOO">FOO</em>', + attributes: null, + }; + + translateElement(element, translation); + is( + element.innerHTML, + "FOO" + ); + } + + { + // types and names mismatch + const element = elem("div")` + <em data-l10n-name="foo" class="foo"></em>`; + const translation = { + value: '<em data-l10n-name="foo" title="FOO">FOO</em>', + attributes: null, + }; + + translateElement(element, translation); + is( + element.innerHTML, + '<em data-l10n-name="foo" class="foo" title="FOO">FOO</em>' + ); + } + </script> +</head> +<body> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/l10n_overlays/test_title.html b/dom/l10n/tests/mochitest/l10n_overlays/test_title.html new file mode 100644 index 0000000000..4571589b8e --- /dev/null +++ b/dom/l10n/tests/mochitest/l10n_overlays/test_title.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test L10nOverlays Special treatment of the title element</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript"> + /* global L10nOverlays */ + "use strict"; + + function elem(name) { + return function(str) { + const element = document.createElement(name); + // eslint-disable-next-line no-unsanitized/property + element.innerHTML = str; + return element; + }; + } + + const { translateElement } = L10nOverlays; + + { + // Text is fine. + const element = elem("title")``; + const translation = { + value: 'Text', + attributes: null, + }; + + translateElement(element, translation); + is( + element.innerHTML, + 'Text' + ); + } + + { + // Markup is ignored. + const element = elem("title")``; + const translation = { + value: '<em>Markup</em>', + attributes: null, + }; + + translateElement(element, translation); + is( + element.textContent, + '<em>Markup</em>' + ); + is( + element.innerHTML, + '<em>Markup</em>' + ); + } + </script> +</head> +<body> +</body> +</html> diff --git a/dom/l10n/tests/mochitest/mochitest.ini b/dom/l10n/tests/mochitest/mochitest.ini new file mode 100644 index 0000000000..82af57c47c --- /dev/null +++ b/dom/l10n/tests/mochitest/mochitest.ini @@ -0,0 +1 @@ +[document_l10n/test_unpriv_iframe.html] |