diff options
Diffstat (limited to '')
-rw-r--r-- | browser/components/newtab/lib/InfoBar.jsm | 172 |
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"]; |