Bug 1770447 - Create moz-support-link component. r=hjones,eemeli

This adds the <moz-support-link> custom element as well as stories and
tests for this reusable component. The purpose of this component is to
replace individual implementations of the "Learn more" links present
in Firefox, specifically in about:preferences and about:addons.

See Bug 1801927 for an instance of using this component to refactor
the "Learn more" links in about:preferences#general.

Differential Revision: https://phabricator.services.mozilla.com/D164131
This commit is contained in:
Tim Giles
2022-12-16 14:54:46 +00:00
parent 96b0203368
commit 2e50bccf7e
8 changed files with 263 additions and 0 deletions

View File

@@ -297,6 +297,10 @@ var whitelist = [
// References to esm generated from jsm programmatically
{ file: "resource://gre/modules/LangPackMatcher.sys.mjs" },
// FIXME: Bug 1770447 - The moz-support-link component isn't in use yet.
// This entry will be removed as part of Bug 1804695 or Bug 1804695.
{ file: "chrome://global/content/elements/moz-support-link.mjs" },
];
if (AppConstants.NIGHTLY_BUILD && AppConstants.platform != "win") {

View File

@@ -0,0 +1,6 @@
# 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/.
storybook-amo-test = Learn more about add-ons
storybook-fluent-test = Learn more

View File

@@ -0,0 +1,5 @@
# 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/.
moz-support-link-text = Learn more

View File

@@ -95,6 +95,7 @@ toolkit.jar:
content/global/elements/moz-button-group.css (widgets/moz-button-group/moz-button-group.css)
content/global/elements/moz-button-group.mjs (widgets/moz-button-group/moz-button-group.mjs)
content/global/elements/moz-input-box.js (widgets/moz-input-box.js)
content/global/elements/moz-support-link.mjs (widgets/moz-support-link/moz-support-link.mjs)
content/global/elements/named-deck.js (widgets/named-deck.js)
content/global/elements/notificationbox.js (widgets/notificationbox.js)
content/global/elements/panel.js (widgets/panel.js)

View File

@@ -8,6 +8,8 @@ support-files =
window_label_checkbox.xhtml
window_menubar.xhtml
seek_with_sound.ogg
prefs =
app.support.baseURL='https://support.mozilla.org/'
[test_contextmenu_nested.xhtml]
skip-if = os == 'linux' # Bug 1116215
@@ -24,6 +26,7 @@ run-if = os == "mac" && os_version != "10.15" # Mac only feature, requires > 10.
[test_menubar.xhtml]
skip-if = os == 'mac'
[test_moz_button_group.html]
[test_moz_support_link.html]
[test_popupanchor.xhtml]
skip-if = os == 'linux' || (verify && (os == 'win')) # Bug 1335894 perma-fail on linux 16.04
[test_popupreflows.xhtml]

View File

@@ -0,0 +1,83 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>MozSupportLink tests</title>
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
<!-- TODO: Bug 1798404 - in-content/common.css can be removed once we have a better
solution for token variables for the new widgets -->
<link rel="stylesheet" href="chrome://global/skin/in-content/common.css">
<script type="module" src="chrome://global/content/elements/moz-support-link.mjs"></script>
</head>
<body>
<p id="display"></p>
<div id="content">
<a
id="testElement"
is="moz-support-link"
data-l10n-id="test"
support-page="support-test"
>testElement</a>
<a
id="testElement2"
is="moz-support-link"
data-l10n-id="test2"
support-page="support-test"
utm-content="utmcontent-test"
>testElement2</a>
</div>
<pre id="test"></pre>
<script>
const { Assert } = ChromeUtils.import("resource://testing-common/Assert.jsm");
add_task(async function test_component_declaration() {
let mozSupportLink = customElements.get("moz-support-link");
let supportUrl = mozSupportLink.SUPPORT_URL;
// Ensure all the semantics of the primary link are present
let supportLink = document.getElementById("testElement");
is(supportLink.tagName, "A", "tagName should be 'A'");
is(supportLink.getAttribute("is"), "moz-support-link", "Generated anchor should be a 'moz-support-link'");
is(supportLink.getAttribute("data-l10n-id"), "test", "data-l10n-id should be 'test'");
is(supportLink.getAttribute("support-page"), "support-test", "support-page should be 'support-test");
is(supportLink.target, "_blank", "support link should open a new window");
is(supportLink.href, `${supportUrl}support-test`, "href should be generated SUPPORT_URL plus support-test");
// Ensure AMO support link has the correct values
let supportLinkAMO = document.getElementById("testElement2");
is(supportLinkAMO.getAttribute("data-l10n-id"), "test2", "data-l10n-id should be 'test2'");
is(supportLinkAMO.getAttribute("support-page"), "support-test", "support-page should be 'support-test");
is(supportLinkAMO.getAttribute("utm-content"), "utmcontent-test", "utm-content should be 'utmcontent-test");
is(supportLinkAMO.target, "_blank", "support link should open a new window");
let expectedHref = `${supportUrl}support-test?utm_source=firefox-browser&utm_medium=firefox-browser&utm_content=utmcontent-test`;
is(supportLinkAMO.href, expectedHref, "href should be generated SUPPORT_URL");
});
add_task(async function test_creating_component() {
// Ensure created support link behaves as expected
let mozSupportLink = customElements.get("moz-support-link");
let supportUrl = mozSupportLink.SUPPORT_URL;
let content = document.getElementById("content");
let utmSupportLink = document.createElement("a", {is: "moz-support-link"});
utmSupportLink.id = "testElement3";
utmSupportLink.innerText = "testElement3";
document.l10n.setAttributes(utmSupportLink, "testElement3");
content.appendChild(utmSupportLink);
is(utmSupportLink.target, "_blank", "support link should open a new window");
is(utmSupportLink.href, supportUrl, "href should be the base SUPPORT_URL");
is(utmSupportLink.getAttribute("data-l10n-id"), "testElement3", "data-l10n-id should be 'testElement3'");
// Set href via "support-page" after creating the element
utmSupportLink.setAttribute("support-page", "created-page");
is(utmSupportLink.href, `${supportUrl}created-page`, "href should be updated after setting the 'support-page' attribute");
// Set href via "utm-content"
utmSupportLink.href = "";
utmSupportLink.setAttribute("utm-content", "created-content");
is(utmSupportLink.href, `${supportUrl}created-page?utm_source=firefox-browser&utm_medium=firefox-browser&utm_content=created-content`, "href should be updated after setting the 'utm-content' attribute");
});
</script>
</body>
</html>

View File

@@ -0,0 +1,92 @@
/* 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/. */
MozXULElement.insertFTLIfNeeded("browser/components/mozSupportLink.ftl");
export default class MozSupportLink extends HTMLAnchorElement {
static SUPPORT_URL = "https://www.mozilla.org/";
static get observedAttributes() {
return ["support-page", "utm-content", "data-l10n-id"];
}
/**
* Handles setting up the SUPPORT_URL preference getter.
* Without this, the tests for this component may not behave
* as expected.
* @private
* @memberof MozSupportLink
*/
#register() {
if (!window.IS_STORYBOOK) {
let { XPCOMUtils } = window.XPCOMUtils
? window
: ChromeUtils.importESModule(
"resource://gre/modules/XPCOMUtils.sys.mjs"
);
XPCOMUtils.defineLazyPreferenceGetter(
MozSupportLink,
"SUPPORT_URL",
"app.support.baseURL",
"",
null,
val => Services.urlFormatter.formatURL(val)
);
}
}
connectedCallback() {
this.#register();
this.#setHref();
this.setAttribute("target", "_blank");
if (!this.getAttribute("data-l10n-id")) {
document.l10n.setAttributes(this, "moz-support-link-text");
document.l10n.translateFragment(this);
}
}
attributeChangedCallback(name, oldVal, newVal) {
if (name === "support-page" || name === "utm-content") {
this.#setHref();
}
}
#setHref() {
let supportPage = this.getAttribute("support-page") ?? "";
let base = MozSupportLink.SUPPORT_URL + supportPage;
this.href = this.hasAttribute("utm-content")
? formatUTMParams(this.getAttribute("utm-content"), base)
: base;
}
}
customElements.define("moz-support-link", MozSupportLink, { extends: "a" });
/**
* Adds UTM parameters to a given URL, if it is an AMO URL.
*
* @param {string} contentAttribute
* Identifies the part of the UI with which the link is associated.
* @param {string} url
* @returns {string}
* The url with UTM parameters if it is an AMO URL.
* Otherwise the url in unmodified form.
*/
export function formatUTMParams(contentAttribute, url) {
if (!contentAttribute) {
return url;
}
let parsedUrl = new URL(url);
let domain = `.${parsedUrl.hostname}`;
if (
!domain.endsWith(".mozilla.org") &&
// For testing: addons-dev.allizom.org and addons.allizom.org
!domain.endsWith(".allizom.org")
) {
return url;
}
parsedUrl.searchParams.set("utm_source", "firefox-browser");
parsedUrl.searchParams.set("utm_medium", "firefox-browser");
parsedUrl.searchParams.set("utm_content", contentAttribute);
return parsedUrl.href;
}

