1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 const { AppConstants
} = ChromeUtils
.importESModule(
6 "resource://gre/modules/AppConstants.sys.mjs"
9 // Copied from nsILookAndFeel.h, see comments on eMetric_AlertNotificationOrigin
10 const NS_ALERT_HORIZONTAL
= 1;
11 const NS_ALERT_LEFT
= 2;
12 const NS_ALERT_TOP
= 4;
14 const WINDOW_MARGIN
= AppConstants
.platform
== "win" ? 0 : 10;
15 const BODY_TEXT_LIMIT
= 200;
16 const WINDOW_SHADOW_SPREAD
= AppConstants
.platform
== "win" ? 10 : 0;
18 var gOrigin
= 0; // Default value: alert from bottom right.
19 var gReplacedWindow
= null;
20 var gAlertListener
= null;
21 var gAlertTextClickable
= false;
22 var gAlertCookie
= "";
23 var gIsActive
= false;
24 var gIsReplaced
= false;
25 var gRequireInteraction
= false;
27 function prefillAlertInfo() {
28 // unwrap all the args....
29 // arguments[0] --> the image src url
30 // arguments[1] --> the alert title
31 // arguments[2] --> the alert text
32 // arguments[3] --> is the text clickable?
33 // arguments[4] --> the alert cookie to be passed back to the listener
34 // arguments[5] --> the alert origin reported by the look and feel
35 // arguments[6] --> bidi
36 // arguments[7] --> lang
37 // arguments[8] --> requires interaction
38 // arguments[9] --> replaced alert window (nsIDOMWindow)
39 // arguments[10] --> an optional callback listener (nsIObserver)
40 // arguments[11] -> the nsIURI.hostPort of the origin, optional
41 // arguments[12] -> the alert icon URL, optional
43 switch (window
.arguments
.length
) {
46 if (window
.arguments
[12]) {
47 let alertBox
= document
.getElementById("alertBox");
48 alertBox
.setAttribute("hasIcon", true);
50 let icon
= document
.getElementById("alertIcon");
51 icon
.src
= window
.arguments
[12];
56 if (window
.arguments
[11]) {
57 let alertBox
= document
.getElementById("alertBox");
58 alertBox
.setAttribute("hasOrigin", true);
60 let hostPort
= window
.arguments
[11];
61 const ALERT_BUNDLE
= Services
.strings
.createBundle(
62 "chrome://alerts/locale/alert.properties"
64 const BRAND_BUNDLE
= Services
.strings
.createBundle(
65 "chrome://branding/locale/brand.properties"
67 const BRAND_NAME
= BRAND_BUNDLE
.GetStringFromName("brandShortName");
68 let label
= document
.getElementById("alertSourceLabel");
71 ALERT_BUNDLE
.formatStringFromName("source.label", [hostPort
])
73 let doNotDisturbMenuItem
= document
.getElementById(
74 "doNotDisturbMenuItem"
76 doNotDisturbMenuItem
.setAttribute(
78 ALERT_BUNDLE
.formatStringFromName("pauseNotifications.label", [
82 let disableForOrigin
= document
.getElementById(
83 "disableForOriginMenuItem"
85 disableForOrigin
.setAttribute(
87 ALERT_BUNDLE
.formatStringFromName(
88 "webActions.disableForOrigin.label",
92 let openSettings
= document
.getElementById("openSettingsMenuItem");
93 openSettings
.setAttribute(
95 ALERT_BUNDLE
.GetStringFromName("webActions.settings.label")
101 gAlertListener
= window
.arguments
[10];
104 gReplacedWindow
= window
.arguments
[9];
107 gRequireInteraction
= window
.arguments
[8];
110 if (window
.arguments
[7]) {
112 .getElementById("alertTitleLabel")
113 .setAttribute("lang", window
.arguments
[7]);
115 .getElementById("alertTextLabel")
116 .setAttribute("lang", window
.arguments
[7]);
120 if (window
.arguments
[6]) {
121 document
.getElementById("alertNotification").style
.direction
=
126 gOrigin
= window
.arguments
[5];
129 gAlertCookie
= window
.arguments
[4];
132 gAlertTextClickable
= window
.arguments
[3];
133 if (gAlertTextClickable
) {
135 .getElementById("alertNotification")
136 .setAttribute("clickable", true);
138 .getElementById("alertTextLabel")
139 .setAttribute("clickable", true);
143 if (window
.arguments
[2]) {
144 document
.getElementById("alertBox").setAttribute("hasBodyText", true);
145 let bodyText
= window
.arguments
[2];
146 let bodyTextLabel
= document
.getElementById("alertTextLabel");
148 if (bodyText
.length
> BODY_TEXT_LIMIT
) {
149 bodyTextLabel
.setAttribute("tooltiptext", bodyText
);
151 let ellipsis
= "\u2026";
153 ellipsis
= Services
.prefs
.getComplexValue(
155 Ci
.nsIPrefLocalizedString
159 // Copied from nsContextMenu.js' formatSearchContextItem().
160 // If the JS character after our truncation point is a trail surrogate,
161 // include it in the truncated string to avoid splitting a surrogate pair.
162 let truncLength
= BODY_TEXT_LIMIT
;
163 let truncChar
= bodyText
[BODY_TEXT_LIMIT
].charCodeAt(0);
164 if (truncChar
>= 0xdc00 && truncChar
<= 0xdfff) {
168 bodyText
= bodyText
.substring(0, truncLength
) + ellipsis
;
170 bodyTextLabel
.textContent
= bodyText
;
175 .getElementById("alertTitleLabel")
176 .setAttribute("value", window
.arguments
[1]);
179 if (window
.arguments
[0]) {
180 document
.getElementById("alertBox").setAttribute("hasImage", true);
182 .getElementById("alertImage")
183 .setAttribute("src", window
.arguments
[0]);
191 function onAlertLoad() {
192 const ALERT_DURATION_IMMEDIATE
= 20000;
193 let alertTextBox
= document
.getElementById("alertTextBox");
194 let alertImageBox
= document
.getElementById("alertImageBox");
195 alertImageBox
.style
.minHeight
= alertTextBox
.scrollHeight
+ "px";
197 window
.sizeToContent();
199 if (gReplacedWindow
&& !gReplacedWindow
.closed
) {
200 moveWindowToReplace(gReplacedWindow
);
201 gReplacedWindow
.gIsReplaced
= true;
202 gReplacedWindow
.close();
207 window
.addEventListener("XULAlertClose", function () {
211 // If the require interaction flag is set, prevent auto-closing the notification.
212 if (!gRequireInteraction
) {
213 if (window
.matchMedia("(prefers-reduced-motion: reduce)").matches
) {
214 setTimeout(function () {
216 }, ALERT_DURATION_IMMEDIATE
);
218 let alertBox
= document
.getElementById("alertBox");
219 alertBox
.addEventListener("animationend", function hideAlert(event
) {
221 event
.animationName
== "alert-animation" ||
222 event
.animationName
== "alert-clicked-animation" ||
223 event
.animationName
== "alert-closing-animation"
225 alertBox
.removeEventListener("animationend", hideAlert
);
229 alertBox
.setAttribute("animate", true);
233 let alertSettings
= document
.getElementById("alertSettings");
234 alertSettings
.addEventListener("focus", onAlertSettingsFocus
);
235 alertSettings
.addEventListener("click", onAlertSettingsClick
);
239 let ev
= new CustomEvent("AlertActive", { bubbles
: true, cancelable
: true });
240 document
.documentElement
.dispatchEvent(ev
);
242 if (gAlertListener
) {
243 gAlertListener
.observe(null, "alertshow", gAlertCookie
);
247 function moveWindowToReplace(aReplacedAlert
) {
248 let heightDelta
= window
.outerHeight
- aReplacedAlert
.outerHeight
;
250 // Move windows that come after the replaced alert if the height is different.
251 if (heightDelta
!= 0) {
252 for (let alertWindow
of Services
.wm
.getEnumerator("alert:alert")) {
253 if (!alertWindow
.gIsActive
) {
256 // boolean to determine if the alert window is after the replaced alert.
258 gOrigin
& NS_ALERT_TOP
259 ? alertWindow
.screenY
> aReplacedAlert
.screenY
260 : aReplacedAlert
.screenY
> alertWindow
.screenY
;
262 // The new Y position of the window.
264 gOrigin
& NS_ALERT_TOP
265 ? alertWindow
.screenY
+ heightDelta
266 : alertWindow
.screenY
- heightDelta
;
267 alertWindow
.moveTo(alertWindow
.screenX
, adjustedY
);
273 gOrigin
& NS_ALERT_TOP
274 ? aReplacedAlert
.screenY
275 : aReplacedAlert
.screenY
- heightDelta
;
276 window
.moveTo(aReplacedAlert
.screenX
, adjustedY
);
279 function moveWindowToEnd() {
280 // Determine position
282 gOrigin
& NS_ALERT_LEFT
284 : screen
.availLeft
+ screen
.availWidth
- window
.outerWidth
;
286 gOrigin
& NS_ALERT_TOP
288 : screen
.availTop
+ screen
.availHeight
- window
.outerHeight
;
290 // Position the window at the end of all alerts.
291 for (let alertWindow
of Services
.wm
.getEnumerator("alert:alert")) {
292 if (alertWindow
!= window
&& alertWindow
.gIsActive
) {
293 if (gOrigin
& NS_ALERT_TOP
) {
296 alertWindow
.screenY
+ alertWindow
.outerHeight
- WINDOW_SHADOW_SPREAD
301 alertWindow
.screenY
- window
.outerHeight
+ WINDOW_SHADOW_SPREAD
307 // Offset the alert by WINDOW_MARGIN pixels from the edge of the screen
308 y
+= gOrigin
& NS_ALERT_TOP
? WINDOW_MARGIN
: -WINDOW_MARGIN
;
309 x
+= gOrigin
& NS_ALERT_LEFT
? WINDOW_MARGIN
: -WINDOW_MARGIN
;
314 function onAlertBeforeUnload() {
316 // Move other alert windows to fill the gap left by closing alert.
317 let heightDelta
= window
.outerHeight
+ WINDOW_MARGIN
- WINDOW_SHADOW_SPREAD
;
318 for (let alertWindow
of Services
.wm
.getEnumerator("alert:alert")) {
319 if (alertWindow
!= window
&& alertWindow
.gIsActive
) {
320 if (gOrigin
& NS_ALERT_TOP
) {
321 if (alertWindow
.screenY
> window
.screenY
) {
324 alertWindow
.screenY
- heightDelta
327 } else if (window
.screenY
> alertWindow
.screenY
) {
330 alertWindow
.screenY
+ heightDelta
337 if (gAlertListener
) {
338 gAlertListener
.observe(null, "alertfinished", gAlertCookie
);
342 function onAlertClick() {
343 if (gAlertListener
&& gAlertTextClickable
) {
344 gAlertListener
.observe(null, "alertclickcallback", gAlertCookie
);
347 let alertBox
= document
.getElementById("alertBox");
348 if (alertBox
.getAttribute("animate") == "true") {
349 // Closed when the animation ends.
350 alertBox
.setAttribute("clicked", "true");
356 function doNotDisturb() {
357 const alertService
= Cc
["@mozilla.org/alerts-service;1"]
358 .getService(Ci
.nsIAlertsService
)
359 .QueryInterface(Ci
.nsIAlertsDoNotDisturb
);
360 alertService
.manualDoNotDisturb
= true;
364 function disableForOrigin() {
365 gAlertListener
.observe(null, "alertdisablecallback", gAlertCookie
);
369 function onAlertSettingsFocus(event
) {
370 event
.target
.removeAttribute("focusedViaMouse");
373 function onAlertSettingsClick(event
) {
374 // XXXjaws Hack used to remove the focus-ring only
375 // from mouse interaction, but focus-ring drawing
376 // should only be enabled when interacting via keyboard.
377 event
.target
.setAttribute("focusedViaMouse", true);
378 event
.stopPropagation();
381 function openSettings() {
382 gAlertListener
.observe(null, "alertsettingscallback", gAlertCookie
);
386 function onAlertClose() {
387 let alertBox
= document
.getElementById("alertBox");
388 if (alertBox
.getAttribute("animate") == "true") {
389 // Closed when the animation ends.
390 alertBox
.setAttribute("closing", "true");