Bug 1438375 - Refactor "extensionControlled" Preferences code to use Fluent. r=flod,Gijs

MozReview-Commit-ID: 9XJxyuMUCHV

--HG--
extra : rebase_source : 8915fee83b8f84310d8e641c4ea9c87c81435606
This commit is contained in:
Zibi Braniecki 2018-04-17 15:31:50 -07:00
Родитель ec60249c43
Коммит 7ab0501b68
9 изменённых файлов: 309 добавлений и 132 удалений

Просмотреть файл

@ -274,7 +274,7 @@ var gConnectionsDialog = {
hideControllingExtension(PROXY_KEY);
setInputsDisabledState(false);
} else {
handleControllingExtension(PREF_SETTING_TYPE, PROXY_KEY, "extensionControlled.proxyConfig")
handleControllingExtension(PREF_SETTING_TYPE, PROXY_KEY)
.then(setInputsDisabledState);
}
}

Просмотреть файл

@ -20,6 +20,10 @@
ondialoghelp="openPrefsHelp()">
<link rel="localization" href="browser/preferences/connection.ftl"/>
<!-- Used for extension-controlled lockdown message -->
<link rel="localization" href="browser/preferences/preferences.ftl"/>
<link rel="localization" href="branding/brand.ftl"/>
<script type="application/javascript" src="chrome://global/content/l10n.js"></script>
<script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
@ -31,9 +35,6 @@
</keyset>
<vbox id="ConnectionsDialogPane" class="prefpane largeDialogContainer">
<stringbundle id="bundlePreferences" src="chrome://browser/locale/preferences/preferences.properties"/>
<stringbundle id="bundleBrand" src="chrome://branding/locale/brand.properties"/>
<script type="application/javascript" src="chrome://browser/content/preferences/connection.js"/>
<hbox id="proxyExtensionContent" align="top" hidden="true">

Просмотреть файл

