summaryrefslogtreecommitdiffstats
path: root/browser/components/newtab/lib/InfoBar.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/newtab/lib/InfoBar.jsm')
-rw-r--r--browser/components/newtab/lib/InfoBar.jsm172
1 files changed, 172 insertions, 0 deletions
diff --git a/browser/components/newtab/lib/InfoBar.jsm b/browser/components/newtab/lib/InfoBar.jsm
new file mode 100644
index 0000000000..c75f17f197
--- /dev/null
+++ b/browser/components/newtab/lib/InfoBar.jsm
@@ -0,0 +1,172 @@
+/* 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";
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
+ RemoteL10n: "resource://activity-stream/lib/RemoteL10n.sys.mjs",
+});
+
+class InfoBarNotification {
+ constructor(message, dispatch) {
+ this._dispatch = dispatch;
+ this.dispatchUserAction = this.dispatchUserAction.bind(this);
+ this.buttonCallback = this.buttonCallback.bind(this);
+ this.infobarCallback = this.infobarCallback.bind(this);
+ this.message = message;
+ this.notification = null;
+ }
+
+ /**
+ * Show the infobar notification and send an impression ping
+ *
+ * @param {object} browser Browser reference for the currently selected tab
+ */
+ showNotification(browser) {
+ let { content } = this.message;
+ let { gBrowser } = browser.ownerGlobal;
+ let doc = gBrowser.ownerDocument;
+ let notificationContainer;
+ if (content.type === "global") {
+ notificationContainer = browser.ownerGlobal.gNotificationBox;
+ } else {
+ notificationContainer = gBrowser.getNotificationBox(browser);
+ }
+
+ let priority = content.priority || notificationContainer.PRIORITY_SYSTEM;
+
+ this.notification = notificationContainer.appendNotification(
+ this.message.id,
+ {
+ label: this.formatMessageConfig(doc, content.text),
+ image: content.icon || "chrome://branding/content/icon64.png",
+ priority,
+ eventCallback: this.infobarCallback,
+ },
+ content.buttons.map(b => this.formatButtonConfig(b))
+ );
+
+ this.addImpression();
+ }
+
+ formatMessageConfig(doc, content) {
+ let docFragment = doc.createDocumentFragment();
+ // notificationbox will only `appendChild` for documentFragments
+ docFragment.appendChild(
+ lazy.RemoteL10n.createElement(doc, "span", { content })
+ );
+
+ return docFragment;
+ }
+
+ formatButtonConfig(button) {
+ let btnConfig = { callback: this.buttonCallback, ...button };
+ // notificationbox will set correct data-l10n-id attributes if passed in
+ // using the l10n-id key. Otherwise the `button.label` text is used.
+ if (button.label.string_id) {
+ btnConfig["l10n-id"] = button.label.string_id;
+ }
+
+ return btnConfig;
+ }
+
+ addImpression() {
+ // Record an impression in ASRouter for frequency capping
+ this._dispatch({ type: "IMPRESSION", data: this.message });
+ // Send a user impression telemetry ping
+ this.sendUserEventTelemetry("IMPRESSION");
+ }
+
+ /**
+ * Called when one of the infobar buttons is clicked
+ */
+ buttonCallback(notificationBox, btnDescription, target) {
+ this.dispatchUserAction(
+ btnDescription.action,
+ target.ownerGlobal.gBrowser.selectedBrowser
+ );
+ let isPrimary = target.classList.contains("primary");
+ let eventName = isPrimary
+ ? "CLICK_PRIMARY_BUTTON"
+ : "CLICK_SECONDARY_BUTTON";
+ this.sendUserEventTelemetry(eventName);
+ }
+
+ dispatchUserAction(action, selectedBrowser) {
+ this._dispatch({ type: "USER_ACTION", data: action }, selectedBrowser);
+ }
+
+ /**
+ * Called when interacting with the toolbar (but not through the buttons)
+ */
+ infobarCallback(eventType) {
+ if (eventType === "removed") {
+ this.notification = null;
+ // eslint-disable-next-line no-use-before-define
+ InfoBar._activeInfobar = null;
+ } else if (this.notification) {
+ this.sendUserEventTelemetry("DISMISSED");
+ this.notification = null;
+ // eslint-disable-next-line no-use-before-define
+ InfoBar._activeInfobar = null;
+ }
+ }
+
+ sendUserEventTelemetry(event) {
+ const ping = {
+ message_id: this.message.id,
+ event,
+ };
+ this._dispatch({
+ type: "INFOBAR_TELEMETRY",
+ data: { action: "infobar_user_event", ...ping },
+ });
+ }
+}
+
+const InfoBar = {
+ _activeInfobar: null,
+
+ maybeLoadCustomElement(win) {
+ if (!win.customElements.get("remote-text")) {
+ Services.scriptloader.loadSubScript(
+ "resource://activity-stream/data/custom-elements/paragraph.js",
+ win
+ );
+ }
+ },
+
+ maybeInsertFTL(win) {
+ win.MozXULElement.insertFTLIfNeeded("browser/newtab/asrouter.ftl");
+ win.MozXULElement.insertFTLIfNeeded(
+ "browser/defaultBrowserNotification.ftl"
+ );
+ },
+
+ showInfoBarMessage(browser, message, dispatch) {
+ // Prevent stacking multiple infobars
+ if (this._activeInfobar) {
+ return null;
+ }
+
+ const win = browser?.ownerGlobal;
+
+ if (!win || lazy.PrivateBrowsingUtils.isWindowPrivate(win)) {
+ return null;
+ }
+
+ this.maybeLoadCustomElement(win);
+ this.maybeInsertFTL(win);
+
+ let notification = new InfoBarNotification(message, dispatch);
+ notification.showNotification(browser);
+ this._activeInfobar = true;
+
+ return notification;
+ },
+};
+
+const EXPORTED_SYMBOLS = ["InfoBar"];