mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-01-28 00:07:28 +00:00
Bug 398776: add new popup-based notification system, and use it for the geolocation notification, r=dolske, r=dao
This commit is contained in:
parent
5c5bcd6aa8
commit
49e5b3ee9e
@ -139,6 +139,14 @@ __defineSetter__("PluralForm", function (val) {
|
||||
return this.PluralForm = val;
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "PopupNotifications", function () {
|
||||
let tmp = {};
|
||||
Cu.import("resource://gre/modules/PopupNotifications.jsm", tmp);
|
||||
return new tmp.PopupNotifications(gBrowser,
|
||||
document.getElementById("notification-popup"),
|
||||
document.getElementById("notification-popup-box"));
|
||||
});
|
||||
|
||||
let gInitialPages = [
|
||||
"about:blank",
|
||||
"about:privatebrowsing",
|
||||
@ -4098,6 +4106,8 @@ var XULBrowserWindow = {
|
||||
// persist across the first location change.
|
||||
let nBox = gBrowser.getNotificationBox(selectedBrowser);
|
||||
nBox.removeTransientNotifications();
|
||||
|
||||
PopupNotifications.locationChange();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -284,6 +284,8 @@
|
||||
|
||||
<popup id="placesContext"/>
|
||||
|
||||
<panel id="notification-popup" position="after_start" noautofocus="true" hidden="true"/>
|
||||
|
||||
<!-- Popup for site identity information -->
|
||||
<panel id="identity-popup" position="after_start" hidden="true" noautofocus="true"
|
||||
onpopupshown="document.getElementById('identity-popup-more-info-button').focus();"
|
||||
@ -607,6 +609,9 @@
|
||||
onsearchcomplete="LocationBarHelpers._searchComplete();"
|
||||
onfocus="document.getElementById('identity-box').style.MozUserFocus= 'normal'"
|
||||
onblur="setTimeout(function() document.getElementById('identity-box').style.MozUserFocus = '', 0);">
|
||||
<box id="notification-popup-box" hidden="true" align="center">
|
||||
<image id="geo-notification-icon"/>
|
||||
</box>
|
||||
<!-- Use onclick instead of normal popup= syntax since the popup
|
||||
code fires onmousedown, and hence eats our favicon drag events.
|
||||
We only add the identity-box button to the tab order when the location bar
|
||||
|
@ -114,6 +114,7 @@ _BROWSER_FILES = \
|
||||
browser_bug432599.js \
|
||||
browser_bug435035.js \
|
||||
browser_bug441778.js \
|
||||
browser_popupNotification.js \
|
||||
browser_bug455852.js \
|
||||
browser_bug462673.js \
|
||||
browser_bug477014.js \
|
||||
|
383
browser/base/content/test/browser_popupNotification.js
Normal file
383
browser/base/content/test/browser_popupNotification.js
Normal file
@ -0,0 +1,383 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is browser popup notification test code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* the Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2010
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Gavin Sharp <gavin@gavinsharp.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
ok(PopupNotifications, "PopupNotifications object exists");
|
||||
ok(PopupNotifications.panel, "PopupNotifications panel exists");
|
||||
|
||||
registerCleanupFunction(cleanUp);
|
||||
|
||||
runNextTest();
|
||||
}
|
||||
|
||||
function cleanUp() {
|
||||
for (var topic in gActiveObservers)
|
||||
Services.obs.removeObserver(gActiveObservers[topic], topic);
|
||||
for (var eventName in gActiveListeners)
|
||||
PopupNotifications.panel.removeEventListener(eventName, gActiveListeners[eventName], false);
|
||||
}
|
||||
|
||||
var gActiveListeners = {};
|
||||
var gActiveObservers = {};
|
||||
|
||||
function runNextTest() {
|
||||
let nextTest = tests[gTestIndex];
|
||||
|
||||
function goNext() {
|
||||
if (++gTestIndex == tests.length)
|
||||
executeSoon(finish);
|
||||
else
|
||||
executeSoon(runNextTest);
|
||||
}
|
||||
|
||||
function addObserver(topic) {
|
||||
function observer() {
|
||||
Services.obs.removeObserver(observer, "PopupNotifications-" + topic);
|
||||
delete gActiveObservers["PopupNotifications-" + topic];
|
||||
|
||||
info("[Test #" + gTestIndex + "] observer for " + topic + " called");
|
||||
nextTest[topic]();
|
||||
goNext();
|
||||
}
|
||||
Services.obs.addObserver(observer, "PopupNotifications-" + topic, false);
|
||||
gActiveObservers["PopupNotifications-" + topic] = observer;
|
||||
}
|
||||
|
||||
if (nextTest.backgroundShow) {
|
||||
addObserver("backgroundShow");
|
||||
} else if (nextTest.updateNotShowing) {
|
||||
addObserver("updateNotShowing");
|
||||
} else {
|
||||
doOnPopupEvent("popupshowing", function () {
|
||||
info("[Test #" + gTestIndex + "] popup showing");
|
||||
});
|
||||
doOnPopupEvent("popupshown", function () {
|
||||
info("[Test #" + gTestIndex + "] popup shown");
|
||||
nextTest.onShown(this);
|
||||
});
|
||||
|
||||
doOnPopupEvent("popuphidden", function () {
|
||||
info("[Test #" + gTestIndex + "] popup hidden");
|
||||
nextTest.onHidden(this);
|
||||
|
||||
goNext();
|
||||
});
|
||||
info("[Test #" + gTestIndex + "] added listeners; panel state: " + PopupNotifications.isPanelOpen);
|
||||
}
|
||||
|
||||
info("[Test #" + gTestIndex + "] running test");
|
||||
nextTest.run();
|
||||
}
|
||||
|
||||
function doOnPopupEvent(eventName, callback) {
|
||||
gActiveListeners[eventName] = function (event) {
|
||||
if (event.target != PopupNotifications.panel)
|
||||
return;
|
||||
PopupNotifications.panel.removeEventListener(eventName, gActiveListeners[eventName], false);
|
||||
delete gActiveListeners[eventName];
|
||||
|
||||
callback.call(PopupNotifications.panel);
|
||||
}
|
||||
PopupNotifications.panel.addEventListener(eventName, gActiveListeners[eventName], false);
|
||||
}
|
||||
|
||||
var gTestIndex = 0;
|
||||
var gNewTab;
|
||||
|
||||
function basicNotification() {
|
||||
var self = this;
|
||||
this.browser = gBrowser.selectedBrowser;
|
||||
this.id = "test-notification";
|
||||
this.message = "This is popup notification " + this.id + " from test " + gTestIndex;
|
||||
this.anchorID = null;
|
||||
this.mainAction = {
|
||||
label: "Main Action",
|
||||
accessKey: "M",
|
||||
callback: function () {
|
||||
self.mainActionClicked = true;
|
||||
}
|
||||
};
|
||||
this.secondaryActions = [
|
||||
{
|
||||
label: "Secondary Action",
|
||||
accessKey: "S",
|
||||
callback: function () {
|
||||
self.secondaryActionClicked = true;
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
var wrongBrowserNotificationObject = new basicNotification();
|
||||
var wrongBrowserNotification;
|
||||
|
||||
var tests = [
|
||||
{ // Test #0
|
||||
run: function () {
|
||||
this.notifyObj = new basicNotification(),
|
||||
showNotification(this.notifyObj);
|
||||
},
|
||||
onShown: function (popup) {
|
||||
checkPopup(popup, this.notifyObj);
|
||||
triggerMainCommand(popup);
|
||||
},
|
||||
onHidden: function (popup) {
|
||||
ok(this.notifyObj.mainActionClicked, "mainAction was clicked");
|
||||
}
|
||||
},
|
||||
{ // Test #1
|
||||
run: function () {
|
||||
this.notifyObj = new basicNotification(),
|
||||
showNotification(this.notifyObj);
|
||||
},
|
||||
onShown: function (popup) {
|
||||
checkPopup(popup, this.notifyObj);
|
||||
triggerSecondaryCommand(popup, 0);
|
||||
},
|
||||
onHidden: function (popup) {
|
||||
ok(this.notifyObj.secondaryActionClicked, "secondaryAction was clicked");
|
||||
}
|
||||
},
|
||||
{ // Test #2
|
||||
run: function () {
|
||||
this.notifyObj = new basicNotification(),
|
||||
this.notification = showNotification(this.notifyObj);
|
||||
},
|
||||
onShown: function (popup) {
|
||||
checkPopup(popup, this.notifyObj);
|
||||
dismissNotification(popup);
|
||||
},
|
||||
onHidden: function (popup) {
|
||||
this.notification.remove();
|
||||
}
|
||||
},
|
||||
// test opening a notification for a background browser
|
||||
{ // Test #3
|
||||
run: function () {
|
||||
gNewTab = gBrowser.addTab("about:blank");
|
||||
isnot(gBrowser.selectedTab, gNewTab, "new tab isn't selected");
|
||||
wrongBrowserNotificationObject.browser = gBrowser.getBrowserForTab(gNewTab);
|
||||
wrongBrowserNotification = showNotification(wrongBrowserNotificationObject);
|
||||
},
|
||||
backgroundShow: function () {
|
||||
is(PopupNotifications.isPanelOpen, false, "panel isn't open");
|
||||
ok(!wrongBrowserNotificationObject.mainActionClicked, "main action wasn't clicked");
|
||||
ok(!wrongBrowserNotificationObject.secondaryActionClicked, "secondary action wasn't clicked");
|
||||
}
|
||||
},
|
||||
// now select that browser and test to see that the notification appeared
|
||||
{ // Test #4
|
||||
run: function () {
|
||||
this.oldSelectedTab = gBrowser.selectedTab;
|
||||
gBrowser.selectedTab = gNewTab;
|
||||
},
|
||||
onShown: function (popup) {
|
||||
checkPopup(popup, wrongBrowserNotificationObject);
|
||||
is(PopupNotifications.isPanelOpen, true, "isPanelOpen getter doesn't lie");
|
||||
|
||||
// switch back to the old browser
|
||||
gBrowser.selectedTab = this.oldSelectedTab;
|
||||
|
||||
},
|
||||
onHidden: function (popup) {
|
||||
// actually remove the notification to prevent it from reappearing
|
||||
wrongBrowserNotification.remove();
|
||||
wrongBrowserNotification = null;
|
||||
}
|
||||
},
|
||||
// test that the removed notification isn't shown on browser re-select
|
||||
{ // Test #5
|
||||
run: function () {
|
||||
gBrowser.selectedTab = gNewTab;
|
||||
},
|
||||
updateNotShowing: function () {
|
||||
is(PopupNotifications.isPanelOpen, false, "panel isn't open");
|
||||
gBrowser.removeTab(gNewTab);
|
||||
}
|
||||
},
|
||||
// Test that two notifications with the same ID result in a single displayed
|
||||
// notification.
|
||||
{ // Test #6
|
||||
run: function () {
|
||||
this.notifyObj = new basicNotification(),
|
||||
// Show the same notification twice
|
||||
this.notification1 = showNotification(this.notifyObj);
|
||||
this.notification2 = showNotification(this.notifyObj);
|
||||
},
|
||||
onShown: function (popup) {
|
||||
checkPopup(popup, this.notifyObj);
|
||||
dismissNotification(popup);
|
||||
},
|
||||
onHidden: function (popup) {
|
||||
}
|
||||
},
|
||||
// Test that two notifications with different IDs are displayed
|
||||
{ // Test #7
|
||||
run: function () {
|
||||
this.testNotif1 = new basicNotification();
|
||||
this.testNotif1.message += " 1";
|
||||
showNotification(this.testNotif1);
|
||||
this.testNotif2 = new basicNotification();
|
||||
this.testNotif2.message += " 2";
|
||||
this.testNotif2.id = "test-notification-2";
|
||||
showNotification(this.testNotif2);
|
||||
},
|
||||
onShown: function (popup) {
|
||||
is(popup.childNodes.length, 2, "two notifications are shown");
|
||||
// Trigger the main command for the first notification, and the secondary
|
||||
// for the second. Need to do mainCommand first since the secondaryCommand
|
||||
// triggering is async.
|
||||
triggerMainCommand(popup);
|
||||
is(popup.childNodes.length, 1, "only one notification left");
|
||||
triggerSecondaryCommand(popup, 0);
|
||||
},
|
||||
onHidden: function (popup) {
|
||||
ok(this.testNotif1.mainActionClicked, "main action #1 was clicked");
|
||||
ok(!this.testNotif1.secondaryActionClicked, "secondary action #1 wasn't clicked");
|
||||
|
||||
ok(!this.testNotif2.mainActionClicked, "main action #2 wasn't clicked");
|
||||
ok(this.testNotif2.secondaryActionClicked, "secondary action #2 was clicked");
|
||||
}
|
||||
},
|
||||
// Test notification without mainAction
|
||||
{ // Test #8
|
||||
run: function () {
|
||||
this.notifyObj = new basicNotification(),
|
||||
this.notifyObj.mainAction = null;
|
||||
showNotification(this.notifyObj);
|
||||
},
|
||||
onShown: function (popup) {
|
||||
checkPopup(popup, this.notifyObj);
|
||||
dismissNotification(popup);
|
||||
},
|
||||
onHidden: function (popup) {
|
||||
}
|
||||
},
|
||||
// Test two notifications with different anchors
|
||||
{ // Test #9
|
||||
run: function () {
|
||||
this.notifyObj = new basicNotification(),
|
||||
this.firstNotification = showNotification(this.notifyObj);
|
||||
this.notifyObj2 = new basicNotification();
|
||||
this.notifyObj2.id += "-2";
|
||||
this.notifyObj2.anchorID = "urlbar";
|
||||
// Second showNotification() overrides the first
|
||||
this.secondNotification = showNotification(this.notifyObj2);
|
||||
},
|
||||
onShown: function (popup) {
|
||||
// This also checks that only one element is shown.
|
||||
checkPopup(popup, this.notifyObj2);
|
||||
dismissNotification(popup);
|
||||
},
|
||||
onHidden: function (popup) {
|
||||
// Remove the first notification
|
||||
this.firstNotification.remove();
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
function showNotification(notifyObj) {
|
||||
return PopupNotifications.show(notifyObj.browser,
|
||||
notifyObj.id,
|
||||
notifyObj.message,
|
||||
notifyObj.anchorID,
|
||||
notifyObj.mainAction,
|
||||
notifyObj.secondaryActions);
|
||||
}
|
||||
|
||||
function checkPopup(popup, notificationObj) {
|
||||
info("[Test #" + gTestIndex + "] checking popup");
|
||||
let notifications = popup.childNodes;
|
||||
|
||||
is(notifications.length, 1, "only one notification displayed");
|
||||
let notification = notifications[0];
|
||||
is(notification.getAttribute("label"), notificationObj.message, "message matches");
|
||||
is(notification.id, notificationObj.id, "id matches");
|
||||
if (notificationObj.mainAction) {
|
||||
is(notification.getAttribute("buttonlabel"), notificationObj.mainAction.label, "main action label matches");
|
||||
is(notification.getAttribute("buttonaccesskey"), notificationObj.mainAction.accessKey, "main action accesskey matches");
|
||||
}
|
||||
let actualSecondaryActions = notification.childNodes;
|
||||
let secondaryActions = notificationObj.secondaryActions || [];
|
||||
is(actualSecondaryActions.length, secondaryActions.length, actualSecondaryActions.length + " secondary actions");
|
||||
secondaryActions.forEach(function (a, i) {
|
||||
is(actualSecondaryActions[i].getAttribute("label"), a.label, "label for secondary action " + i + " matches");
|
||||
is(actualSecondaryActions[i].getAttribute("accesskey"), a.accessKey, "accessKey for secondary action " + i + " matches");
|
||||
});
|
||||
}
|
||||
|
||||
function triggerMainCommand(popup) {
|
||||
info("[Test #" + gTestIndex + "] triggering main command");
|
||||
let notifications = popup.childNodes;
|
||||
ok(notifications.length > 0, "at least one notification displayed");
|
||||
let notification = notifications[0];
|
||||
|
||||
// 20, 10 so that the inner button is hit
|
||||
EventUtils.synthesizeMouse(notification.button, 20, 10, {});
|
||||
}
|
||||
|
||||
function triggerSecondaryCommand(popup, index) {
|
||||
info("[Test #" + gTestIndex + "] triggering secondary command");
|
||||
let notifications = popup.childNodes;
|
||||
ok(notifications.length > 0, "at least one notification displayed");
|
||||
let notification = notifications[0];
|
||||
|
||||
notification.button.focus();
|
||||
|
||||
popup.addEventListener("popupshown", function () {
|
||||
popup.removeEventListener("popupshown", arguments.callee, false);
|
||||
|
||||
// Press down until the desired command is selected
|
||||
for (let i = 0; i <= index; i++)
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
|
||||
// Activate
|
||||
EventUtils.synthesizeKey("VK_ENTER", {});
|
||||
}, false);
|
||||
|
||||
// One down event to open the popup
|
||||
EventUtils.synthesizeKey("VK_DOWN", { altKey: (navigator.platform.indexOf("Mac") == -1) });
|
||||
}
|
||||
|
||||
function dismissNotification(popup) {
|
||||
info("[Test #" + gTestIndex + "] dismissing notification");
|
||||
executeSoon(function () {
|
||||
EventUtils.synthesizeKey("VK_ESCAPE", {});
|
||||
});
|
||||
}
|
@ -38,7 +38,9 @@
|
||||
#
|
||||
# ***** END LICENSE BLOCK *****
|
||||
|
||||
<bindings id="urlbarBindings" xmlns="http://www.mozilla.org/xbl">
|
||||
<bindings id="urlbarBindings" xmlns="http://www.mozilla.org/xbl"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:xbl="http://www.mozilla.org/xbl">
|
||||
|
||||
<binding id="urlbar" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete">
|
||||
<implementation implements="nsIObserver, nsIDOMEventListener">
|
||||
@ -679,5 +681,4 @@
|
||||
|
||||
</implementation>
|
||||
</binding>
|
||||
|
||||
</bindings>
|
||||
|
@ -1261,9 +1261,15 @@ GeolocationPrompt.prototype = {
|
||||
contractID: "@mozilla.org/geolocation/prompt;1",
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIGeolocationPrompt]),
|
||||
|
||||
|
||||
prompt: function GP_prompt(request) {
|
||||
var result = Services.perms.testExactPermission(request.requestingURI, "geo");
|
||||
var requestingURI = request.requestingURI;
|
||||
|
||||
// Ignore requests from non-nsIStandardURLs
|
||||
if (!(requestingURI instanceof Ci.nsIStandardURL))
|
||||
return;
|
||||
|
||||
var result = Services.perms.testExactPermission(requestingURI, "geo");
|
||||
|
||||
if (result == Ci.nsIPermissionManager.ALLOW_ACTION) {
|
||||
request.allow();
|
||||
@ -1275,13 +1281,6 @@ GeolocationPrompt.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
function setPagePermission(uri, allow) {
|
||||
if (allow == true)
|
||||
Services.perms.add(uri, "geo", Ci.nsIPermissionManager.ALLOW_ACTION);
|
||||
else
|
||||
Services.perms.add(uri, "geo", Ci.nsIPermissionManager.DENY_ACTION);
|
||||
}
|
||||
|
||||
function getChromeWindow(aWindow) {
|
||||
var chromeWin = aWindow
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
@ -1294,93 +1293,63 @@ GeolocationPrompt.prototype = {
|
||||
return chromeWin;
|
||||
}
|
||||
|
||||
var requestingWindow = request.requestingWindow.top;
|
||||
var chromeWindowObject = getChromeWindow(requestingWindow).wrappedJSObject;
|
||||
var tabbrowser = chromeWindowObject.gBrowser;
|
||||
var browser = tabbrowser.getBrowserForDocument(requestingWindow.document);
|
||||
var notificationBox = tabbrowser.getNotificationBox(browser);
|
||||
var browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
|
||||
|
||||
var notification = notificationBox.getNotificationWithValue("geolocation");
|
||||
if (!notification) {
|
||||
var browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
|
||||
var mainAction = {
|
||||
label: browserBundle.GetStringFromName("geolocation.shareLocation"),
|
||||
accessKey: browserBundle.GetStringFromName("geolocation.shareLocation.accesskey"),
|
||||
callback: function(notification) {
|
||||
request.allow();
|
||||
},
|
||||
};
|
||||
|
||||
var buttons = [{
|
||||
label: browserBundle.GetStringFromName("geolocation.shareLocation"),
|
||||
accessKey: browserBundle.GetStringFromName("geolocation.shareLocation.accesskey"),
|
||||
callback: function(notification) {
|
||||
var elements = notification.getElementsByClassName("rememberChoice");
|
||||
if (elements.length && elements[0].checked)
|
||||
setPagePermission(request.requestingURI, true);
|
||||
request.allow();
|
||||
},
|
||||
},
|
||||
{
|
||||
label: browserBundle.GetStringFromName("geolocation.dontShareLocation"),
|
||||
accessKey: browserBundle.GetStringFromName("geolocation.dontShareLocation.accesskey"),
|
||||
callback: function(notification) {
|
||||
var elements = notification.getElementsByClassName("rememberChoice");
|
||||
if (elements.length && elements[0].checked)
|
||||
setPagePermission(request.requestingURI, false);
|
||||
request.cancel();
|
||||
},
|
||||
}];
|
||||
|
||||
var message;
|
||||
// XXX Bug 573536
|
||||
// browserBundle.GetStringFromName("geolocation.learnMore")
|
||||
//var formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter);
|
||||
//link.href = formatter.formatURLPref("browser.geolocation.warning.infoURL");
|
||||
|
||||
// Different message/info if it is a local file
|
||||
if (request.requestingURI.schemeIs("file")) {
|
||||
message = browserBundle.formatStringFromName("geolocation.fileWantsToKnow",
|
||||
[request.requestingURI.path], 1);
|
||||
} else {
|
||||
message = browserBundle.formatStringFromName("geolocation.siteWantsToKnow",
|
||||
[request.requestingURI.host], 1);
|
||||
var message;
|
||||
var secondaryActions = [];
|
||||
|
||||
// Different message/options if it is a local file
|
||||
if (requestingURI.schemeIs("file")) {
|
||||
message = browserBundle.formatStringFromName("geolocation.fileWantsToKnow",
|
||||
[request.requestingURI.path], 1);
|
||||
} else {
|
||||
message = browserBundle.formatStringFromName("geolocation.siteWantsToKnow",
|
||||
[requestingURI.host], 1);
|
||||
|
||||
// Don't offer to "always/never share" in PB mode
|
||||
var inPrivateBrowsing = Cc["@mozilla.org/privatebrowsing;1"].
|
||||
getService(Ci.nsIPrivateBrowsingService).
|
||||
privateBrowsingEnabled;
|
||||
|
||||
if (!inPrivateBrowsing) {
|
||||
secondaryActions.push({
|
||||
label: browserBundle.GetStringFromName("geolocation.alwaysShare"),
|
||||
accessKey: browserBundle.GetStringFromName("geolocation.alwaysShare.accesskey"),
|
||||
callback: function () {
|
||||
Services.perms.add(requestingURI, "geo", Ci.nsIPermissionManager.ALLOW_ACTION);
|
||||
request.allow();
|
||||
}
|
||||
});
|
||||
secondaryActions.push({
|
||||
label: browserBundle.GetStringFromName("geolocation.neverShare"),
|
||||
accessKey: browserBundle.GetStringFromName("geolocation.neverShare.accesskey"),
|
||||
callback: function () {
|
||||
Services.perms.add(requestingURI, "geo", Ci.nsIPermissionManager.DENY_ACTION);
|
||||
request.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var newBar = notificationBox.appendNotification(message,
|
||||
"geolocation",
|
||||
"chrome://browser/skin/Geo.png",
|
||||
notificationBox.PRIORITY_INFO_HIGH,
|
||||
buttons);
|
||||
|
||||
// For whatever reason, if we do this immediately
|
||||
// (eg, without the setTimeout), the "link"
|
||||
// element does not show up in the notification
|
||||
// bar.
|
||||
function geolocation_hacks_to_notification () {
|
||||
|
||||
// Never show a remember checkbox inside the private browsing mode
|
||||
var inPrivateBrowsing = Cc["@mozilla.org/privatebrowsing;1"].
|
||||
getService(Ci.nsIPrivateBrowsingService).
|
||||
privateBrowsingEnabled;
|
||||
|
||||
// don't show "Remember for this site" checkbox for file:
|
||||
var host;
|
||||
try {
|
||||
host = request.requestingURI.host;
|
||||
} catch (ex) {}
|
||||
|
||||
if (!inPrivateBrowsing && host) {
|
||||
var checkbox = newBar.ownerDocument.createElementNS(XULNS, "checkbox");
|
||||
checkbox.className = "rememberChoice";
|
||||
checkbox.setAttribute("label", browserBundle.GetStringFromName("geolocation.remember"));
|
||||
checkbox.setAttribute("accesskey", browserBundle.GetStringFromName("geolocation.remember.accesskey"));
|
||||
newBar.appendChild(checkbox);
|
||||
}
|
||||
|
||||
var link = newBar.ownerDocument.createElementNS(XULNS, "label");
|
||||
link.className = "text-link";
|
||||
link.setAttribute("value", browserBundle.GetStringFromName("geolocation.learnMore"));
|
||||
|
||||
var formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter);
|
||||
link.href = formatter.formatURLPref("browser.geolocation.warning.infoURL");
|
||||
|
||||
var description = newBar.ownerDocument.getAnonymousElementByAttribute(newBar, "anonid", "messageText");
|
||||
description.appendChild(link);
|
||||
};
|
||||
|
||||
chromeWindowObject.setTimeout(geolocation_hacks_to_notification, 0);
|
||||
|
||||
}
|
||||
|
||||
var requestingWindow = request.requestingWindow.top;
|
||||
var chromeWin = getChromeWindow(requestingWindow).wrappedJSObject;
|
||||
var browser = chromeWin.gBrowser.getBrowserForDocument(requestingWindow.document);
|
||||
|
||||
chromeWin.PopupNotifications.show(browser, "geolocation", message, "geo-notification-icon",
|
||||
mainAction, secondaryActions);
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -51,42 +51,33 @@ function test() {
|
||||
gBrowser.selectedBrowser.addEventListener("load", function () {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
|
||||
|
||||
setTimeout(function() {
|
||||
// Make sure the notification is correctly displayed with a remember control
|
||||
let notificationBox = gBrowser.getNotificationBox();
|
||||
let notification = notificationBox.getNotificationWithValue("geolocation");
|
||||
ok(notification, "Notification box should be displaying outside of private browsing mode");
|
||||
is(notification.getElementsByClassName("rememberChoice").length, 1,
|
||||
"The remember control must be displayed outside of private browsing mode");
|
||||
notificationBox.currentNotification.close();
|
||||
let notification = PopupNotifications.getNotification("geolocation");
|
||||
ok(notification, "Notification should exist");
|
||||
ok(notification.secondaryActions.length > 1, "Secondary actions should exist (always/never remember)");
|
||||
notification.remove();
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
|
||||
// enter the private browsing mode
|
||||
pb.privateBrowsingEnabled = true;
|
||||
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function () {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
|
||||
|
||||
// Make sure the notification is correctly displayed without a remember control
|
||||
let notification = PopupNotifications.getNotification("geolocation");
|
||||
ok(notification, "Notification should exist");
|
||||
is(notification.secondaryActions.length, 0, "Secondary actions shouldn't exist (always/never remember)");
|
||||
notification.remove();
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
|
||||
// enter the private browsing mode
|
||||
pb.privateBrowsingEnabled = true;
|
||||
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function () {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
|
||||
|
||||
setTimeout(function () {
|
||||
// Make sure the notification is correctly displayed without a remember control
|
||||
let notificationBox = gBrowser.getNotificationBox();
|
||||
let notification = notificationBox.getNotificationWithValue("geolocation");
|
||||
ok(notification, "Notification box should be displaying outside of private browsing mode");
|
||||
is(notification.getElementsByClassName("rememberChoice").length, 0,
|
||||
"The remember control must not be displayed inside of private browsing mode");
|
||||
notificationBox.currentNotification.close();
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
|
||||
// cleanup
|
||||
pb.privateBrowsingEnabled = false;
|
||||
finish();
|
||||
}, 100); // remember control is added in a setTimeout(0) call
|
||||
}, true);
|
||||
content.location = testPageURL;
|
||||
}, 100); // remember control is added in a setTimeout(0) call
|
||||
// cleanup
|
||||
pb.privateBrowsingEnabled = false;
|
||||
finish();
|
||||
}, true);
|
||||
content.location = testPageURL;
|
||||
}, true);
|
||||
content.location = testPageURL;
|
||||
}
|
||||
|
@ -201,19 +201,21 @@ puAlertText=Click here for details
|
||||
|
||||
# Geolocation UI
|
||||
|
||||
# LOCALIZATION NOTE (geolocation.shareLocation geolocation.dontShareLocation):
|
||||
# LOCALIZATION NOTE (geolocation.shareLocation geolocation.dontShareLocation geolocation.alwaysShare geolocation.neverShare):
|
||||
#If you're having trouble with the word Share, please use Allow and Block in your language.
|
||||
geolocation.shareLocation=Share Location
|
||||
geolocation.shareLocation.accesskey=a
|
||||
geolocation.dontShareLocation=Don't Share
|
||||
geolocation.dontShareLocation.accesskey=o
|
||||
geolocation.alwaysShare=Always Share
|
||||
geolocation.alwaysShare.accesskey=A
|
||||
geolocation.neverShare=Never Share
|
||||
geolocation.neverShare.accesskey=N
|
||||
geolocation.siteWantsToKnow=%S wants to know your location.
|
||||
geolocation.fileWantsToKnow=The file %S wants to know your location.
|
||||
# LOCALIZATION NOTE (geolocation.learnMore): Use the unicode ellipsis char, \u2026,
|
||||
# or use "..." if \u2026 doesn't suit traditions in your locale.
|
||||
geolocation.learnMore=Learn More…
|
||||
geolocation.remember=Remember for this site
|
||||
geolocation.remember.accesskey=R
|
||||
|
||||
# Phishing/Malware Notification Bar.
|
||||
# LOCALIZATION NOTE (notAForgery, notAnAttack)
|
||||
|
@ -955,6 +955,34 @@ toolbar[iconsize="small"] #fullscreen-button {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* Notification popup */
|
||||
#notification-popup {
|
||||
margin: 4px 0;
|
||||
min-width: 280px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.popup-notification-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
-moz-margin-end: 10px;
|
||||
}
|
||||
|
||||
.popup-notification-icon[popupid="geolocation"] {
|
||||
list-style-image: url(chrome://browser/skin/Geo.png);
|
||||
}
|
||||
|
||||
/* Notification icon box */
|
||||
#notification-popup-box {
|
||||
margin: 0 3px;
|
||||
}
|
||||
|
||||
#geo-notification-icon {
|
||||
list-style-image: url(chrome://browser/skin/Geo.png);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
/* Feed icon */
|
||||
#feed-button,
|
||||
#feed-button > .button-box,
|
||||
|
@ -1881,7 +1881,8 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
|
||||
}
|
||||
|
||||
/* Popup Bounding Box */
|
||||
#identity-popup {
|
||||
#identity-popup,
|
||||
#notification-popup {
|
||||
-moz-appearance: none;
|
||||
-moz-window-shadow: none;
|
||||
background-color: transparent;
|
||||
@ -1891,7 +1892,38 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
|
||||
-moz-border-image: url(chrome://browser/skin/hud-panel.png) 26 18 22 50 / 26px 18px 22px 50px repeat;
|
||||
}
|
||||
|
||||
#identity-popup-container {
|
||||
#notification-popup {
|
||||
margin-top: -1px;
|
||||
margin-left: -27px;
|
||||
}
|
||||
|
||||
#notification-popup-box {
|
||||
margin: 0 3px;
|
||||
}
|
||||
|
||||
#geo-notification-icon {
|
||||
list-style-image: url(chrome://browser/skin/Geo.png);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
.popup-notification-description {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.popup-notification-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
-moz-margin-end: 10px;
|
||||
}
|
||||
|
||||
.popup-notification-icon[popupid="geolocation"] {
|
||||
list-style-image: url(chrome://browser/skin/Geo.png);
|
||||
}
|
||||
|
||||
#identity-popup-container,
|
||||
#identity-popup-notification-container {
|
||||
margin: 4px 3px 2px -30px;
|
||||
color: #fff;
|
||||
}
|
||||
|
@ -102,7 +102,8 @@
|
||||
/* Bug 413060, comment 16: Vista Aero is a special case where we use a
|
||||
tooltip appearance for the address bar popup panels */
|
||||
#identity-popup,
|
||||
#editBookmarkPanel {
|
||||
#editBookmarkPanel,
|
||||
#notification-popup {
|
||||
-moz-appearance: tooltip;
|
||||
color: InfoText;
|
||||
}
|
||||
|
@ -1540,11 +1540,39 @@ toolbarbutton.bookmark-item[dragover="true"][open="true"] {
|
||||
}
|
||||
|
||||
/* Popup Bounding Box */
|
||||
#identity-popup {
|
||||
#identity-popup,
|
||||
#notification-popup {
|
||||
-moz-appearance: menupopup;
|
||||
color: MenuText;
|
||||
}
|
||||
|
||||
/* Notification popup */
|
||||
#notification-popup {
|
||||
padding: 10px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.popup-notification-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
-moz-margin-end: 10px;
|
||||
}
|
||||
|
||||
.popup-notification-icon[popupid="geolocation"] {
|
||||
list-style-image: url(chrome://browser/skin/Geo.png);
|
||||
}
|
||||
|
||||
/* Notification icon box */
|
||||
#notification-popup-box {
|
||||
margin: 0 3px;
|
||||
}
|
||||
|
||||
#geo-notification-icon {
|
||||
list-style-image: url(chrome://browser/skin/Geo.png);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
#identity-popup-container {
|
||||
min-width: 280px;
|
||||
padding: 9px;
|
||||
|
@ -45,16 +45,14 @@ include $(DEPTH)/config/autoconf.mk
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
_TEST_FILES = \
|
||||
test_manyCurrentSerial.html \
|
||||
test_manyCurrentConcurrent.html \
|
||||
test_manyCurrentSerial.html \
|
||||
test_manyCurrentConcurrent.html \
|
||||
test_garbageWatch.html \
|
||||
test_manyWatchConcurrent.html \
|
||||
test_manyWatchSerial.html \
|
||||
test_manyWindows.html \
|
||||
test_allowCurrent.html \
|
||||
test_allowWatch.html \
|
||||
test_cancelCurrent.html \
|
||||
test_cancelWatch.html \
|
||||
test_clearWatch.html \
|
||||
test_clearWatch_invalid.html \
|
||||
test_timeoutWatch.html \
|
||||
@ -63,9 +61,14 @@ _TEST_FILES = \
|
||||
geolocation_common.js \
|
||||
geolocation.html \
|
||||
test_optional_api_params.html \
|
||||
network_geolocation.sjs \
|
||||
network_geolocation.sjs \
|
||||
$(NULL)
|
||||
|
||||
ifndef MOZ_PHOENIX
|
||||
_TEST_FILES += test_cancelCurrent.html \
|
||||
test_cancelWatch.html \
|
||||
$(NULL)
|
||||
endif
|
||||
|
||||
libs:: $(_TEST_FILES)
|
||||
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
|
||||
|
@ -1,73 +1,10 @@
|
||||
<html> <head>
|
||||
<title>Simple access of geolocation</title>
|
||||
<head>
|
||||
<script>
|
||||
|
||||
// This is copied from geolocation-common.js. We do this so
|
||||
// that we do not document write ANYTHING to this page.
|
||||
|
||||
function getNotificationBox()
|
||||
{
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
function getChromeWindow(aWindow) {
|
||||
var chromeWin = aWindow
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShellTreeItem)
|
||||
.rootTreeItem
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindow)
|
||||
.QueryInterface(Ci.nsIDOMChromeWindow);
|
||||
return chromeWin;
|
||||
}
|
||||
|
||||
var notifyWindow = window.top;
|
||||
|
||||
var chromeWin = getChromeWindow(notifyWindow);
|
||||
|
||||
var notifyBox = chromeWin.getNotificationBox(notifyWindow);
|
||||
|
||||
return notifyBox;
|
||||
}
|
||||
|
||||
|
||||
function clickNotificationButton(aButtonIndex) {
|
||||
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
||||
|
||||
// This is a bit of a hack. The notification doesn't have an API to
|
||||
// trigger buttons, so we dive down into the implementation and twiddle
|
||||
// the buttons directly.
|
||||
var box = getNotificationBox();
|
||||
var bar = box.getNotificationWithValue("geolocation");
|
||||
var button = bar.getElementsByTagName("button").item(aButtonIndex);
|
||||
button.doCommand();
|
||||
}
|
||||
|
||||
const kAcceptButton = 0;
|
||||
const kDenyButton = 1;
|
||||
|
||||
gotPosition = function(location){
|
||||
document.location.href="http://www.mozilla.org";
|
||||
};
|
||||
|
||||
failPosition = function(error){
|
||||
document.location.href="http://www.mozilla.org";
|
||||
};
|
||||
|
||||
function accept() {
|
||||
clickNotificationButton(kAcceptButton);
|
||||
}
|
||||
|
||||
navigator.geolocation.getCurrentPosition(gotPosition,
|
||||
failPosition,
|
||||
{timeout:30000});
|
||||
|
||||
setTimeout(accept, 1000);
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
</body>
|
||||
<html>
|
||||
<head>
|
||||
<title>Simple access of geolocation</title>
|
||||
<head>
|
||||
<script>
|
||||
navigator.geolocation.getCurrentPosition(function () {}, null, {timeout:30000});
|
||||
</script>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
|
@ -56,35 +56,45 @@ function check_geolocation(location) {
|
||||
}
|
||||
|
||||
|
||||
function getNotificationBox()
|
||||
function getChromeWindow()
|
||||
{
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
function getChromeWindow(aWindow) {
|
||||
var chromeWin = aWindow
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShellTreeItem)
|
||||
.rootTreeItem
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindow)
|
||||
.QueryInterface(Ci.nsIDOMChromeWindow);
|
||||
return chromeWin;
|
||||
}
|
||||
|
||||
var notifyWindow = window.top;
|
||||
|
||||
var chromeWin = getChromeWindow(notifyWindow);
|
||||
var chromeWin = window.top
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShellTreeItem)
|
||||
.rootTreeItem
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindow)
|
||||
.QueryInterface(Ci.nsIDOMChromeWindow);
|
||||
return chromeWin;
|
||||
}
|
||||
|
||||
function getNotificationBox()
|
||||
{
|
||||
var chromeWin = getChromeWindow();
|
||||
var notifyBox = chromeWin.getNotificationBox(notifyWindow);
|
||||
|
||||
return notifyBox;
|
||||
}
|
||||
|
||||
|
||||
function clickNotificationButton(aButtonIndex) {
|
||||
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
||||
|
||||
// First, check for new-style Firefox notifications
|
||||
var chromeWin = getChromeWindow();
|
||||
if (chromeWin.PopupNotifications) {
|
||||
var panel = chromeWin.PopupNotifications.panel;
|
||||
var notificationEl = panel.getElementsByAttribute("id", "geolocation")[0];
|
||||
if (aButtonIndex == kAcceptButton)
|
||||
notificationEl.button.doCommand();
|
||||
else if (aButtonIndex == kDenyButton)
|
||||
throw "clickNotificationButton(kDenyButton) isn't supported in Firefox";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, fall back to looking for a notificationbox
|
||||
// This is a bit of a hack. The notification doesn't have an API to
|
||||
// trigger buttons, so we dive down into the implementation and twiddle
|
||||
// the buttons directly.
|
||||
|
@ -107,10 +107,9 @@ ok(!exception, exception);
|
||||
|
||||
// Successful calls trigger a geolocation notification,
|
||||
// so clean up ready for the next test.
|
||||
clickNotificationButton(kDenyButton);
|
||||
clickNotificationButton(kAcceptButton);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
@ -85,6 +85,7 @@ endif
|
||||
EXTRA_JS_MODULES = \
|
||||
InlineSpellChecker.jsm \
|
||||
WindowDraggingUtils.jsm \
|
||||
PopupNotifications.jsm \
|
||||
$(NULL)
|
||||
|
||||
EXTRA_PP_JS_MODULES = \
|
||||
|
431
toolkit/content/PopupNotifications.jsm
Normal file
431
toolkit/content/PopupNotifications.jsm
Normal file
@ -0,0 +1,431 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is browser notifications.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* the Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2010
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Gavin Sharp <gavin@gavinsharp.com> (Original Author)
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
var EXPORTED_SYMBOLS = ["PopupNotifications"];
|
||||
|
||||
var Cc = Components.classes, Ci = Components.interfaces;
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
/**
|
||||
* Notification object describes a single popup notification.
|
||||
*
|
||||
* @see PopupNotifications.show()
|
||||
*/
|
||||
function Notification(id, message, anchorID, mainAction, secondaryActions,
|
||||
browser, owner) {
|
||||
this.id = id;
|
||||
this.message = message;
|
||||
this.anchorID = anchorID;
|
||||
this.mainAction = mainAction;
|
||||
this.secondaryActions = secondaryActions || [];
|
||||
this.browser = browser;
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
Notification.prototype = {
|
||||
/**
|
||||
* Removes the notification and updates the popup accordingly if needed.
|
||||
*/
|
||||
remove: function Notification_remove() {
|
||||
this.owner.remove(this);
|
||||
},
|
||||
|
||||
get anchorElement() {
|
||||
let anchorElement = null;
|
||||
if (this.anchorID)
|
||||
anchorElement = this.owner.window.document.getElementById(this.anchorID);
|
||||
|
||||
if (!anchorElement)
|
||||
anchorElement = this.owner.iconBox;
|
||||
|
||||
return anchorElement;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The PopupNotifications object manages popup notifications for a given browser
|
||||
* window.
|
||||
* @param tabbrowser
|
||||
* window's <xul:tabbrowser/>. Used to observe tab switching events and
|
||||
* for determining the active browser element.
|
||||
* @param panel
|
||||
* The <xul:panel/> element to use for notifications. The panel is
|
||||
* populated with <popupnotification> children and displayed it as
|
||||
* needed.
|
||||
* @param iconBox
|
||||
* Optional reference to a container element that should be hidden or
|
||||
* unhidden when notifications are hidden or shown. Used as a fallback
|
||||
* popup anchor if notifications do not specify anchor IDs.
|
||||
*/
|
||||
function PopupNotifications(tabbrowser, panel, iconBox) {
|
||||
this.window = tabbrowser.ownerDocument.defaultView;
|
||||
this.panel = panel;
|
||||
this.iconBox = iconBox;
|
||||
this.tabbrowser = tabbrowser;
|
||||
|
||||
let self = this;
|
||||
this.panel.addEventListener("popuphidden", function (event) {
|
||||
self._onPopupHidden(event);
|
||||
}, true);
|
||||
|
||||
function updateFromListeners() {
|
||||
// setTimeout(..., 0) needed, otherwise openPopup from "activate" event
|
||||
// handler results in the popup being hidden again for some reason...
|
||||
self.window.setTimeout(function () {
|
||||
self._update();
|
||||
}, 0);
|
||||
}
|
||||
this.window.addEventListener("activate", updateFromListeners, true);
|
||||
this.tabbrowser.tabContainer.addEventListener("TabSelect", updateFromListeners, true);
|
||||
}
|
||||
|
||||
PopupNotifications.prototype = {
|
||||
/**
|
||||
* Retrieve a Notification object associated with the browser/ID pair.
|
||||
* @param id
|
||||
* The Notification ID to search for.
|
||||
* @param browser
|
||||
* The browser whose notifications should be searched. If null, the
|
||||
* currently selected browser's notifications will be searched.
|
||||
*
|
||||
* @returns the corresponding Notification object, or null if no such
|
||||
* notification exists.
|
||||
*/
|
||||
getNotification: function PopupNotifications_getNotification(id, browser) {
|
||||
let n = null;
|
||||
let notifications = this._getNotificationsForBrowser(browser || this.tabbrowser.selectedBrowser);
|
||||
notifications.some(function(x) x.id == id && (n = x))
|
||||
return n;
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a new popup notification.
|
||||
* @param browser
|
||||
* The <xul:browser> element associated with the notification. Must not
|
||||
* be null.
|
||||
* @param id
|
||||
* A unique ID that identifies the type of notification (e.g.
|
||||
* "geolocation"). Only one notification with a given ID can be visible
|
||||
* at a time. If a notification already exists with the given ID, it
|
||||
* will be replaced.
|
||||
* @param message
|
||||
* The text to be displayed in the notification.
|
||||
* @param anchorID
|
||||
* The ID of the element that should be used as this notification
|
||||
* popup's anchor. May be null, in which case the notification will be
|
||||
* anchored to the iconBox.
|
||||
* @param mainAction
|
||||
* A JavaScript object literal describing the notification button's
|
||||
* action. If present, it must have the following properties:
|
||||
* - label (string): the button's label.
|
||||
* - accessKey (string): the button's accessKey.
|
||||
* - callback (function): a callback to be invoked when the button is
|
||||
* pressed.
|
||||
* If null, the notification will not have a button, and
|
||||
* secondaryActions will be ignored.
|
||||
* @param secondaryActions
|
||||
* An optional JavaScript array describing the notification's alternate
|
||||
* actions. The array should contain objects with the same properties
|
||||
* as mainAction. These are used to populate the notification button's
|
||||
* dropdown menu.
|
||||
* @returns the Notification object corresponding to the added notification.
|
||||
*/
|
||||
show: function PopupNotifications_show(browser, id, message, anchorID,
|
||||
mainAction, secondaryActions, options) {
|
||||
function isInvalidAction(a) {
|
||||
return !a || !(typeof(a.callback) == "function") || !a.label || !a.accessKey;
|
||||
}
|
||||
|
||||
if (!browser)
|
||||
throw "PopupNotifications_show: invalid browser";
|
||||
if (!id)
|
||||
throw "PopupNotifications_show: invalid ID";
|
||||
if (!message)
|
||||
throw "PopupNotifications_show: invalid message";
|
||||
if (mainAction && isInvalidAction(mainAction))
|
||||
throw "PopupNotifications_show: invalid mainAction";
|
||||
if (secondaryActions.some(isInvalidAction))
|
||||
throw "PopupNotifications_show: invalid secondaryActions";
|
||||
|
||||
let notification = new Notification(id, message, anchorID, mainAction,
|
||||
secondaryActions, browser, this);
|
||||
|
||||
|
||||
let existingNotification = this.getNotification(id, browser);
|
||||
if (existingNotification)
|
||||
this._remove(existingNotification);
|
||||
|
||||
let notifications = this._getNotificationsForBrowser(browser);
|
||||
notifications.push(notification);
|
||||
|
||||
let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
|
||||
if (browser == this.tabbrowser.selectedBrowser && fm.activeWindow == this.window) {
|
||||
// show panel now
|
||||
this._update(notification.anchorElement);
|
||||
} else {
|
||||
// Otherwise, update() will display the notification the next time the
|
||||
// relevant tab/window is selected.
|
||||
|
||||
// Notify observers that we're not showing the popup (useful for testing)
|
||||
this._notify("backgroundShow");
|
||||
}
|
||||
|
||||
return notification;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if the notification popup is currently being displayed.
|
||||
*/
|
||||
get isPanelOpen() {
|
||||
let panelState = this.panel.state;
|
||||
|
||||
return panelState == "showing" || panelState == "open";
|
||||
},
|
||||
|
||||
/**
|
||||
* Called by the consumer to indicate that the current browser's location has
|
||||
* changed, so that we can update the active notifications accordingly.
|
||||
*/
|
||||
locationChange: function PopupNotifications_locationChange() {
|
||||
// For now, just clear all notifications...
|
||||
this._currentNotifications = [];
|
||||
|
||||
this._update();
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes a Notification.
|
||||
* @param notification
|
||||
* The Notification object to remove.
|
||||
*/
|
||||
remove: function PopupNotifications_remove(notification) {
|
||||
let isCurrent = this._currentNotifications.indexOf(notification) != -1;
|
||||
this._remove(notification);
|
||||
|
||||
// update the panel, if needed
|
||||
if (this.isPanelOpen && isCurrent)
|
||||
this._update();
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Utility methods
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Gets and sets notifications for the currently selected browser.
|
||||
*/
|
||||
get _currentNotifications() {
|
||||
return this._getNotificationsForBrowser(this.tabbrowser.selectedBrowser);
|
||||
},
|
||||
set _currentNotifications(a) {
|
||||
return this.tabbrowser.selectedBrowser.popupNotifications = a;
|
||||
},
|
||||
|
||||
_remove: function PopupNotifications_removeHelper(notification) {
|
||||
// This notification may already be removed, in which case let's just fail
|
||||
// silently.
|
||||
let notifications = this._getNotificationsForBrowser(notification.browser);
|
||||
if (!notifications)
|
||||
return;
|
||||
|
||||
var index = notifications.indexOf(notification);
|
||||
if (index == -1)
|
||||
return;
|
||||
|
||||
// remove the notification
|
||||
notifications.splice(index, 1);
|
||||
},
|
||||
|
||||
/**
|
||||
* Hides the notification popup.
|
||||
*/
|
||||
_hidePanel: function PopupNotifications_hide() {
|
||||
this._ignoreDismissal = true;
|
||||
this.panel.hidePopup();
|
||||
this._ignoreDismissal = false;
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
_refreshPanel: function PopupNotifications_refreshPanel(notificationsToShow) {
|
||||
while (this.panel.lastChild)
|
||||
this.panel.removeChild(this.panel.lastChild);
|
||||
|
||||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
|
||||
notificationsToShow.forEach(function (n) {
|
||||
let doc = this.window.document;
|
||||
let popupnotification = doc.createElementNS(XUL_NS, "popupnotification");
|
||||
popupnotification.setAttribute("label", n.message);
|
||||
popupnotification.setAttribute("id", n.id);
|
||||
if (n.mainAction) {
|
||||
popupnotification.setAttribute("buttonlabel", n.mainAction.label);
|
||||
popupnotification.setAttribute("buttonaccesskey", n.mainAction.accessKey);
|
||||
popupnotification.setAttribute("buttoncommand", "PopupNotifications._onButtonCommand(event);");
|
||||
if (n.secondaryActions.length) {
|
||||
popupnotification.setAttribute("buttontype", "menu-button");
|
||||
popupnotification.setAttribute("menucommand", "PopupNotifications._onMenuCommand(event);");
|
||||
}
|
||||
}
|
||||
popupnotification.notification = n;
|
||||
|
||||
this.panel.appendChild(popupnotification);
|
||||
|
||||
if (n.secondaryActions) {
|
||||
n.secondaryActions.forEach(function (a) {
|
||||
let item = doc.createElementNS(XUL_NS, "menuitem");
|
||||
item.setAttribute("label", a.label);
|
||||
item.setAttribute("accesskey", a.accessKey);
|
||||
item.notification = n;
|
||||
item.action = a;
|
||||
|
||||
popupnotification.appendChild(item);
|
||||
}, this);
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
|
||||
_showPanel: function PopupNotifications_showPanel(notificationsToShow, anchorElement) {
|
||||
this.panel.hidden = false;
|
||||
|
||||
this._refreshPanel(notificationsToShow);
|
||||
|
||||
if (this.isPanelOpen && this._currentAnchorElement == anchorElement)
|
||||
return;
|
||||
|
||||
// Make sure the identity popup hangs in the correct direction.
|
||||
var position = (this.window.getComputedStyle(this.panel, "").direction == "rtl") ? "after_end" : "after_start";
|
||||
|
||||
this._currentAnchorElement = anchorElement;
|
||||
|
||||
this.panel.openPopup(anchorElement, position);
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the notification state in response to window activation or tab
|
||||
* selection changes.
|
||||
*/
|
||||
_update: function PopupNotifications_update(anchor) {
|
||||
let anchorElement, notificationsToShow = [];
|
||||
if (this._currentNotifications.length > 0) {
|
||||
if (this.iconBox)
|
||||
this.iconBox.hidden = false;
|
||||
|
||||
// Only show the notifications that have the passed-in anchor (or the
|
||||
// first notification's anchor, if none was passed in). Other
|
||||
// notifications will be shown once these are dismissed.
|
||||
anchorElement = anchor || this._currentNotifications[0].anchorElement;
|
||||
notificationsToShow = this._currentNotifications.filter(function (n) {
|
||||
return n.anchorElement == anchorElement;
|
||||
});
|
||||
}
|
||||
|
||||
if (notificationsToShow.length > 0) {
|
||||
this._showPanel(notificationsToShow, anchorElement);
|
||||
} else {
|
||||
// Notify observers that we're not showing the popup (useful for testing)
|
||||
this._notify("updateNotShowing");
|
||||
|
||||
this._hidePanel();
|
||||
|
||||
if (this.iconBox)
|
||||
this.iconBox.hidden = true;
|
||||
}
|
||||
},
|
||||
|
||||
_getNotificationsForBrowser: function PopupNotifications_getNotifications(browser) {
|
||||
if (browser.popupNotifications)
|
||||
return browser.popupNotifications;
|
||||
|
||||
return browser.popupNotifications = [];
|
||||
},
|
||||
|
||||
_onPopupHidden: function PopupNotifications_onPopupHidden(event) {
|
||||
if (event.target != this.panel || this._ignoreDismissal)
|
||||
return;
|
||||
|
||||
// Remove notifications being dismissed
|
||||
Array.forEach(this.panel.childNodes, function (nEl) {
|
||||
let notificationObj = nEl.notification;
|
||||
this._remove(notificationObj);
|
||||
}, this);
|
||||
|
||||
this._update();
|
||||
},
|
||||
|
||||
_onButtonCommand: function PopupNotifications_onButtonCommand(event) {
|
||||
// Need to find the associated notification object, which is a bit tricky
|
||||
// since it isn't associated with the button directly - this is kind of
|
||||
// gross and very dependent on the structure of the popupnotification
|
||||
// binding's content.
|
||||
let target = event.originalTarget;
|
||||
let notificationEl;
|
||||
let parent = target;
|
||||
while (parent && (parent = target.ownerDocument.getBindingParent(parent)))
|
||||
notificationEl = parent;
|
||||
|
||||
if (!notificationEl)
|
||||
throw "PopupNotifications_onButtonCommand: couldn't find notification element";
|
||||
|
||||
if (!notificationEl.notification)
|
||||
throw "PopupNotifications_onButtonCommand: couldn't find notification";
|
||||
|
||||
let notification = notificationEl.notification;
|
||||
notification.mainAction.callback.call();
|
||||
|
||||
this._remove(notification);
|
||||
this._update();
|
||||
},
|
||||
|
||||
_onMenuCommand: function PopupNotifications_onMenuCommand(event) {
|
||||
let target = event.originalTarget;
|
||||
if (!target.action || !target.notification)
|
||||
throw "menucommand target has no associated action/notification";
|
||||
|
||||
event.stopPropagation();
|
||||
target.action.callback.call();
|
||||
|
||||
this._remove(target.notification);
|
||||
this._update();
|
||||
},
|
||||
|
||||
_notify: function PopupNotifications_notify(topic) {
|
||||
Services.obs.notifyObservers(null, "PopupNotifications-" + topic, "");
|
||||
}
|
||||
}
|
@ -465,4 +465,34 @@
|
||||
</method>
|
||||
</implementation>
|
||||
</binding>
|
||||
|
||||
<binding id="popup-notification">
|
||||
<content>
|
||||
<xul:image class="popup-notification-icon"
|
||||
xbl:inherits="popupid=id"/>
|
||||
<xul:vbox>
|
||||
<xul:description class="popup-notification-description"
|
||||
xbl:inherits="value=label"/>
|
||||
<xul:spacer flex="1"/>
|
||||
<xul:hbox pack="end">
|
||||
<xul:button anonid="button"
|
||||
class="popup-notification-menubutton"
|
||||
xbl:inherits="oncommand=buttoncommand,label=buttonlabel,accesskey=buttonaccesskey,type=buttontype">
|
||||
<xul:menupopup anonid="menupopup"
|
||||
xbl:inherits="oncommand=menucommand">
|
||||
<children/>
|
||||
</xul:menupopup>
|
||||
</xul:button>
|
||||
</xul:hbox>
|
||||
</xul:vbox>
|
||||
</content>
|
||||
<implementation>
|
||||
<field name="button" readonly="true">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "button");
|
||||
</field>
|
||||
<field name="menupopup" readonly="true">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "menupopup");
|
||||
</field>
|
||||
</implementation>
|
||||
</binding>
|
||||
</bindings>
|
||||
|
@ -202,6 +202,15 @@ notification {
|
||||
-moz-binding: url("chrome://global/content/bindings/notification.xml#notification");
|
||||
}
|
||||
|
||||
/*********** popup notification ************/
|
||||
popupnotification {
|
||||
-moz-binding: url("chrome://global/content/bindings/notification.xml#popup-notification")
|
||||
}
|
||||
|
||||
.popup-notification-menubutton:not([label]) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/********** image **********/
|
||||
|
||||
image {
|
||||
|
@ -85,11 +85,3 @@ notification[type="critical"] .messageImage {
|
||||
-moz-box-shadow: 0 0 3px 1px -moz-mac-focusring,
|
||||
0 0 0 2px -moz-mac-focusring inset;
|
||||
}
|
||||
|
||||
/* geolocation adds a text-link for a "Learn more..." link.
|
||||
normal blue color of text-link is hard to read.
|
||||
*/
|
||||
notification[type="info"] .text-link {
|
||||
color: rgba(255,255,255,0.95) !important;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user