@ -57,14 +57,14 @@ let extensionControlledContentIds = {
}
};
function getExtensionControlledArgs(settingName) {
switch (settingName) {
case "proxy.settings":
return [document.getElementById("bundleBrand").getString("brandShortName")];
default:
return [];
}
}
const extensionControlledL10nKeys = {
"homepage_override": "homepage-override",
"newTabURL": "new-tab-url",
"defaultSearch": "default-search",
"privacy.containers": "privacy-containers",
"websites.trackingProtectionMode": "websites-tracking-protection-mode",
"proxy.settings": "proxy-config",
};
let extensionControlledIds = {};
@ -96,7 +96,7 @@ async function getControllingExtension(type, settingName) {
return addon;
}
async function handleControllingExtension(type, settingName, stringId) {
async function handleControllingExtension(type, settingName) {
let addon = await getControllingExtension(type, settingName);
// Sometimes the ExtensionSettingsStore gets in a bad state where it thinks
@ -106,7 +106,7 @@ async function handleControllingExtension(type, settingName, stringId) {
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1411046 for an example.
if (addon) {
extensionControlledIds[settingName] = addon.id;
showControllingExtension(settingName, addon, stringId);
showControllingExtension(settingName, addon);
} else {
let elements = getControllingExtensionEls(settingName);
if (extensionControlledIds[settingName]
@ -122,35 +122,58 @@ async function handleControllingExtension(type, settingName, stringId) {
return !!addon;
}
function getControllingExtensionFragment(stringId, addon, ...extraArgs) {
let msg = document.getElementById("bundlePreferences").getString(stringId);
let image = document.createElement("image");
const defaultIcon = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
image.setAttribute("src", addon.iconURL || defaultIcon);
image.classList.add("extension-controlled-icon");
let addonBit = document.createDocumentFragment();
addonBit.appendChild(image);
addonBit.appendChild(document.createTextNode(" " + addon.name));
return BrowserUtils.getLocalizedFragment(document, msg, addonBit, ...extraArgs);
function settingNameToL10nID(settingName) {
if (!extensionControlledL10nKeys.hasOwnProperty(settingName)) {
throw new Error(`Unknown extension controlled setting name: ${settingName}`);
}
return `extension-controlled-${extensionControlledL10nKeys[settingName]}`;
}
async function showControllingExtension(
settingName, addon, stringId = `extensionControlled.${settingName}`) {
/**
* Set the localization data for the description of the controlling extension.
*
* @param elem {Element}
* <description> element to be annotated
* @param addon {Object?}
* Addon object with meta information about the addon (or null)
* @param settingName {String}
* If `addon` is set this handled the name of the setting that will be used
* to fetch the l10n id for the given message.
* If `addon` is set to null, this will be the full l10n-id assigned to the
* element.
*/
function setControllingExtensionDescription(elem, addon, settingName) {
// Remove the old content from the description.
while (elem.firstChild) {
elem.firstChild.remove();
}
if (addon === null) {
document.l10n.setAttributes(elem, settingName);
return;
}
let image = document.createElementNS("http://www.w3.org/1999/xhtml", "img");
const defaultIcon = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
image.setAttribute("src", addon.iconURL || defaultIcon);
image.setAttribute("data-l10n-name", "icon");
image.classList.add("extension-controlled-icon");
elem.appendChild(image);
const l10nId = settingNameToL10nID(settingName);
document.l10n.setAttributes(elem, l10nId, {
name: addon.name
});
}
async function showControllingExtension(settingName, addon) {
// Tell the user what extension is controlling the setting.
let elements = getControllingExtensionEls(settingName);
let extraArgs = getExtensionControlledArgs(settingName);
elements.section.classList.remove("extension-controlled-disabled");
let description = elements.description;
// Remove the old content from the description.
while (description.firstChild) {
description.firstChild.remove();
}
let fragment = getControllingExtensionFragment(
stringId, addon, ...extraArgs);
description.appendChild(fragment);
setControllingExtensionDescription(description, addon, settingName);
if (elements.button) {
elements.button.hidden = false;
@ -173,19 +196,39 @@ function showEnableExtensionMessage(settingName) {
elements.button.hidden = true;
elements.section.classList.add("extension-controlled-disabled");
let icon = url => {
let img = document.createElement("image");
elements.description.textContent = "";
// We replace localization of the <description> with a DOM Fragment containing
// the enable-extension-enable message. That means a change from:
//
// <description data-l10n-id="..."/>
//
// to:
//
// <description>
// <img/>
// <label data-l10n-id="..."/>
// </description>
//
// We need to remove the l10n-id annotation from the <description> to prevent
// Fluent from overwriting the element in case of any retranslation.
elements.description.removeAttribute("data-l10n-id");
let icon = (url, name) => {
let img = document.createElementNS("http://www.w3.org/1999/xhtml", "img");
img.src = url;
img.setAttribute("data-l10n-name", name);
img.className = "extension-controlled-icon";
return img;
};
let addonIcon = icon("chrome://mozapps/skin/extensions/extensionGeneric-16.svg");
let toolbarIcon = icon("chrome://browser/skin/menu.svg");
let message = document.getElementById("bundlePreferences")
.getString("extensionControlled.enable");
let frag = BrowserUtils.getLocalizedFragment(document, message, addonIcon, toolbarIcon);
elements.description.innerHTML = "";
elements.description.appendChild(frag);
let label = document.createElement("label");
let addonIcon = icon("chrome://mozapps/skin/extensions/extensionGeneric-16.svg", "addons-icon");
let toolbarIcon = icon("chrome://browser/skin/menu.svg", "menu-icon");
label.appendChild(addonIcon);
label.appendChild(toolbarIcon);
document.l10n.setAttributes(label, "extension-controlled-enable");
elements.description.appendChild(label);
let dismissButton = document.createElement("image");
dismissButton.setAttribute("class", "extension-controlled-icon close-icon");
dismissButton.addEventListener("click", function dismissHandler() {

Просмотреть файл

@ -31,7 +31,6 @@ Preferences.addAll([
const HOMEPAGE_OVERRIDE_KEY = "homepage_override";
const URL_OVERRIDES_TYPE = "url_overrides";
const NEW_TAB_KEY = "newTabURL";
const NEW_TAB_STRING_ID = "extensionControlled.newTabURL2";
let gHomePane = {
HOME_MODE_FIREFOX_HOME: "0",
@ -45,7 +44,7 @@ let gHomePane = {
*/
async _handleNewTabOverrides() {
const isControlled = await handleControllingExtension(
URL_OVERRIDES_TYPE, NEW_TAB_KEY, NEW_TAB_STRING_ID);
URL_OVERRIDES_TYPE, NEW_TAB_KEY);
const el = document.getElementById("newTabMode");
el.disabled = isControlled;
},
@ -127,8 +126,7 @@ let gHomePane = {
if (homePref.locked) {
return Promise.resolve(false);
}
return handleControllingExtension(
PREF_SETTING_TYPE, HOMEPAGE_OVERRIDE_KEY, "extensionControlled.homepage_override2");
return handleControllingExtension(PREF_SETTING_TYPE, HOMEPAGE_OVERRIDE_KEY);
},
/**

Просмотреть файл

@ -932,21 +932,13 @@ var gMainPane = {
// extension is in control or not.
async updateProxySettingsUI() {
let controllingExtension = await getControllingExtension(PREF_SETTING_TYPE, PROXY_KEY);
let fragment = controllingExtension ?
getControllingExtensionFragment(
"extensionControlled.proxyConfig", controllingExtension, this._brandShortName) :
BrowserUtils.getLocalizedFragment(
document,
this._prefsBundle.getString("connectionDesc.label"),
this._brandShortName);
let description = document.getElementById("connectionSettingsDescription");
// Remove the old content from the description.
while (description.firstChild) {
description.firstChild.remove();
if (controllingExtension) {
setControllingExtensionDescription(description, controllingExtension, "proxy.settings");
} else {
setControllingExtensionDescription(description, null, "network-proxy-connection-description");
}
description.appendChild(fragment);
},
async checkBrowserContainers(event) {

Просмотреть файл

@ -73,10 +73,10 @@ function waitForEnableMessage(messageId, doc) {
{ attributeFilter: ["class"], attributes: true });
}
function waitForMessageContent(messageId, content, doc) {
function waitForMessageContent(messageId, l10nId, doc) {
return waitForMessageChange(
getElement(messageId, doc),
target => target.textContent === content,
target => doc.l10n.getAttributes(target).id === l10nId,
{ childList: true });
}
@ -107,9 +107,12 @@ add_task(async function testExtensionControlledHomepage() {
// The homepage has been set by the extension, the user is notified and it isn't editable.
let controlledLabel = controlledContent.querySelector("description");
is(homepagePref(), extensionHomepage, "homepage is set by extension");
// There are two spaces before "set_homepage" because it's " <image /> set_homepage".
is(controlledLabel.textContent, "An extension, set_homepage, is controlling your home page.",
"The user is notified that an extension is controlling the homepage");
Assert.deepEqual(doc.l10n.getAttributes(controlledLabel), {
id: "extension-controlled-homepage-override",
args: {
name: "set_homepage",
}
}, "The user is notified that an extension is controlling the homepage");
is(controlledContent.hidden, false, "The extension controlled row is hidden");
is(homeModeEl.disabled, true, "The homepage input is disabled");
@ -119,8 +122,9 @@ add_task(async function testExtensionControlledHomepage() {
await enableMessageShown;
// The user is notified how to enable the extension.
is(controlledLabel.textContent, "To enable the extension go to Add-ons in the menu.",
"The user is notified of how to enable the extension again");
is(doc.l10n.getAttributes(controlledLabel.querySelector("label")).id,
"extension-controlled-enable",
"The user is notified of how to enable the extension again");
// The user can dismiss the enable instructions.
let hidden = waitForMessageHidden("browserHomePageExtensionContent");
@ -309,9 +313,12 @@ add_task(async function testExtensionControlledNewTab() {
// The new tab page has been set by the extension and the user is notified.
let controlledLabel = controlledContent.querySelector("description");
ok(aboutNewTabService.newTabURL.startsWith("moz-extension:"), "new tab url is set by extension");
// There are two spaces before "set_newtab" because it's " <image /> set_newtab".
is(controlledLabel.textContent, "An extension, set_newtab, is controlling your New Tab page.",
"The user is notified that an extension is controlling the new tab page");
Assert.deepEqual(doc.l10n.getAttributes(controlledLabel), {
id: "extension-controlled-new-tab-url",
args: {
name: "set_newtab",
}
}, "The user is notified that an extension is controlling the new tab page");
is(controlledContent.hidden, false, "The extension controlled row is hidden");
// Disable the extension.
@ -319,8 +326,9 @@ add_task(async function testExtensionControlledNewTab() {
// Verify the user is notified how to enable the extension.
await waitForEnableMessage(controlledContent.id);
is(controlledLabel.textContent, "To enable the extension go to Add-ons in the menu.",
"The user is notified of how to enable the extension again");
is(doc.l10n.getAttributes(controlledLabel.querySelector("label")).id,
"extension-controlled-enable",
"The user is notified of how to enable the extension again");
// Verify the enable message can be dismissed.
let hidden = waitForMessageHidden(controlledContent.id);
@ -388,10 +396,12 @@ add_task(async function testExtensionControlledDefaultSearch() {
let controlledLabel = controlledContent.querySelector("description");
let extensionEngine = Services.search.currentEngine;
ok(initialEngine != extensionEngine, "The default engine has changed.");
// There are two spaces before "set_default_search" because it's " <image /> set_default_search".
is(controlledLabel.textContent,
"An extension, set_default_search, has set your default search engine.",
"The user is notified that an extension is controlling the default search engine");
Assert.deepEqual(doc.l10n.getAttributes(controlledLabel), {
id: "extension-controlled-default-search",
args: {
name: "set_default_search",
}
}, "The user is notified that an extension is controlling the default search engine");
is(controlledContent.hidden, false, "The extension controlled row is shown");
// Set the engine back to the initial one, ensure the message is hidden.
@ -529,9 +539,12 @@ add_task(async function testExtensionControlledTrackingProtection() {
is(controlledButton.hidden, !isControlled, "The disable extension button's visibility is as expected.");
if (isControlled) {
let controlledDesc = controlledLabel.querySelector("description");
// There are two spaces before "set_tp" because it's " <image /> set_tp".
is(controlledDesc.textContent, "An extension, set_tp, is controlling tracking protection.",
"The user is notified that an extension is controlling TP.");
Assert.deepEqual(doc.l10n.getAttributes(controlledDesc), {
id: "extension-controlled-websites-tracking-protection-mode",
args: {
name: "set_tp",
}
}, "The user is notified that an extension is controlling TP.");
}
if (uiType === "new") {
@ -561,8 +574,9 @@ add_task(async function testExtensionControlledTrackingProtection() {
// The user is notified how to enable the extension.
let controlledDesc = controlledLabel.querySelector("description");
is(controlledDesc.textContent, "To enable the extension go to Add-ons in the menu.",
"The user is notified of how to enable the extension again");
is(doc.l10n.getAttributes(controlledDesc.querySelector("label")).id,
"extension-controlled-enable",
"The user is notified of how to enable the extension again");
// The user can dismiss the enable instructions.
let hidden = waitForMessageHidden(labelId);
@ -650,22 +664,21 @@ add_task(async function testExtensionControlledProxyConfig() {
}
function expectedConnectionSettingsMessage(doc, isControlled) {
let brandShortName = doc.getElementById("bundleBrand").getString("brandShortName");
return isControlled ?
`An extension, set_proxy, is controlling how ${brandShortName} connects to the internet.` :
`Configure how ${brandShortName} connects to the internet.`;
"extension-controlled-proxy-config" :
"network-proxy-connection-description";
}
function connectionSettingsMessagePromise(doc, isControlled) {
return waitForMessageContent(
CONNECTION_SETTINGS_DESC_ID,
expectedConnectionSettingsMessage(doc, isControlled)
expectedConnectionSettingsMessage(doc, isControlled),
doc
);
}
function verifyState(doc, isControlled) {
let isPanel = doc.getElementById(CONTROLLED_BUTTON_ID);
let brandShortName = doc.getElementById("bundleBrand").getString("brandShortName");
is(proxyType === proxySvc.PROXYCONFIG_DIRECT, isControlled,
"Proxy pref is set to the expected value.");
@ -679,9 +692,12 @@ add_task(async function testExtensionControlledProxyConfig() {
}
if (isControlled) {
let controlledDesc = controlledSection.querySelector("description");
// There are two spaces before "set_proxy" because it's " <image /> set_proxy".
is(controlledDesc.textContent, `An extension, set_proxy, is controlling how ${brandShortName} connects to the internet.`,
"The user is notified that an extension is controlling proxy settings.");
Assert.deepEqual(doc.l10n.getAttributes(controlledDesc), {
id: "extension-controlled-proxy-config",
args: {
name: "set_proxy",
}
}, "The user is notified that an extension is controlling proxy settings.");
}
function getProxyControls() {
let controlGroup = doc.getElementById("networkProxyType");
@ -712,9 +728,10 @@ add_task(async function testExtensionControlledProxyConfig() {
is(element.disabled, isControlled, `Proxy controls are ${controlState}.`);
}
} else {
is(doc.getElementById(CONNECTION_SETTINGS_DESC_ID).textContent,
expectedConnectionSettingsMessage(doc, isControlled),
"The connection settings description is as expected.");
let elem = doc.getElementById(CONNECTION_SETTINGS_DESC_ID);
is(doc.l10n.getAttributes(elem).id,
expectedConnectionSettingsMessage(doc, isControlled),
"The connection settings description is as expected.");
}
}
@ -728,8 +745,9 @@ add_task(async function testExtensionControlledProxyConfig() {
// The user is notified how to enable the extension.
let controlledDesc = controlledSection.querySelector("description");
is(controlledDesc.textContent, "To enable the extension go to Add-ons in the menu.",
"The user is notified of how to enable the extension again");
is(panelDoc.l10n.getAttributes(controlledDesc.querySelector("label")).id,
"extension-controlled-enable",
"The user is notified of how to enable the extension again");
// The user can dismiss the enable instructions.
let hidden = waitForMessageHidden(sectionId, panelDoc);

Просмотреть файл

@ -76,6 +76,47 @@ should-restart-ok = Restart { -brand-short-name } now
cancel-no-restart-button = Cancel
restart-later = Restart Later
## Extension Control Notifications
##
## These strings are used to inform the user
## about changes made by extensions to browser settings.
##
## <img data-l10n-name="icon"/> is going to be replaced by the extension icon.
##
## Variables:
## $name (String): name of the extension
# This string is shown to notify the user that their home page
# is being controlled by an extension.
extension-controlled-homepage-override = An extension, <img data-l10n-name="icon"/> { $name }, is controlling your home page.
# This string is shown to notify the user that their new tab page
# is being controlled by an extension.
extension-controlled-new-tab-url = An extension, <img data-l10n-name="icon"/> { $name }, is controlling your New Tab page.
# This string is shown to notify the user that the default search engine
# is being controlled by an extension.
extension-controlled-default-search = An extension, <img data-l10n-name="icon"/> { $name }, has set your default search engine.
# This string is shown to notify the user that Container Tabs
# are being enabled by an extension.
extension-controlled-privacy-containers = An extension, <img data-l10n-name="icon"/> { $name }, requires Container Tabs.
# This string is shown to notify the user that their tracking protection preferences
# are being controlled by an extension.
extension-controlled-websites-tracking-protection-mode = An extension, <img data-l10n-name="icon"/> { $name }, is controlling tracking protection.
# This string is shown to notify the user that their proxy configuration preferences
# are being controlled by an extension.
extension-controlled-proxy-config = An extension, <img data-l10n-name="icon"/> { $name }, is controlling how { -brand-short-name } connects to the internet.
# This string is shown after the user disables an extension to notify the user
# how to enable an extension that they disabled.
#
# <img data-l10n-name="addons-icon"/> will be replaced with Add-ons icon
# <img data-l10n-name="menu-icon"/> will be replaced with Menu icon
extension-controlled-enable = To enable the extension go to <img data-l10n-name="addons-icon"/> Add-ons in the <img data-l10n-name="menu-icon"/> menu.
## Preferences UI Search Results
search-results-header = Search Results
@ -362,6 +403,8 @@ browsing-search-on-start-typing =
network-proxy-title = Network Proxy
network-proxy-connection-description = Configure how { -brand-short-name } connects to the internet.
network-proxy-connection-learn-more = Learn More
network-proxy-connection-settings =

Просмотреть файл

@ -168,41 +168,3 @@ spaceAlert.under5GB.okButton.label=OK, Got it
spaceAlert.under5GB.okButton.accesskey=K
# LOCALIZATION NOTE (spaceAlert.under5GB.message): %S = brandShortName
spaceAlert.under5GB.message=%S is running out of disk space. Website contents may not display properly. Visit “Learn More” to optimize your disk usage for better browsing experience.
# LOCALIZATION NOTE (extensionControlled.homepage_override2):
# This string is shown to notify the user that their home page is being controlled by an extension.
extensionControlled.homepage_override2 = An extension, %S, is controlling your home page.
# LOCALIZATION NOTE (extensionControlled.newTabURL2):
# This string is shown to notify the user that their new tab page is being controlled by an extension.
extensionControlled.newTabURL2 = An extension, %S, is controlling your New Tab page.
# LOCALIZATION NOTE (extensionControlled.defaultSearch):
# This string is shown to notify the user that the default search engine is being controlled
# by an extension. %S is the icon and name of the extension.
extensionControlled.defaultSearch = An extension, %S, has set your default search engine.
# LOCALIZATION NOTE (extensionControlled.privacy.containers):
# This string is shown to notify the user that Container Tabs are being enabled by an extension
# %S is the container addon controlling it
extensionControlled.privacy.containers = An extension, %S, requires Container Tabs.
# LOCALIZATION NOTE (extensionControlled.websites.trackingProtectionMode):
# This string is shown to notify the user that their tracking protection preferences are being controlled by an extension.
extensionControlled.websites.trackingProtectionMode = An extension, %S, is controlling tracking protection.
# LOCALIZATION NOTE (extensionControlled.proxyConfig):
# This string is shown to notify the user that their proxy configuration preferences are being controlled by an extension.
# %1$S is the icon and name of the extension.
# %2$S is the brandShortName from brand.properties (for example "Nightly")
extensionControlled.proxyConfig = An extension, %1$S, is controlling how %2$S connects to the internet.
# LOCALIZATION NOTE (extensionControlled.enable):
# %1$S is replaced with the icon for the add-ons menu.
# %2$S is replaced with the icon for the toolbar menu.
# This string is shown to notify the user how to enable an extension that they disabled.
extensionControlled.enable = To enable the extension go to %1$S Add-ons in the %2$S menu.
# LOCALIZATION NOTE (connectionDesc.label):
# %S is the brandShortName from brand.properties (for example "Nightly")
connectionDesc.label = Configure how %S connects to the internet.

Просмотреть файл

@ -0,0 +1,120 @@
# coding=utf8
# Any copyright is dedicated to the Public Domain.
# http://creativecommons.org/publicdomain/zero/1.0/
from __future__ import absolute_import
import fluent.syntax.ast as FTL
from fluent.migrate.helpers import MESSAGE_REFERENCE, EXTERNAL_ARGUMENT
from fluent.migrate import COPY, CONCAT, REPLACE
def migrate(ctx):
"""Bug 1438375 - Migrate Extension Controlled settings in Preferences to Fluent, part {index}."""
ctx.add_transforms(
'browser/browser/preferences/preferences.ftl',
'browser/browser/preferences/preferences.ftl',
[
FTL.Message(
id=FTL.Identifier('extension-controlled-homepage-override'),
value=REPLACE(
'browser/chrome/browser/preferences/preferences.properties',
'extensionControlled.homepage_override2',
{
'%S': CONCAT(
FTL.TextElement('<img data-l10n-name="icon"/> '),
EXTERNAL_ARGUMENT('name')
)
}
)
),
FTL.Message(
id=FTL.Identifier('extension-controlled-new-tab-url'),
value=REPLACE(
'browser/chrome/browser/preferences/preferences.properties',
'extensionControlled.newTabURL2',
{
'%S': CONCAT(
FTL.TextElement('<img data-l10n-name="icon"/> '),
EXTERNAL_ARGUMENT('name')
)
}
)
),
FTL.Message(
id=FTL.Identifier('extension-controlled-default-search'),
value=REPLACE(
'browser/chrome/browser/preferences/preferences.properties',
'extensionControlled.defaultSearch',
{
'%S': CONCAT(
FTL.TextElement('<img data-l10n-name="icon"/> '),
EXTERNAL_ARGUMENT('name')
)
}
)
),
FTL.Message(
id=FTL.Identifier('extension-controlled-privacy-containers'),
value=REPLACE(
'browser/chrome/browser/preferences/preferences.properties',
'extensionControlled.privacy.containers',
{
'%S': CONCAT(
FTL.TextElement('<img data-l10n-name="icon"/> '),
EXTERNAL_ARGUMENT('name')
)
}
)
),
FTL.Message(
id=FTL.Identifier('extension-controlled-websites-tracking-protection-mode'),
value=REPLACE(
'browser/chrome/browser/preferences/preferences.properties',
'extensionControlled.websites.trackingProtectionMode',
{
'%S': CONCAT(
FTL.TextElement('<img data-l10n-name="icon"/> '),
EXTERNAL_ARGUMENT('name')
)
}
)
),
FTL.Message(
id=FTL.Identifier('extension-controlled-proxy-config'),
value=REPLACE(
'browser/chrome/browser/preferences/preferences.properties',
'extensionControlled.proxyConfig',
{
'%1$S': CONCAT(
FTL.TextElement('<img data-l10n-name="icon"/> '),
EXTERNAL_ARGUMENT('name')
),
'%2$S': MESSAGE_REFERENCE('-brand-short-name'),
}
)
),
FTL.Message(
id=FTL.Identifier('extension-controlled-enable'),
value=REPLACE(
'browser/chrome/browser/preferences/preferences.properties',
'extensionControlled.enable',
{
'%1$S': FTL.TextElement('<img data-l10n-name="addons-icon"/>'),
'%2$S': FTL.TextElement('<img data-l10n-name="menu-icon"/>'),
}
)
),
FTL.Message(
id=FTL.Identifier('network-proxy-connection-description'),
value=REPLACE(
'browser/chrome/browser/preferences/preferences.properties',
'connectionDesc.label',
{
'%S': MESSAGE_REFERENCE('-brand-short-name'),
}
)
),
]
)