Bug 398776: add new popup-based notification system, and use it for the geolocation notification, r=dolske, r=dao

This commit is contained in:
Gavin Sharp 2010-04-09 13:45:25 -04:00
Родитель 5c5bcd6aa8
Коммит 49e5b3ee9e
21 изменённых файлов: 1101 добавлений и 238 удалений

Просмотреть файл

@ -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 \

Просмотреть файл

@ -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 = \

Просмотреть файл

@ -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;
}