/* 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 { MozLitElement } from "chrome://global/content/lit-utils.mjs";
import { html } from "chrome://global/content/vendor/lit.all.mjs";
const { ERRORS, WARNINGS, MAX_ITEM_COUNT } = ChromeUtils.importESModule(
"moz-src:///browser/components/sharing/ContentSharingUtils.sys.mjs"
);
const { XPCOMUtils } = ChromeUtils.importESModule(
"resource://gre/modules/XPCOMUtils.sys.mjs"
);
const MAX_PREVIEW_LINKS = 3;
const WINDOW_BREAKPOINT_SIZE = 830;
const lazy = {};
XPCOMUtils.defineLazyPreferenceGetter(
lazy,
"CONTENT_SHARING_SERVER_URL",
"browser.contentsharing.server.url",
""
);
XPCOMUtils.defineLazyPreferenceGetter(
lazy,
"CONTENT_SHARING_DEBUG",
"browser.contentsharing.debug",
false
);
const DEFAULT_COPY_ICON = "chrome://global/skin/icons/edit-copy.svg";
const DEFAULT_COPY_L10N_ID = "content-sharing-modal-copy-link";
const COPIED_COPY_ICON = "chrome://global/skin/icons/check.svg";
const COPIED_COPY_L10N_ID = "content-sharing-modal-link-copied";
const ACCEPTABLE_USE_POLICY_URL =
"https://www.mozilla.org/about/legal/acceptable-use/";
// eslint-disable-next-line import/no-unassigned-import
import "chrome://global/content/elements/moz-card.mjs";
// eslint-disable-next-line import/no-unassigned-import
import "chrome://global/content/elements/moz-button.mjs";
// eslint-disable-next-line import/no-unassigned-import
import "chrome://global/content/elements/moz-message-bar.mjs";
/**
* Element used for content sharing modal content
*/
export class ContentSharingModal extends MozLitElement {
static properties = {
shareResult: { type: Object },
size: { type: String, reflect: true },
loading: { type: Boolean },
};
static queries = {
title: ".share-title",
linkCount: ".share-count",
links: { all: ".link" },
moreLinks: ".more-links",
previewCard: ".preview > moz-card",
copyButton: "#copy-button",
viewPageButton: "#view-page",
signInButton: "#sign-in",
loadingButton: "#loading-button",
tooManyLinks: ".too-many-links",
errorMessageBar: "moz-message-bar",
};
async getUpdateComplete() {
await super.getUpdateComplete();
await this.previewCard?.updateComplete;
}
async connectedCallback() {
super.connectedCallback();
let { shareResult, loadingPromise, size } = window.arguments?.[0] ?? {};
this.shareResult = shareResult;
if (loadingPromise) {
this.loading = true;
this.shareResult = (await loadingPromise).shareResult;
this.loading = false;
}
// The modal does not resize with the window so when the modal is opened
// from ContentSharingUtils the current windows width is passed. If the
// window width is less than 830, the modal will open in the "small" state.
this.size = size < WINDOW_BREAKPOINT_SIZE ? "small" : null;
}
close() {
// Borrowing a hack from unexpectedScriptLoad.js, which we use to ensure
// opened tabs are foregrounded. To be fixed in bug 2040823.
window.top.document.documentElement.removeAttribute("window-modal-open");
window.close();
}
linkTemplate(link) {
if (link.type === "bookmarks") {
return html`
`;
}
return html``;
}
linksTemplate() {
if (!this.shareResult.share?.links) {
return null;
}
if (this.shareResult.share.links.length > MAX_PREVIEW_LINKS) {
return html`${this.shareResult.share.links
.slice(0, 3)
.map(link => this.linkTemplate(link))}
${this.linksInfoTemplate()}`;
}
return this.shareResult.share.links.map(link => this.linkTemplate(link));
}
handleViewPageClick() {
Glean.collectionShare.ctaClicked.record({
button: "view-page",
signed_in: true,
});
this.close();
this.documentGlobal.frameElement.documentGlobal.openWebLinkIn(
this.shareResult.url,
"tab"
);
}
handleCopyClick() {
window.navigator.clipboard.writeText(this.shareResult.url);
Glean.collectionShare.ctaClicked.record({
button: "copy-button",
signed_in: true,
});
this.copyButton.setAttribute("iconsrc", COPIED_COPY_ICON);
this.copyButton.setAttribute("data-l10n-id", COPIED_COPY_L10N_ID);
new Promise(r => setTimeout(r, 1000)).then(() => {
this.copyButton.setAttribute("iconsrc", DEFAULT_COPY_ICON);
this.copyButton.setAttribute("data-l10n-id", DEFAULT_COPY_L10N_ID);
});
}
handleSignInClick() {
Glean.collectionShare.ctaClicked.record({
button: "sign-in",
signed_in: false,
});
const accountSlug = lazy.CONTENT_SHARING_DEBUG
? "/accounts/dummy/login/"
: "/accounts/fxa/login/";
const signInURL = lazy.CONTENT_SHARING_SERVER_URL + accountSlug;
this.close();
this.documentGlobal.frameElement.documentGlobal.openWebLinkIn(
signInURL,
"tab"
);
}
acceptableUseClick(event) {
if (!HTMLAnchorElement.isInstance(event.target)) {
return;
}
event.preventDefault();
this.close();
// Need to do this explicity because just clicking isn't opening a tab
this.documentGlobal.frameElement.documentGlobal.openWebLinkIn(
event.target.href,
"tab"
);
}
loadingTemplate() {
return html``;
}
descriptionActionTemplate() {
if (this.loading) {
return this.loadingTemplate();
}
// If we got the url or
// if there were no errors or
// if were not signed in and got an unauthorized error
if (
this.shareResult.url ||
!this.shareResult.error ||
(!this.shareResult.isSignedIn &&
this.shareResult.error === ERRORS.UNAUTHORIZED)
) {
return html`${this.buttonsTemplate()}`;
}
// Links that fail schema validation are dropped when the share is built, so
// this error only occurs when no shareable links remain.
if (this.shareResult.error === ERRORS.INVALID_SCHEMA) {
return html``;
}
if (this.shareResult.error) {
return html``;
}
// I don't think we can be in this state?
return null;
}
buttonsTemplate() {
// Note: Avoid changing existing button IDs, because they are submitted
// with button click telemetry. If new buttons or added, or IDs change,
// be sure to update the list of buttons in metrics.yaml.
if (this.shareResult.isSignedIn) {
return html``;
}
return html``;
}
policyTemplate() {
if (this.shareResult.isSignedIn) {
return null;
}
return html`