123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263 |
- /* 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/. */
- "use strict";
- ChromeUtils.defineModuleGetter(this, "DOMLocalization",
- "resource://gre/modules/DOMLocalization.jsm");
- ChromeUtils.defineModuleGetter(this, "FxAccounts",
- "resource://gre/modules/FxAccounts.jsm");
- ChromeUtils.defineModuleGetter(this, "Services",
- "resource://gre/modules/Services.jsm");
- ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
- "resource://gre/modules/PrivateBrowsingUtils.jsm");
- class _BookmarkPanelHub {
- constructor() {
- this._id = "BookmarkPanelHub";
- this._trigger = {id: "bookmark-panel"};
- this._handleMessageRequest = null;
- this._addImpression = null;
- this._dispatch = null;
- this._initialized = false;
- this._response = null;
- this._l10n = null;
- this.messageRequest = this.messageRequest.bind(this);
- this.toggleRecommendation = this.toggleRecommendation.bind(this);
- this.sendUserEventTelemetry = this.sendUserEventTelemetry.bind(this);
- this.collapseMessage = this.collapseMessage.bind(this);
- }
- /**
- * @param {function} handleMessageRequest
- * @param {function} addImpression
- * @param {function} dispatch - Used for sending user telemetry information
- */
- init(handleMessageRequest, addImpression, dispatch) {
- this._handleMessageRequest = handleMessageRequest;
- this._addImpression = addImpression;
- this._dispatch = dispatch;
- this._l10n = new DOMLocalization();
- this._initialized = true;
- }
- uninit() {
- this._l10n = null;
- this._initialized = false;
- this._handleMessageRequest = null;
- this._addImpression = null;
- this._dispatch = null;
- this._response = null;
- }
- /**
- * Checks if a similar cached requests exists before forwarding the request
- * to ASRouter. Caches only 1 request, unique identifier is `request.url`.
- * Caching ensures we don't duplicate requests and telemetry pings.
- * Return value is important for the caller to know if a message will be
- * shown.
- *
- * @returns {obj|null} response object or null if no messages matched
- */
- async messageRequest(target, win) {
- if (!this._initialized) {
- return false;
- }
- if (this._response && this._response.win === win && this._response.url === target.url && this._response.content) {
- this.showMessage(this._response.content, target, win);
- return true;
- }
- // If we didn't match on a previously cached request then make sure
- // the container is empty
- this._removeContainer(target);
- const response = await this._handleMessageRequest(this._trigger);
- return this.onResponse(response, target, win);
- }
- /**
- * If the response contains a message render it and send an impression.
- * Otherwise we remove the message from the container.
- */
- onResponse(response, target, win) {
- this._response = {
- ...response,
- collapsed: false,
- target,
- win,
- url: target.url,
- };
- if (response && response.content) {
- // Only insert localization files if we need to show a message
- win.MozXULElement.insertFTLIfNeeded("browser/newtab/asrouter.ftl");
- win.MozXULElement.insertFTLIfNeeded("browser/branding/sync-brand.ftl");
- this.showMessage(response.content, target, win);
- this.sendImpression();
- this.sendUserEventTelemetry("IMPRESSION", win);
- } else {
- this.hideMessage(target);
- }
- target.infoButton.disabled = !response;
- return !!response;
- }
- showMessage(message, target, win) {
- if (this._response && this._response.collapsed) {
- this.toggleRecommendation(false);
- return;
- }
- const createElement = elem => target.document.createElementNS("http://www.w3.org/1999/xhtml", elem);
- if (!target.container.querySelector("#cfrMessageContainer")) {
- const recommendation = createElement("div");
- recommendation.setAttribute("id", "cfrMessageContainer");
- recommendation.addEventListener("click", async e => {
- target.hidePopup();
- const url = await FxAccounts.config.promiseEmailFirstURI("bookmark");
- win.ownerGlobal.openLinkIn(url, "tabshifted", {
- private: false,
- triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({}),
- csp: null,
- });
- this.sendUserEventTelemetry("CLICK", win);
- });
- recommendation.style.color = message.color;
- recommendation.style.background = `-moz-linear-gradient(-45deg, ${message.background_color_1} 0%, ${message.background_color_2} 70%)`;
- const close = createElement("button");
- close.setAttribute("id", "cfrClose");
- close.setAttribute("aria-label", "close");
- close.style.color = message.color;
- close.addEventListener("click", e => {
- this.sendUserEventTelemetry("DISMISS", win);
- this.collapseMessage();
- target.close(e);
- });
- const title = createElement("h1");
- title.setAttribute("id", "editBookmarkPanelRecommendationTitle");
- const content = createElement("p");
- content.setAttribute("id", "editBookmarkPanelRecommendationContent");
- const cta = createElement("button");
- cta.setAttribute("id", "editBookmarkPanelRecommendationCta");
- // If `string_id` is present it means we are relying on fluent for translations
- if (message.text.string_id) {
- this._l10n.setAttributes(close, message.close_button.tooltiptext.string_id);
- this._l10n.setAttributes(title, message.title.string_id);
- this._l10n.setAttributes(content, message.text.string_id);
- this._l10n.setAttributes(cta, message.cta.string_id);
- } else {
- close.setAttribute("title", message.close_button.tooltiptext);
- title.textContent = message.title;
- content.textContent = message.text;
- cta.textContent = message.cta;
- }
- recommendation.appendChild(close);
- recommendation.appendChild(title);
- recommendation.appendChild(content);
- recommendation.appendChild(cta);
- target.container.appendChild(recommendation);
- }
- this.toggleRecommendation(true);
- }
- toggleRecommendation(visible) {
- if (!this._response) {
- return;
- }
- const {target} = this._response;
- if (visible === undefined) {
- // When called from the info button of the bookmark panel
- target.infoButton.checked = !target.infoButton.checked;
- } else {
- target.infoButton.checked = visible;
- }
- if (target.infoButton.checked) {
- // If it was ever collapsed we need to cancel the state
- this._response.collapsed = false;
- target.container.removeAttribute("disabled");
- } else {
- target.container.setAttribute("disabled", "disabled");
- }
- }
- collapseMessage() {
- this._response.collapsed = true;
- this.toggleRecommendation(false);
- }
- _removeContainer(target) {
- if (target || (this._response && this._response.target)) {
- const container = (target || this._response.target).container.querySelector("#cfrMessageContainer");
- if (container) {
- container.remove();
- }
- }
- }
- hideMessage(target) {
- this._removeContainer(target);
- this.toggleRecommendation(false);
- this._response = null;
- }
- _forceShowMessage(target, message) {
- const doc = target.browser.ownerGlobal.gBrowser.ownerDocument;
- const win = target.browser.ownerGlobal.window;
- const panelTarget = {
- container: doc.getElementById("editBookmarkPanelRecommendation"),
- infoButton: doc.getElementById("editBookmarkPanelInfoButton"),
- document: doc,
- close: e => {
- e.stopPropagation();
- this.toggleRecommendation(false);
- },
- };
- // Remove any existing message
- this.hideMessage(panelTarget);
- // Reset the reference to the panel elements
- this._response = {target: panelTarget};
- // Required if we want to preview messages that include fluent strings
- win.MozXULElement.insertFTLIfNeeded("browser/newtab/asrouter.ftl");
- win.MozXULElement.insertFTLIfNeeded("browser/branding/sync-brand.ftl");
- this.showMessage(message.content, panelTarget, win);
- }
- sendImpression() {
- this._addImpression(this._response);
- }
- sendUserEventTelemetry(event, win) {
- // Only send pings for non private browsing windows
- if (!PrivateBrowsingUtils.isBrowserPrivate(win.ownerGlobal.gBrowser.selectedBrowser)) {
- this._sendTelemetry({message_id: this._response.id, bucket_id: this._response.id, event});
- }
- }
- _sendTelemetry(ping) {
- this._dispatch({
- type: "DOORHANGER_TELEMETRY",
- data: {action: "cfr_user_event", source: "CFR", ...ping},
- });
- }
- }
- this._BookmarkPanelHub = _BookmarkPanelHub;
- /**
- * BookmarkPanelHub - singleton instance of _BookmarkPanelHub that can initiate
- * message requests and render messages.
- */
- this.BookmarkPanelHub = new _BookmarkPanelHub();
- const EXPORTED_SYMBOLS = ["BookmarkPanelHub", "_BookmarkPanelHub"];
|