diff options
Diffstat (limited to 'components/alerts/content/alert.js')
-rw-r--r-- | components/alerts/content/alert.js | 366 |
1 files changed, 366 insertions, 0 deletions
diff --git a/components/alerts/content/alert.js b/components/alerts/content/alert.js new file mode 100644 index 000000000..b2ebedbc0 --- /dev/null +++ b/components/alerts/content/alert.js @@ -0,0 +1,366 @@ +/* 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/. */ + +var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +Cu.import("resource://gre/modules/Services.jsm"); + +/* + * This indicates from which corner of the screen alerts slide in, + * and from which direction (horizontal/vertical). + * 0, the default, represents bottom right, sliding vertically. + * Use any bitwise combination of the following constants: + * NS_ALERT_HORIZONTAL (1), NS_ALERT_LEFT (2), NS_ALERT_TOP (4). + * + * 6 4 + * +-----------+ + * 7| |5 + * | | + * 3| |1 + * +-----------+ + * 2 0 + */ +const NS_ALERT_HORIZONTAL = 1; +const NS_ALERT_LEFT = 2; +const NS_ALERT_TOP = 4; + +#ifdef XP_WIN +const WINDOW_MARGIN = 0; +const WINDOW_SHADOW_SPREAD = 10; +#else +const WINDOW_MARGIN = 10; +const WINDOW_SHADOW_SPREAD = 0; +#endif + +const BODY_TEXT_LIMIT = 200; + +var gOrigin = 0; // Default value: alert from bottom right. +var gReplacedWindow = null; +var gAlertListener = null; +var gAlertTextClickable = false; +var gAlertCookie = ""; +var gIsReplaced = false; +var gRequireInteraction = false; + +function prefillAlertInfo() { + // unwrap all the args.... + // arguments[0] --> the image src url + // arguments[1] --> the alert title + // arguments[2] --> the alert text + // arguments[3] --> is the text clickable? + // arguments[4] --> the alert cookie to be passed back to the listener + // arguments[5] --> the alert origin reported by the look and feel + // arguments[6] --> bidi + // arguments[7] --> lang + // arguments[8] --> requires interaction + // arguments[9] --> replaced alert window (nsIDOMWindow) + // arguments[10] --> an optional callback listener (nsIObserver) + // arguments[11] -> the nsIURI.hostPort of the origin, optional + // arguments[12] -> the alert icon URL, optional + + document.getElementById('alertTime').setAttribute('value', (new Date).getTime()); + + switch (window.arguments.length) { + default: + case 13: { + if (window.arguments[12]) { + let alertBox = document.getElementById("alertBox"); + alertBox.setAttribute("hasIcon", true); + + let icon = document.getElementById("alertIcon"); + icon.src = window.arguments[12]; + } + } + case 12: { + if (window.arguments[11]) { + let alertBox = document.getElementById("alertBox"); + alertBox.setAttribute("hasOrigin", true); + + let hostPort = window.arguments[11]; + const ALERT_BUNDLE = Services.strings.createBundle( + "chrome://alerts/locale/alert.properties"); + const BRAND_BUNDLE = Services.strings.createBundle( + "chrome://branding/locale/brand.properties"); + const BRAND_NAME = BRAND_BUNDLE.GetStringFromName("brandShortName"); + let label = document.getElementById("alertSourceLabel"); + label.setAttribute("value", + ALERT_BUNDLE.formatStringFromName("source.label", + [hostPort], + 1)); + let doNotDisturbMenuItem = document.getElementById("doNotDisturbMenuItem"); + doNotDisturbMenuItem.setAttribute("label", + ALERT_BUNDLE.formatStringFromName("doNotDisturb.label", + [BRAND_NAME], + 1)); + let disableForOrigin = document.getElementById("disableForOriginMenuItem"); + disableForOrigin.setAttribute("label", + ALERT_BUNDLE.formatStringFromName("webActions.disableForOrigin.label", + [hostPort], + 1)); + let openSettings = document.getElementById("openSettingsMenuItem"); + openSettings.setAttribute("label", + ALERT_BUNDLE.GetStringFromName("webActions.settings.label")); + } + } + case 11: + gAlertListener = window.arguments[10]; + case 10: + gReplacedWindow = window.arguments[9]; + case 9: + gRequireInteraction = window.arguments[8]; + case 8: + if (window.arguments[7]) { + document.getElementById("alertTitleLabel").setAttribute("lang", window.arguments[7]); + document.getElementById("alertTextLabel").setAttribute("lang", window.arguments[7]); + } + case 7: + if (window.arguments[6]) { + document.getElementById("alertNotification").style.direction = window.arguments[6]; + } + case 6: + gOrigin = window.arguments[5]; + case 5: + gAlertCookie = window.arguments[4]; + case 4: + gAlertTextClickable = window.arguments[3]; + if (gAlertTextClickable) { + document.getElementById("alertNotification").setAttribute("clickable", true); + document.getElementById("alertTextLabel").setAttribute("clickable", true); + } + case 3: + if (window.arguments[2]) { + document.getElementById("alertBox").setAttribute("hasBodyText", true); + let bodyText = window.arguments[2]; + let bodyTextLabel = document.getElementById("alertTextLabel"); + + if (bodyText.length > BODY_TEXT_LIMIT) { + bodyTextLabel.setAttribute("tooltiptext", bodyText); + + let ellipsis = "\u2026"; + try { + ellipsis = Services.prefs.getComplexValue("intl.ellipsis", + Ci.nsIPrefLocalizedString).data; + } catch (e) { } + + // Copied from nsContextMenu.js' formatSearchContextItem(). + // If the JS character after our truncation point is a trail surrogate, + // include it in the truncated string to avoid splitting a surrogate pair. + let truncLength = BODY_TEXT_LIMIT; + let truncChar = bodyText[BODY_TEXT_LIMIT].charCodeAt(0); + if (truncChar >= 0xDC00 && truncChar <= 0xDFFF) { + truncLength++; + } + + bodyText = bodyText.substring(0, truncLength) + + ellipsis; + } + bodyTextLabel.textContent = bodyText; + } + case 2: + document.getElementById("alertTitleLabel").setAttribute("value", window.arguments[1]); + case 1: + if (window.arguments[0]) { + document.getElementById("alertBox").setAttribute("hasImage", true); + document.getElementById("alertImage").setAttribute("src", window.arguments[0]); + } + case 0: + break; + } +} + +function onAlertLoad() { + const ALERT_DURATION_IMMEDIATE_MIN = 4000; + const ALERT_DURATION_IMMEDIATE_MAX = 60000; + let alertDurationImmediate = Services.prefs.getIntPref("alerts.durationImmediate", ALERT_DURATION_IMMEDIATE_MIN); + alertDurationImmediate = alertDurationImmediate >= ALERT_DURATION_IMMEDIATE_MIN + && alertDurationImmediate <= ALERT_DURATION_IMMEDIATE_MAX + ? alertDurationImmediate : ALERT_DURATION_IMMEDIATE_MIN; + let alertTextBox = document.getElementById("alertTextBox"); + let alertImageBox = document.getElementById("alertImageBox"); + alertImageBox.style.minHeight = alertTextBox.scrollHeight + "px"; + + sizeToContent(); + + if (gReplacedWindow && !gReplacedWindow.closed) { + moveWindowToReplace(gReplacedWindow); + gReplacedWindow.gIsReplaced = true; + gReplacedWindow.close(); + } else { + moveWindowToEnd(); + } + + window.addEventListener("XULAlertClose", function() { window.close(); }); + + // If the require interaction flag is set, prevent auto-closing the notification. + if (!gRequireInteraction) { + if (Services.prefs.getBoolPref("alerts.disableSlidingEffect")) { + setTimeout(function() { window.close(); }, alertDurationImmediate); + } else { + let alertBox = document.getElementById("alertBox"); + alertBox.addEventListener("animationend", function hideAlert(event) { + if (event.animationName == "alert-animation" || + event.animationName == "alert-clicked-animation" || + event.animationName == "alert-closing-animation") { + alertBox.removeEventListener("animationend", hideAlert, false); + window.close(); + } + }, false); + alertBox.style.animationDuration = Math.round(alertDurationImmediate / 1000).toString() + "s"; + alertBox.setAttribute("animate", true); + } + } + + let alertSettings = document.getElementById("alertSettings"); + alertSettings.addEventListener("focus", onAlertSettingsFocus); + alertSettings.addEventListener("click", onAlertSettingsClick); + + let ev = new CustomEvent("AlertActive", {bubbles: true, cancelable: true}); + document.documentElement.dispatchEvent(ev); + + if (gAlertListener) { + gAlertListener.observe(null, "alertshow", gAlertCookie); + } +} + +function moveWindowToReplace(aReplacedAlert) { + let heightDelta = window.outerHeight - aReplacedAlert.outerHeight; + + // Move windows that come after the replaced alert if the height is different. + if (heightDelta != 0) { + let windows = Services.wm.getEnumerator("alert:alert"); + while (windows.hasMoreElements()) { + let alertWindow = windows.getNext(); + // boolean to determine if the alert window is after the replaced alert. + let alertIsAfter = gOrigin & NS_ALERT_TOP ? + alertWindow.screenY > aReplacedAlert.screenY : + aReplacedAlert.screenY > alertWindow.screenY; + if (alertIsAfter) { + // The new Y position of the window. + let adjustedY = gOrigin & NS_ALERT_TOP ? + alertWindow.screenY + heightDelta : + alertWindow.screenY - heightDelta; + alertWindow.moveTo(alertWindow.screenX, adjustedY); + } + } + } + + let adjustedY = gOrigin & NS_ALERT_TOP ? aReplacedAlert.screenY : + aReplacedAlert.screenY - heightDelta; + window.moveTo(aReplacedAlert.screenX, adjustedY); +} + +function moveWindowToEnd() { + // Determine position + let x = gOrigin & NS_ALERT_LEFT ? screen.availLeft : + screen.availLeft + screen.availWidth - window.outerWidth; + let y = gOrigin & NS_ALERT_TOP ? screen.availTop : + screen.availTop + screen.availHeight - window.outerHeight; + + // Position the window at the end of all alerts. + let windows = Services.wm.getEnumerator("alert:alert"); + while (windows.hasMoreElements()) { + let alertWindow = windows.getNext(); + let alertWindowTime = Number( + alertWindow.document.getElementById('alertTime').getAttribute('value')); + let windowTime = Number( + window.document.getElementById('alertTime').getAttribute('value')); + // The time of window creation. + // Otherwise calling the notification twice (and more) in a row + // does not work. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=1263155 + if ((alertWindow != window) && (alertWindowTime <= windowTime)) { + if (gOrigin & NS_ALERT_TOP) { + y = Math.max(y, alertWindow.screenY + alertWindow.outerHeight - WINDOW_SHADOW_SPREAD); + } else { + y = Math.min(y, alertWindow.screenY - window.outerHeight + WINDOW_SHADOW_SPREAD); + } + } + } + + // Offset the alert by WINDOW_MARGIN pixels from the edge of the screen + y += gOrigin & NS_ALERT_TOP ? WINDOW_MARGIN : -WINDOW_MARGIN; + x += gOrigin & NS_ALERT_LEFT ? WINDOW_MARGIN : -WINDOW_MARGIN; + + window.moveTo(x, y); +} + +function onAlertBeforeUnload() { + if (!gIsReplaced) { + // Move other alert windows to fill the gap left by closing alert. + let heightDelta = window.outerHeight + WINDOW_MARGIN - WINDOW_SHADOW_SPREAD; + let windows = Services.wm.getEnumerator("alert:alert"); + while (windows.hasMoreElements()) { + let alertWindow = windows.getNext(); + if (alertWindow != window) { + if (gOrigin & NS_ALERT_TOP) { + if (alertWindow.screenY > window.screenY) { + alertWindow.moveTo(alertWindow.screenX, alertWindow.screenY - heightDelta); + } + } else if (window.screenY > alertWindow.screenY) { + alertWindow.moveTo(alertWindow.screenX, alertWindow.screenY + heightDelta); + } + } + } + } + + if (gAlertListener) { + gAlertListener.observe(null, "alertfinished", gAlertCookie); + } +} + +function onAlertClick() { + if (gAlertListener && gAlertTextClickable) { + gAlertListener.observe(null, "alertclickcallback", gAlertCookie); + } + + let alertBox = document.getElementById("alertBox"); + if (alertBox.getAttribute("animate") == "true") { + // Closed when the animation ends. + alertBox.style.animationDuration = ".6s"; + alertBox.setAttribute("clicked", "true"); + } else { + window.close(); + } +} + +function doNotDisturb() { + const alertService = Cc["@mozilla.org/alerts-service;1"] + .getService(Ci.nsIAlertsService) + .QueryInterface(Ci.nsIAlertsDoNotDisturb); + alertService.manualDoNotDisturb = true; + onAlertClose(); +} + +function disableForOrigin() { + gAlertListener.observe(null, "alertdisablecallback", gAlertCookie); + onAlertClose(); +} + +function onAlertSettingsFocus(event) { + event.target.removeAttribute("focusedViaMouse"); +} + +function onAlertSettingsClick(event) { + // XXXjaws Hack used to remove the focus-ring only + // from mouse interaction, but focus-ring drawing + // should only be enabled when interacting via keyboard. + event.target.setAttribute("focusedViaMouse", true); + event.stopPropagation(); +} + +function openSettings() { + gAlertListener.observe(null, "alertsettingscallback", gAlertCookie); + onAlertClose(); +} + +function onAlertClose() { + let alertBox = document.getElementById("alertBox"); + if (alertBox.getAttribute("animate") == "true") { + // Closed when the animation ends. + alertBox.style.animationDuration = ".6s"; + alertBox.setAttribute("closing", "true"); + } else { + window.close(); + } +} |