View File

@@ -0,0 +1,69 @@
/* 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/. */
import { html, ifDefined } from "../vendor/lit.all.mjs";
// eslint-disable-next-line import/no-unassigned-import
import "./moz-support-link.mjs";
MozXULElement.insertFTLIfNeeded(
"locales-preview/moz-support-link-storybook.ftl"
);
MozXULElement.insertFTLIfNeeded("browser/components/mozSupportLink.ftl");
const fluentStrings = [
"storybook-amo-test",
"storybook-fluent-test",
"moz-support-link-text",
];
export default {
title: "Design System/Experiments/MozSupportLink",
argTypes: {
dataL10nId: {
type: "string",
options: [fluentStrings[0], fluentStrings[1], fluentStrings[2]],
control: { type: "select" },
description: "Fluent ID used to generate the text content.",
},
supportPage: {
type: "string",
description:
"Short-hand string from SUMO to the specific support page. **Note:** changing this will not create a valid URL since we don't have access to the generated support link used in Firefox",
},
utmContent: {
type: "string",
description:
"UTM parameter for a URL, if it is an AMO URL. **Note:** changing this will not create a valid URL since we don't have access to the generated support link used in Firefox",
},
onClick: { action: "clicked" },
},
};
const Template = ({ dataL10nId, supportPage, utmContent }) => html`
<a
is="moz-support-link"
data-l10n-id=${ifDefined(dataL10nId)}
support-page=${ifDefined(supportPage)}
utm-content=${ifDefined(utmContent)}
>
</a>
`;
export const withAMOUrl = Template.bind({});
withAMOUrl.args = {
dataL10nId: fluentStrings[0],
supportPage: "addons",
utmContent: "promoted-addon-badge",
};
export const Primary = Template.bind({});
Primary.args = {
supportPage: "preferences",
};
export const withFluentId = Template.bind({});
withFluentId.args = {
dataL10nId: fluentStrings[1],
supportPage: "preferences",
};