Backed out changeset 9c4ec733448e (bug 1937234) Backed out changeset 05d3d5a45230 (bug 1937234) Backed out changeset 27877e1ab900 (bug 1937234) Backed out changeset d56b2c3bfd0e (bug 1937234)
273 lines
8.7 KiB
JavaScript
273 lines
8.7 KiB
JavaScript
/* 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/. */
|
|
|
|
const lazy = {};
|
|
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
EveryWindow: "resource:///modules/EveryWindow.sys.mjs",
|
|
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
|
|
clearTimeout: "resource://gre/modules/Timer.sys.mjs",
|
|
requestIdleCallback: "resource://gre/modules/Timer.sys.mjs",
|
|
setTimeout: "resource://gre/modules/Timer.sys.mjs",
|
|
});
|
|
|
|
let notificationsByWindow = new WeakMap();
|
|
|
|
export class _ToolbarBadgeHub {
|
|
constructor() {
|
|
this.id = "toolbar-badge-hub";
|
|
this.state = {};
|
|
this.removeAllNotifications = this.removeAllNotifications.bind(this);
|
|
this.removeToolbarNotification = this.removeToolbarNotification.bind(this);
|
|
this.addToolbarNotification = this.addToolbarNotification.bind(this);
|
|
this.registerBadgeToAllWindows = this.registerBadgeToAllWindows.bind(this);
|
|
this._sendPing = this._sendPing.bind(this);
|
|
this.sendUserEventTelemetry = this.sendUserEventTelemetry.bind(this);
|
|
|
|
this._handleMessageRequest = null;
|
|
this._addImpression = null;
|
|
this._blockMessageById = null;
|
|
this._sendTelemetry = null;
|
|
this._initialized = false;
|
|
}
|
|
|
|
async init(
|
|
waitForInitialized,
|
|
{
|
|
handleMessageRequest,
|
|
addImpression,
|
|
blockMessageById,
|
|
unblockMessageById,
|
|
sendTelemetry,
|
|
}
|
|
) {
|
|
if (this._initialized) {
|
|
return;
|
|
}
|
|
|
|
this._initialized = true;
|
|
this._handleMessageRequest = handleMessageRequest;
|
|
this._blockMessageById = blockMessageById;
|
|
this._unblockMessageById = unblockMessageById;
|
|
this._addImpression = addImpression;
|
|
this._sendTelemetry = sendTelemetry;
|
|
// Need to wait for ASRouter to initialize before trying to fetch messages
|
|
await waitForInitialized;
|
|
this.messageRequest({
|
|
triggerId: "toolbarBadgeUpdate",
|
|
template: "toolbar_badge",
|
|
});
|
|
}
|
|
|
|
maybeInsertFTL(win) {
|
|
win.MozXULElement.insertFTLIfNeeded("browser/newtab/asrouter.ftl");
|
|
}
|
|
|
|
_clearBadgeTimeout() {
|
|
if (this.state.showBadgeTimeoutId) {
|
|
lazy.clearTimeout(this.state.showBadgeTimeoutId);
|
|
}
|
|
}
|
|
|
|
removeAllNotifications(event) {
|
|
if (event) {
|
|
// ignore right clicks
|
|
if (
|
|
(event.type === "mousedown" || event.type === "click") &&
|
|
event.button !== 0
|
|
) {
|
|
return;
|
|
}
|
|
// ignore keyboard access that is not one of the usual accessor keys
|
|
if (
|
|
event.type === "keypress" &&
|
|
event.key !== " " &&
|
|
event.key !== "Enter"
|
|
) {
|
|
return;
|
|
}
|
|
|
|
event.target.removeEventListener(
|
|
"mousedown",
|
|
this.removeAllNotifications
|
|
);
|
|
event.target.removeEventListener("keypress", this.removeAllNotifications);
|
|
// If we have an event it means the user interacted with the badge
|
|
// we should send telemetry
|
|
if (this.state.notification) {
|
|
this.sendUserEventTelemetry("CLICK", this.state.notification);
|
|
}
|
|
}
|
|
// Will call uninit on every window
|
|
lazy.EveryWindow.unregisterCallback(this.id);
|
|
if (this.state.notification) {
|
|
this._blockMessageById(this.state.notification.id);
|
|
}
|
|
this._clearBadgeTimeout();
|
|
this.state = {};
|
|
}
|
|
|
|
removeToolbarNotification(toolbarButton) {
|
|
// Remove it from the element that displays the badge
|
|
toolbarButton
|
|
.querySelector(".toolbarbutton-badge")
|
|
.classList.remove("feature-callout");
|
|
toolbarButton.removeAttribute("badged");
|
|
// Remove id used for for aria-label badge description
|
|
const notificationDescription = toolbarButton.querySelector(
|
|
"#toolbarbutton-notification-description"
|
|
);
|
|
if (notificationDescription) {
|
|
notificationDescription.remove();
|
|
toolbarButton.removeAttribute("aria-labelledby");
|
|
toolbarButton.removeAttribute("aria-describedby");
|
|
}
|
|
}
|
|
|
|
addToolbarNotification(win, message) {
|
|
const document = win.browser.ownerDocument;
|
|
let toolbarbutton = document.getElementById(message.content.target);
|
|
if (toolbarbutton) {
|
|
const badge = toolbarbutton.querySelector(".toolbarbutton-badge");
|
|
badge.classList.add("feature-callout");
|
|
toolbarbutton.setAttribute("badged", true);
|
|
// If we have additional aria-label information for the notification
|
|
// we add this content to the hidden `toolbarbutton-text` node.
|
|
// We then use `aria-labelledby` to link this description to the button
|
|
// that received the notification badge.
|
|
if (message.content.badgeDescription) {
|
|
// Insert strings as soon as we know we're showing them
|
|
this.maybeInsertFTL(win);
|
|
toolbarbutton.setAttribute(
|
|
"aria-labelledby",
|
|
`toolbarbutton-notification-description ${message.content.target}`
|
|
);
|
|
// Because tooltiptext is different to the label, it gets duplicated as
|
|
// the description. Setting `describedby` to the same value as
|
|
// `labelledby` will be detected by the a11y code and the description
|
|
// will be removed.
|
|
toolbarbutton.setAttribute(
|
|
"aria-describedby",
|
|
`toolbarbutton-notification-description ${message.content.target}`
|
|
);
|
|
const descriptionEl = document.createElement("span");
|
|
descriptionEl.setAttribute(
|
|
"id",
|
|
"toolbarbutton-notification-description"
|
|
);
|
|
descriptionEl.hidden = true;
|
|
document.l10n.setAttributes(
|
|
descriptionEl,
|
|
message.content.badgeDescription.string_id
|
|
);
|
|
toolbarbutton.appendChild(descriptionEl);
|
|
}
|
|
// `mousedown` event required because of the `onmousedown` defined on
|
|
// the button that prevents `click` events from firing
|
|
toolbarbutton.addEventListener("mousedown", this.removeAllNotifications);
|
|
// `keypress` event required for keyboard accessibility
|
|
toolbarbutton.addEventListener("keypress", this.removeAllNotifications);
|
|
this.state = { notification: { id: message.id } };
|
|
|
|
// Impression should be added when the badge becomes visible
|
|
this._addImpression(message);
|
|
// Send a telemetry ping when adding the notification badge
|
|
this.sendUserEventTelemetry("IMPRESSION", message);
|
|
|
|
return toolbarbutton;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
registerBadgeToAllWindows(message) {
|
|
lazy.EveryWindow.registerCallback(
|
|
this.id,
|
|
win => {
|
|
if (notificationsByWindow.has(win)) {
|
|
// nothing to do
|
|
return;
|
|
}
|
|
const el = this.addToolbarNotification(win, message);
|
|
notificationsByWindow.set(win, el);
|
|
},
|
|
win => {
|
|
const el = notificationsByWindow.get(win);
|
|
if (el) {
|
|
this.removeToolbarNotification(el);
|
|
}
|
|
notificationsByWindow.delete(win);
|
|
}
|
|
);
|
|
}
|
|
|
|
registerBadgeNotificationListener(message, options = {}) {
|
|
// We need to clear any existing notifications and only show
|
|
// the one set by devtools
|
|
if (options.force) {
|
|
this.removeAllNotifications();
|
|
// When debugging immediately show the badge
|
|
this.registerBadgeToAllWindows(message);
|
|
return;
|
|
}
|
|
|
|
if (message.content.delay) {
|
|
this.state.showBadgeTimeoutId = lazy.setTimeout(() => {
|
|
lazy.requestIdleCallback(() => this.registerBadgeToAllWindows(message));
|
|
}, message.content.delay);
|
|
} else {
|
|
this.registerBadgeToAllWindows(message);
|
|
}
|
|
}
|
|
|
|
async messageRequest({ triggerId, template }) {
|
|
const telemetryObject = { triggerId };
|
|
TelemetryStopwatch.start("MS_MESSAGE_REQUEST_TIME_MS", telemetryObject);
|
|
const message = await this._handleMessageRequest({
|
|
triggerId,
|
|
template,
|
|
});
|
|
TelemetryStopwatch.finish("MS_MESSAGE_REQUEST_TIME_MS", telemetryObject);
|
|
if (message) {
|
|
this.registerBadgeNotificationListener(message);
|
|
}
|
|
}
|
|
|
|
_sendPing(ping) {
|
|
this._sendTelemetry({
|
|
type: "TOOLBAR_BADGE_TELEMETRY",
|
|
data: { action: "badge_user_event", ...ping },
|
|
});
|
|
}
|
|
|
|
sendUserEventTelemetry(event, message) {
|
|
const win = Services.wm.getMostRecentWindow("navigator:browser");
|
|
// Only send pings for non private browsing windows
|
|
if (
|
|
win &&
|
|
!lazy.PrivateBrowsingUtils.isBrowserPrivate(
|
|
win.ownerGlobal.gBrowser.selectedBrowser
|
|
)
|
|
) {
|
|
this._sendPing({
|
|
message_id: message.id,
|
|
event,
|
|
});
|
|
}
|
|
}
|
|
|
|
uninit() {
|
|
this._clearBadgeTimeout();
|
|
this.state = {};
|
|
this._initialized = false;
|
|
notificationsByWindow = new WeakMap();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ToolbarBadgeHub - singleton instance of _ToolbarBadgeHub that can initiate
|
|
* message requests and render messages.
|
|
*/
|
|
export const ToolbarBadgeHub = new _ToolbarBadgeHub();
|