diff --git a/browser/components/preferences/content/moreFromMozilla.ftl b/browser/components/preferences/content/moreFromMozilla.ftl
index ddd3f86094f0..70cdbbf968b1 100644
--- a/browser/components/preferences/content/moreFromMozilla.ftl
+++ b/browser/components/preferences/content/moreFromMozilla.ftl
@@ -14,13 +14,16 @@ category-more-from-mozilla =
pane-more-from-mozilla-subtitle = Check out other Mozilla products that work to support a healthy internet.
firefox-mobile-title = { -brand-product-name } Mobile
-firefox-mobile-description = Get the mobile browser that puts your privacy first.
+firefox-mobile-description = The mobile browser that puts your privacy first.
+more-mozilla-advanced-firefox-mobile-description = From blocking trackers to putting the brakes on autoplay annoyances, Firefox mobile browsers work overtime to make sure you’re getting the good Internet.
mozilla-vpn-title = { -mozilla-vpn-brand-name }
mozilla-vpn-description = Discover an added layer of anonymous browsing and protection.
+more-mozilla-advanced-mozilla-vpn-description = Mozilla VPN adds another layer of anonymous browsing and protection. And, unlike some other VPNs, it’s secure and doesn’t track your activity.
mozilla-rally-title = { -vendor-short-name } Rally
mozilla-rally-description = Put your data to work for a better Internet for everyone.
+more-mozilla-advanced-mozilla-rally-description = Donate your data to research studies working to create a safer, more open Internet that helps people, not Big Tech.
button-firefox-mobile =
.label = Get { -brand-product-name } Mobile
@@ -30,3 +33,5 @@ button-mozilla-vpn =
button-mozilla-rally =
.label = Join Rally
+
+more-mozilla-learn-more-link = Learn more
diff --git a/browser/components/preferences/moreFromMozilla.inc.xhtml b/browser/components/preferences/moreFromMozilla.inc.xhtml
index aaa048c7e2d3..811f7bbb591f 100644
--- a/browser/components/preferences/moreFromMozilla.inc.xhtml
+++ b/browser/components/preferences/moreFromMozilla.inc.xhtml
@@ -13,33 +13,40 @@
data-category="paneMoreFromMozilla">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/browser/components/preferences/moreFromMozilla.js b/browser/components/preferences/moreFromMozilla.js
index 7158d11156d8..a7a6d16fd9c8 100644
--- a/browser/components/preferences/moreFromMozilla.js
+++ b/browser/components/preferences/moreFromMozilla.js
@@ -6,23 +6,135 @@
var gMoreFromMozillaPane = {
initialized: false,
+ option: null,
- openURL(url) {
+ getURL(url, option) {
const URL_PARAMS = {
- utm_source: "about-preferences",
- utm_campaign: "morefrommozilla-na",
- utm_medium: "firefox-release-browser",
- entrypoint_experiment: "morefrommozilla",
- entrypoint_variation: "a",
+ utm_source: "about-prefs",
+ utm_campaign: "morefrommozilla",
+ utm_medium: "firefox-desktop",
+ };
+ // UTM content param used in analytics to record
+ // UI template used to open URL
+ const utm_content = {
+ simple: "fxvt-113-a-na",
+ advanced: "fxvt-113-b-na",
};
let pageUrl = new URL(url);
for (let [key, val] of Object.entries(URL_PARAMS)) {
pageUrl.searchParams.append(key, val);
}
+ if (option) {
+ pageUrl.searchParams.set("utm_content", utm_content[option]);
+ }
+ return pageUrl.toString();
+ },
- let mainWindow = window.windowRoot.ownerGlobal;
- mainWindow.openTrustedLinkIn(pageUrl.toString(), "tab");
+ renderProducts() {
+ let products = [
+ {
+ id: "firefox-mobile",
+ title_string_id: "firefox-mobile-title",
+ description_string_id: "firefox-mobile-description",
+ qrcode: null,
+ button: {
+ id: "fxMobile",
+ type: "link",
+ label_string_id: "more-mozilla-learn-more-link",
+ actionURL: "https://www.mozilla.org/firefox/browsers/mobile/",
+ },
+ },
+ {
+ id: "mozilla-vpn",
+ title_string_id: "mozilla-vpn-title",
+ description_string_id: "mozilla-vpn-description",
+ button: {
+ id: "mozillaVPN",
+ label_string_id: "button-mozilla-vpn",
+ actionURL: "https://www.mozilla.org/products/vpn/",
+ },
+ },
+ {
+ id: "mozilla-rally",
+ title_string_id: "mozilla-rally-title",
+ description_string_id: "mozilla-rally-description",
+ button: {
+ id: "mozillaRally",
+ label_string_id: "button-mozilla-rally",
+ actionURL: "https://rally.mozilla.org/",
+ },
+ },
+ ];
+ this._productsContainer = document.getElementById(
+ "moreFromMozillaCategory"
+ );
+ let frag = document.createDocumentFragment();
+ this._template = document.getElementById(this.option || "simple");
+
+ // Exit when template is not found
+ if (!this._template) {
+ return;
+ }
+
+ for (let product of products) {
+ let template = this._template.content.cloneNode(true);
+ let title = template.querySelector(".product-title");
+ let desc = template.querySelector(".description");
+
+ title.setAttribute("data-l10n-id", product.title_string_id);
+ title.id = product.id;
+
+ // Handle advanced template display of product details
+ if (this.option === "advanced") {
+ template.querySelector(".product-img").id = `${product.id}-image`;
+ desc.setAttribute(
+ "data-l10n-id",
+ `more-mozilla-advanced-${product.description_string_id}`
+ );
+ } else {
+ desc.setAttribute("data-l10n-id", product.description_string_id);
+ }
+
+ let isLink = product.button.type === "link";
+ let actionElement = template.querySelector(
+ isLink ? ".text-link" : ".small-button"
+ );
+
+ if (actionElement) {
+ actionElement.hidden = false;
+ actionElement.id = `${this.option}-${product.button.id}`;
+ actionElement.setAttribute(
+ "data-l10n-id",
+ product.button.label_string_id
+ );
+
+ if (isLink) {
+ actionElement.setAttribute(
+ "href",
+ this.getURL(product.button.actionURL, this.option)
+ );
+ actionElement.setAttribute("target", "_blank");
+ } else {
+ actionElement.addEventListener("click", function() {
+ let mainWindow = window.windowRoot.ownerGlobal;
+ mainWindow.openTrustedLinkIn(
+ gMoreFromMozillaPane.getURL(
+ product.button.actionURL,
+ gMoreFromMozillaPane.option
+ ),
+ "tab"
+ );
+ });
+ }
+ }
+
+ if (product.qrcode) {
+ template.querySelector(".qrcode-section").hidden = false;
+ }
+ frag.appendChild(template);
+ }
+ this._productsContainer.appendChild(frag);
},
async init() {
@@ -34,20 +146,6 @@ var gMoreFromMozillaPane = {
.getElementById("moreFromMozillaCategory")
.removeAttribute("data-hidden-from-search");
- document.getElementById("mozillaVPN").addEventListener("click", function() {
- gMoreFromMozillaPane.openURL("https://www.mozilla.org/products/vpn/");
- });
-
- document.getElementById("fxMobile").addEventListener("click", function() {
- gMoreFromMozillaPane.openURL(
- "https://www.mozilla.org/en-US/firefox/browsers/mobile/"
- );
- });
-
- document
- .getElementById("mozillaRally")
- .addEventListener("click", function() {
- gMoreFromMozillaPane.openURL("https://rally.mozilla.org/");
- });
+ this.renderProducts();
},
};
diff --git a/browser/components/preferences/preferences.js b/browser/components/preferences/preferences.js
index c5958c8f475e..cdbd6e135dae 100644
--- a/browser/components/preferences/preferences.js
+++ b/browser/components/preferences/preferences.js
@@ -205,6 +205,9 @@ function init_all() {
NimbusFeatures.moreFromMozilla.recordExposureEvent({ once: true });
if (NimbusFeatures.moreFromMozilla.getVariable("enabled")) {
document.getElementById("category-more-from-mozilla").hidden = false;
+ gMoreFromMozillaPane.option = NimbusFeatures.moreFromMozilla.getVariable(
+ "template"
+ );
register_module("paneMoreFromMozilla", gMoreFromMozillaPane);
}
// The Sync category needs to be the last of the "real" categories
diff --git a/browser/components/preferences/tests/browser_moreFromMozilla.js b/browser/components/preferences/tests/browser_moreFromMozilla.js
index 44e61c810528..840a2423bc29 100644
--- a/browser/components/preferences/tests/browser_moreFromMozilla.js
+++ b/browser/components/preferences/tests/browser_moreFromMozilla.js
@@ -88,3 +88,68 @@ add_task(async function test_aboutpreferences_event_telemetry() {
TelemetryTestUtils.assertNumberOfEvents(2);
BrowserTestUtils.removeTab(gBrowser.selectedTab);
});
+
+add_task(async function test_aboutpreferences_simple_template() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.preferences.moreFromMozilla", true],
+ ["browser.preferences.moreFromMozilla.template", "simple"],
+ ],
+ });
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+
+ let doc = gBrowser.contentDocument;
+ let moreFromMozillaCategory = doc.getElementById(
+ "category-more-from-mozilla"
+ );
+
+ let clickedPromise = ContentTaskUtils.waitForEvent(
+ moreFromMozillaCategory,
+ "click"
+ );
+ moreFromMozillaCategory.click();
+ await clickedPromise;
+
+ let productCards = doc.querySelectorAll("vbox.simple");
+ Assert.ok(productCards, "The product cards from simple template found");
+ Assert.equal(productCards.length, 3, "3 product cards displayed");
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function test_aboutpreferences_advanced_template() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.preferences.moreFromMozilla", true],
+ ["browser.preferences.moreFromMozilla.template", "advanced"],
+ ],
+ });
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
+ leaveOpen: true,
+ });
+
+ let doc = gBrowser.contentDocument;
+ let moreFromMozillaCategory = doc.getElementById(
+ "category-more-from-mozilla"
+ );
+
+ let clickedPromise = ContentTaskUtils.waitForEvent(
+ moreFromMozillaCategory,
+ "click"
+ );
+ moreFromMozillaCategory.click();
+ await clickedPromise;
+
+ let productCards = doc.querySelectorAll("vbox.advanced");
+ Assert.ok(productCards, "The product cards from advanced template found");
+ Assert.equal(productCards.length, 3, "3 product cards displayed");
+ Assert.deepEqual(
+ Array.from(productCards).map(
+ node => node.querySelector(".product-img")?.id
+ ),
+ ["firefox-mobile-image", "mozilla-vpn-image", "mozilla-rally-image"],
+ "Advanced template product marketing images"
+ );
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/modules/BrowserUsageTelemetry.jsm b/browser/modules/BrowserUsageTelemetry.jsm
index c991bb64004f..7ae575f9d6a8 100644
--- a/browser/modules/BrowserUsageTelemetry.jsm
+++ b/browser/modules/BrowserUsageTelemetry.jsm
@@ -814,7 +814,10 @@ let BrowserUsageTelemetry = {
// Find the actual element we're interested in.
let node = sourceEvent.target;
- while (!UI_TARGET_ELEMENTS.includes(node.localName)) {
+ while (
+ !UI_TARGET_ELEMENTS.includes(node.localName) &&
+ !node.classList?.contains("wants-telemetry")
+ ) {
node = node.parentNode;
if (!node) {
// A click on a space or label or something we're not interested in.
diff --git a/browser/themes/shared/jar.inc.mn b/browser/themes/shared/jar.inc.mn
index de9537ff9b39..d954659995d7 100644
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -85,6 +85,9 @@
skin/classic/browser/preferences/face-smile.svg (../shared/preferences/face-smile.svg)
skin/classic/browser/preferences/fxaPairDevice.css (../shared/preferences/fxaPairDevice.css)
skin/classic/browser/preferences/ios-menu.svg (../shared/preferences/ios-menu.svg)
+ skin/classic/browser/preferences/img-mobile.svg (../shared/preferences/img-mobile.svg)
+ skin/classic/browser/preferences/img-rally.png (../shared/preferences/img-rally.png)
+ skin/classic/browser/preferences/img-vpn.svg (../shared/preferences/img-vpn.svg)
skin/classic/browser/preferences/mozilla-logo.svg (../shared/preferences/mozilla-logo.svg)
skin/classic/browser/preferences/no-search-bar.svg (../shared/preferences/no-search-bar.svg)
skin/classic/browser/preferences/privacy.css (../shared/preferences/privacy.css)
diff --git a/browser/themes/shared/preferences/img-mobile.svg b/browser/themes/shared/preferences/img-mobile.svg
new file mode 100644
index 000000000000..0d9204cc7ffd
--- /dev/null
+++ b/browser/themes/shared/preferences/img-mobile.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/browser/themes/shared/preferences/img-rally.png b/browser/themes/shared/preferences/img-rally.png
new file mode 100644
index 000000000000..abe7341dcea2
Binary files /dev/null and b/browser/themes/shared/preferences/img-rally.png differ
diff --git a/browser/themes/shared/preferences/img-vpn.svg b/browser/themes/shared/preferences/img-vpn.svg
new file mode 100644
index 000000000000..c85577acb85c
--- /dev/null
+++ b/browser/themes/shared/preferences/img-vpn.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/browser/themes/shared/preferences/preferences.inc.css b/browser/themes/shared/preferences/preferences.inc.css
index e5859f0fe47e..7a12af533edf 100644
--- a/browser/themes/shared/preferences/preferences.inc.css
+++ b/browser/themes/shared/preferences/preferences.inc.css
@@ -1186,15 +1186,28 @@ richlistitem .text-link:hover {
}
#moreFromMozillaCategory .product-title {
+ margin: 4px 0;
background-repeat: no-repeat;
background-size: contain;
padding-inline-start: 30px;
- margin: 4px 0;
-moz-context-properties: fill;
fill: currentColor;
}
-#moreFromMozillaCategory .product-title:-moz-locale-dir(rtl) {
+#moreFromMozillaCategory .advanced .product-title {
+ background-size: initial;
+ padding-inline-start: 0;
+ padding-top: 33px;
+}
+
+#moreFromMozillaCategory .product-img {
+ background-repeat: no-repeat;
+ background-size: 150px;
+ padding-inline-start: 174px;
+}
+
+#moreFromMozillaCategory .product-title:-moz-locale-dir(rtl),
+#moreFromMozillaCategory .product-img:-moz-locale-dir(rtl) {
background-position-x: right;
}
@@ -1204,13 +1217,46 @@ richlistitem .text-link:hover {
margin: 4px 0;
}
-#moreFromMozillaCategory button {
+#moreFromMozillaCategory .advanced .description {
+ padding-inline-start: 0;
+}
+
+#moreFromMozillaCategory .small-button {
margin-block: 16px;
margin-inline: 30px 0;
}
+#moreFromMozillaCategory .advanced .small-button {
+ margin: 24px 0;
+}
+
+#moreFromMozillaCategory vbox.advanced {
+ box-shadow: 0 2px 6px rgba(58, 57, 68, 0.2);
+ border-radius: 4px;
+ margin-block: 20px 10px;
+ margin-inline: 2px 0;
+ padding: 16px;
+}
+
+@media (prefers-color-scheme: dark) {
+ #moreFromMozillaCategory vbox.advanced {
+ background-color: rgb(66, 65, 77);
+ }
+}
+
+#moreFromMozillaCategory .advanced .product-info {
+ min-height: 200px;
+}
+
+#moreFromMozillaCategory .simple .text-link {
+ display: block;
+ margin-block: 4px 0;
+ margin-inline-start: 4px;
+}
+
#firefox-mobile {
- background-image: url("chrome://branding/content/about-logo.svg");
+ background-image: url("chrome://devtools/skin/images/browsers/mobile.svg");
+ padding-top: 5px;
}
#mozilla-vpn {
@@ -1220,3 +1266,15 @@ richlistitem .text-link:hover {
#mozilla-rally {
background-image: url("chrome://browser/skin/preferences/rally-logo.svg");
}
+
+#firefox-mobile-image {
+ background-image: url("chrome://browser/skin/preferences/img-mobile.svg");
+}
+
+#mozilla-vpn-image {
+ background-image: url("chrome://browser/skin/preferences/img-vpn.svg");
+}
+
+#mozilla-rally-image {
+ background-image: url("chrome://browser/skin/preferences/img-rally.png");
+}
diff --git a/toolkit/components/nimbus/FeatureManifest.yaml b/toolkit/components/nimbus/FeatureManifest.yaml
index 29892b3c4dad..bdb4cb02698b 100644
--- a/toolkit/components/nimbus/FeatureManifest.yaml
+++ b/toolkit/components/nimbus/FeatureManifest.yaml
@@ -112,6 +112,10 @@ moreFromMozilla:
type: boolean
fallbackPref: browser.preferences.moreFromMozilla
description: Should users see the new more from Mozilla section.
+ template:
+ type: string
+ fallbackPref: browser.preferences.moreFromMozilla.template
+ description: UI template used to display Mozilla products. Possible values simple, advanced. Default is simple.
abouthomecache:
description: "The startup about:home cache."
hasExposure: false