/* 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 { IMServices } from "resource:///modules/IMServices.sys.mjs"; import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; import { PluralForm } from "resource://gre/modules/PluralForm.sys.mjs"; import { clearTimeout, setTimeout } from "resource://gre/modules/Timer.sys.mjs"; import { ChatIcons } from "resource:///modules/chatIcons.sys.mjs"; // Time in seconds: it is the minimum time of inactivity // needed to show the bundled notification. var kTimeToWaitForMoreMsgs = 3; export var Notifications = { get ellipsis() { let ellipsis = "[\u2026]"; try { ellipsis = Services.prefs.getComplexValue( "intl.ellipsis", Ci.nsIPrefLocalizedString ).data; } catch (e) {} return ellipsis; }, // Holds the first direct message of a bundle while we wait for further // messages from the same sender to arrive. _heldMessage: null, // Number of messages to be bundled in the notification (excluding // _heldMessage). _msgCounter: 0, // Time the last message was received. _lastMessageTime: 0, // Sender of the last message. _lastMessageSender: null, // timeout Id for the set timeout for showing notification. _timeoutId: null, _showMessageNotification(aMessage, aCounter = 0) { // We are about to show the notification, so let's play the notification sound. // We play the sound if the user is away from TB window or even away from chat tab. let win = Services.wm.getMostRecentWindow("mail:3pane"); if ( !Services.focus.activeWindow || win.document.getElementById("tabmail").currentTabInfo.mode.name != "chat" ) { Services.obs.notifyObservers(aMessage, "play-chat-notification-sound"); } // If TB window has focus, there's no need to show the notification.. if (win && win.document.hasFocus()) { this._heldMessage = null; this._msgCounter = 0; return; } let bundle = Services.strings.createBundle( "chrome://messenger/locale/chat.properties" ); let messageText, icon, name; let notificationContent = Services.prefs.getIntPref( "mail.chat.notification_info" ); // 0 - show all the info, // 1 - show only the sender not the message, // 2 - show no details about the message being notified. switch (notificationContent) { case 0: let parser = new DOMParser(); let doc = parser.parseFromString(aMessage.displayMessage, "text/html"); let body = doc.querySelector("body"); let encoder = Cu.createDocumentEncoder("text/plain"); encoder.init(doc, "text/plain", 0); encoder.setNode(body); messageText = encoder.encodeToString().replace(/\s+/g, " "); // Crop the end of the text if needed. if (messageText.length > 50) { messageText = messageText.substr(0, 50); if (aCounter == 0) { messageText = messageText + this.ellipsis; } } // If there are more messages being bundled, add the count string. // ellipsis is a part of bundledMessagePreview so we don't include it here. if (aCounter > 0) { let bundledMessage = bundle.formatStringFromName( "bundledMessagePreview", [messageText] ); messageText = PluralForm.get(aCounter, bundledMessage).replace( "#1", aCounter ); } // Falls through case 1: // Use the buddy icon if available for the icon of the notification. let conv = aMessage.conversation; icon = conv.convIconFilename; if (!icon && !conv.isChat) { icon = conv.buddy?.buddyIconFilename; } // Handle third person messages name = aMessage.alias || aMessage.who; if (messageText && aMessage.action) { messageText = name + " " + messageText; } // Falls through case 2: if (!icon) { icon = ChatIcons.fallbackUserIconURI; } if (!messageText) { let bundle = Services.strings.createBundle( "chrome://messenger/locale/chat.properties" ); messageText = bundle.GetStringFromName("messagePreview"); } } let alert = Cc["@mozilla.org/alert-notification;1"].createInstance( Ci.nsIAlertNotification ); alert.init( "", // name icon, name, // title messageText, true // clickable ); // Show the notification! Cc["@mozilla.org/alerts-service;1"] .getService(Ci.nsIAlertsService) .showAlert(alert, (subject, topic, data) => { if (topic != "alertclickcallback") { return; } // If there is a timeout set, clear it. clearTimeout(this._timeoutId); this._heldMessage = null; this._msgCounter = 0; this._lastMessageTime = 0; this._lastMessageSender = null; // Focus the conversation if the notification is clicked. let uiConv = IMServices.conversations.getUIConversation( aMessage.conversation ); let mainWindow = Services.wm.getMostRecentWindow("mail:3pane"); if (mainWindow) { mainWindow.focus(); mainWindow.showChatTab(); mainWindow.chatHandler.focusConversation(uiConv); } else { Services.appShell.hiddenDOMWindow.openDialog( "chrome://messenger/content/messenger.xhtml", "_blank", "chrome,dialog=no,all", null, { tabType: "chat", tabParams: { convType: "focus", conv: uiConv }, } ); } if (AppConstants.platform == "macosx") { Cc["@mozilla.org/widget/macdocksupport;1"] .getService(Ci.nsIMacDockSupport) .activateApplication(true); } }); this._heldMessage = null; this._msgCounter = 0; }, init() { Services.obs.addObserver(Notifications, "new-otr-verification-request"); Services.obs.addObserver(Notifications, "new-directed-incoming-message"); Services.obs.addObserver(Notifications, "alertclickcallback"); }, _notificationPrefName: "mail.chat.show_desktop_notifications", observe(aSubject, aTopic, aData) { if (!Services.prefs.getBoolPref(this._notificationPrefName)) { return; } switch (aTopic) { case "new-directed-incoming-message": // If this is the first message, we show the notification and // store the sender's name. let sender = aSubject.who || aSubject.alias; if (this._lastMessageSender == null) { this._lastMessageSender = sender; this._lastMessageTime = aSubject.time; this._showMessageNotification(aSubject); } else if ( this._lastMessageSender != sender || aSubject.time > this._lastMessageTime + kTimeToWaitForMoreMsgs ) { // If the sender is not the same as the previous sender or the // time elapsed since the last message is greater than kTimeToWaitForMoreMsgs, // we show the held notification and set timeout for the message just arrived. if (this._heldMessage) { // if the time for the current message is greater than _lastMessageTime by // more than kTimeToWaitForMoreMsgs, this will not happen since the notification will // have already been dispatched. clearTimeout(this._timeoutId); this._showMessageNotification(this._heldMessage, this._msgCounter); } this._lastMessageSender = sender; this._lastMessageTime = aSubject.time; this._showMessageNotification(aSubject); } else if ( this._lastMessageSender == sender && this._lastMessageTime + kTimeToWaitForMoreMsgs >= aSubject.time ) { // If the sender is same as the previous sender and the time elapsed since the // last held message is less than kTimeToWaitForMoreMsgs, we increase the held messages // counter and update the last message's arrival time. this._lastMessageTime = aSubject.time; if (!this._heldMessage) { this._heldMessage = aSubject; } else { this._msgCounter++; } clearTimeout(this._timeoutId); this._timeoutId = setTimeout(() => { this._showMessageNotification(this._heldMessage, this._msgCounter); }, kTimeToWaitForMoreMsgs * 1000); } break; case "new-otr-verification-request": // If the Chat tab is not focused, play the sounds and update the icon // counter, and show the counter in the buddy richlistitem. let win = Services.wm.getMostRecentWindow("mail:3pane"); if ( !Services.focus.activeWindow || win.document.getElementById("tabmail").currentTabInfo.mode.name != "chat" ) { Services.obs.notifyObservers( aSubject, "play-chat-notification-sound" ); } break; } }, };