mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-28 23:31:56 +00:00
Bug 1297475 - Move content permission prompts into a JSM and add an Integration. r=Paolo
MozReview-Commit-ID: Dq3I9pzcdyY --HG-- extra : rebase_source : a5d5008631ba85762c3c265552f42e50266bd9b8
This commit is contained in:
parent
0e1c4d7b3a
commit
cb6b23a1c2
@ -37,6 +37,7 @@ XPCOMUtils.defineLazyServiceGetter(this, "AlertsService", "@mozilla.org/alerts-s
|
||||
["Feeds", "resource:///modules/Feeds.jsm"],
|
||||
["FileUtils", "resource://gre/modules/FileUtils.jsm"],
|
||||
["FormValidationHandler", "resource:///modules/FormValidationHandler.jsm"],
|
||||
["Integration", "resource://gre/modules/Integration.jsm"],
|
||||
["LightweightThemeManager", "resource://gre/modules/LightweightThemeManager.jsm"],
|
||||
["LoginHelper", "resource://gre/modules/LoginHelper.jsm"],
|
||||
["LoginManagerParent", "resource://gre/modules/LoginManagerParent.jsm"],
|
||||
@ -46,6 +47,7 @@ XPCOMUtils.defineLazyServiceGetter(this, "AlertsService", "@mozilla.org/alerts-s
|
||||
["OS", "resource://gre/modules/osfile.jsm"],
|
||||
["PageThumbs", "resource://gre/modules/PageThumbs.jsm"],
|
||||
["PdfJs", "resource://pdf.js/PdfJs.jsm"],
|
||||
["PermissionUI", "resource:///modules/PermissionUI.jsm"],
|
||||
["PlacesBackups", "resource://gre/modules/PlacesBackups.jsm"],
|
||||
["PlacesUtils", "resource://gre/modules/PlacesUtils.jsm"],
|
||||
["PluralForm", "resource://gre/modules/PluralForm.jsm"],
|
||||
@ -2431,6 +2433,49 @@ BrowserGlue.prototype = {
|
||||
_xpcom_factory: BrowserGlueServiceFactory,
|
||||
}
|
||||
|
||||
/**
|
||||
* ContentPermissionIntegration is responsible for showing the user
|
||||
* simple permission prompts when content requests additional
|
||||
* capabilities.
|
||||
*
|
||||
* While there are some built-in permission prompts, createPermissionPrompt
|
||||
* can also be overridden by system add-ons or tests to provide new ones.
|
||||
*
|
||||
* This override ability is provided by Integration.jsm. See
|
||||
* PermissionUI.jsm for an example of how to provide a new prompt
|
||||
* from an add-on.
|
||||
*/
|
||||
const ContentPermissionIntegration = {
|
||||
/**
|
||||
* Creates a PermissionPrompt for a given permission type and
|
||||
* nsIContentPermissionRequest.
|
||||
*
|
||||
* @param {string} type
|
||||
* The type of the permission request from content. This normally
|
||||
* matches the "type" field of an nsIContentPermissionType, but it
|
||||
* can be something else if the permission does not use the
|
||||
* nsIContentPermissionRequest model. Note that this type might also
|
||||
* be different from the permission key used in the permissions
|
||||
* database.
|
||||
* Example: "geolocation"
|
||||
* @param {nsIContentPermissionRequest} request
|
||||
* The request for a permission from content.
|
||||
* @return {PermissionPrompt} (see PermissionUI.jsm),
|
||||
* or undefined if the type cannot be handled.
|
||||
*/
|
||||
createPermissionPrompt(type, request) {
|
||||
switch (type) {
|
||||
case "geolocation": {
|
||||
return new PermissionUI.GeolocationPermissionPrompt(request);
|
||||
}
|
||||
case "desktop-notification": {
|
||||
return new PermissionUI.DesktopNotificationPermissionPrompt(request);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
};
|
||||
|
||||
function ContentPermissionPrompt() {}
|
||||
|
||||
ContentPermissionPrompt.prototype = {
|
||||
@ -2438,301 +2483,50 @@ ContentPermissionPrompt.prototype = {
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionPrompt]),
|
||||
|
||||
_getBrowserForRequest: function (aRequest) {
|
||||
// "element" is only defined in e10s mode.
|
||||
let browser = aRequest.element;
|
||||
if (!browser) {
|
||||
// Find the requesting browser.
|
||||
browser = aRequest.window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell)
|
||||
.chromeEventHandler;
|
||||
}
|
||||
return browser;
|
||||
},
|
||||
|
||||
/**
|
||||
* Show a permission prompt.
|
||||
* This implementation of nsIContentPermissionPrompt.prompt ensures
|
||||
* that there's only one nsIContentPermissionType in the request,
|
||||
* and that it's of type nsIContentPermissionType. Failing to
|
||||
* satisfy either of these conditions will result in this method
|
||||
* throwing NS_ERRORs. If the combined ContentPermissionIntegration
|
||||
* cannot construct a prompt for this particular request, an
|
||||
* NS_ERROR_FAILURE will be thrown.
|
||||
*
|
||||
* @param aRequest The permission request.
|
||||
* @param aMessage The message to display on the prompt.
|
||||
* @param aPermission The type of permission to prompt.
|
||||
* @param aActions An array of actions of the form:
|
||||
* [main action, secondary actions, ...]
|
||||
* Actions are of the form { stringId, action, expireType, callback }
|
||||
* Permission is granted if action is null or ALLOW_ACTION.
|
||||
* @param aNotificationId The id of the PopupNotification.
|
||||
* @param aAnchorId The id for the PopupNotification anchor.
|
||||
* @param aOptions Options for the PopupNotification
|
||||
* Any time an error is thrown, the nsIContentPermissionRequest is
|
||||
* cancelled automatically.
|
||||
*
|
||||
* @param {nsIContentPermissionRequest} request
|
||||
* The request that we're to show a prompt for.
|
||||
*/
|
||||
_showPrompt: function CPP_showPrompt(aRequest, aMessage, aPermission, aActions,
|
||||
aNotificationId, aAnchorId, aOptions) {
|
||||
var browser = this._getBrowserForRequest(aRequest);
|
||||
var chromeWin = browser.ownerGlobal;
|
||||
var requestPrincipal = aRequest.principal;
|
||||
|
||||
// Transform the prompt actions into PopupNotification actions.
|
||||
var popupNotificationActions = [];
|
||||
for (var i = 0; i < aActions.length; i++) {
|
||||
let promptAction = aActions[i];
|
||||
|
||||
// Don't offer action in PB mode if the action remembers permission for more than a session.
|
||||
if (PrivateBrowsingUtils.isWindowPrivate(chromeWin) &&
|
||||
promptAction.expireType != Ci.nsIPermissionManager.EXPIRE_SESSION &&
|
||||
promptAction.action) {
|
||||
continue;
|
||||
prompt(request) {
|
||||
try {
|
||||
// Only allow exactly one permission request here.
|
||||
let types = request.types.QueryInterface(Ci.nsIArray);
|
||||
if (types.length != 1) {
|
||||
throw Components.Exception(
|
||||
"Expected an nsIContentPermissionRequest with only 1 type.",
|
||||
Cr.NS_ERROR_UNEXPECTED);
|
||||
}
|
||||
|
||||
var action = {
|
||||
label: gBrowserBundle.GetStringFromName(promptAction.stringId),
|
||||
accessKey: gBrowserBundle.GetStringFromName(promptAction.stringId + ".accesskey"),
|
||||
callback: function() {
|
||||
if (promptAction.callback) {
|
||||
promptAction.callback();
|
||||
}
|
||||
let type = types.queryElementAt(0, Ci.nsIContentPermissionType).type;
|
||||
let combinedIntegration =
|
||||
Integration.contentPermission.getCombined(ContentPermissionIntegration);
|
||||
|
||||
// Remember permissions.
|
||||
if (promptAction.action) {
|
||||
Services.perms.addFromPrincipal(requestPrincipal, aPermission,
|
||||
promptAction.action, promptAction.expireType);
|
||||
}
|
||||
|
||||
// Grant permission if action is null or ALLOW_ACTION.
|
||||
if (!promptAction.action || promptAction.action == Ci.nsIPermissionManager.ALLOW_ACTION) {
|
||||
aRequest.allow();
|
||||
} else {
|
||||
aRequest.cancel();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
popupNotificationActions.push(action);
|
||||
}
|
||||
|
||||
var mainAction = popupNotificationActions.length ?
|
||||
popupNotificationActions[0] : null;
|
||||
var secondaryActions = popupNotificationActions.splice(1);
|
||||
|
||||
// Only allow exactly one permission request here.
|
||||
let types = aRequest.types.QueryInterface(Ci.nsIArray);
|
||||
if (types.length != 1) {
|
||||
aRequest.cancel();
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!aOptions)
|
||||
aOptions = {};
|
||||
aOptions.displayURI = requestPrincipal.URI;
|
||||
|
||||
return chromeWin.PopupNotifications.show(browser, aNotificationId, aMessage, aAnchorId,
|
||||
mainAction, secondaryActions, aOptions);
|
||||
},
|
||||
|
||||
_promptGeo : function(aRequest) {
|
||||
var secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
|
||||
|
||||
var message;
|
||||
|
||||
// Share location action.
|
||||
var actions = [{
|
||||
stringId: "geolocation.shareLocation",
|
||||
action: null,
|
||||
expireType: null,
|
||||
callback: function() {
|
||||
secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_GEOLOCATION_REQUEST_SHARE_LOCATION);
|
||||
},
|
||||
}];
|
||||
|
||||
let options = {
|
||||
learnMoreURL: Services.urlFormatter.formatURLPref("browser.geolocation.warning.infoURL"),
|
||||
};
|
||||
|
||||
if (aRequest.principal.URI.schemeIs("file")) {
|
||||
message = gBrowserBundle.GetStringFromName("geolocation.shareWithFile2");
|
||||
} else {
|
||||
message = gBrowserBundle.GetStringFromName("geolocation.shareWithSite2");
|
||||
// Always share location action.
|
||||
actions.push({
|
||||
stringId: "geolocation.alwaysShareLocation",
|
||||
action: Ci.nsIPermissionManager.ALLOW_ACTION,
|
||||
expireType: null,
|
||||
callback: function() {
|
||||
secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_GEOLOCATION_REQUEST_ALWAYS_SHARE);
|
||||
},
|
||||
});
|
||||
|
||||
// Never share location action.
|
||||
actions.push({
|
||||
stringId: "geolocation.neverShareLocation",
|
||||
action: Ci.nsIPermissionManager.DENY_ACTION,
|
||||
expireType: null,
|
||||
callback: function() {
|
||||
secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_GEOLOCATION_REQUEST_NEVER_SHARE);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_GEOLOCATION_REQUEST);
|
||||
|
||||
this._showPrompt(aRequest, message, "geo", actions, "geolocation",
|
||||
"geo-notification-icon", options);
|
||||
},
|
||||
|
||||
_promptFlyWebPublishServer : function(aRequest) {
|
||||
var message = "Would you like to let this site start a server accessible to nearby devices and people?";
|
||||
var actions = [
|
||||
{
|
||||
stringId: "flyWebPublishServer.allowPublishServer",
|
||||
action: Ci.nsIPermissionManager.ALLOW_ACTION,
|
||||
expireType: Ci.nsIPermissionManager.EXPIRE_SESSION
|
||||
},
|
||||
{
|
||||
stringId: "flyWebPublishServer.denyPublishServer",
|
||||
action: Ci.nsIPermissionManager.DENY_ACTION,
|
||||
expireType: Ci.nsIPermissionManager.EXPIRE_SESSION
|
||||
let permissionPrompt =
|
||||
combinedIntegration.createPermissionPrompt(type, request);
|
||||
if (!permissionPrompt) {
|
||||
throw Components.Exception(
|
||||
`Failed to handle permission of type ${type}`,
|
||||
Cr.NS_ERROR_FAILURE);
|
||||
}
|
||||
];
|
||||
|
||||
let options = {
|
||||
learnMoreURL: "https://flyweb.github.io",
|
||||
popupIconURL: "chrome://flyweb/skin/icon-64.png"
|
||||
};
|
||||
|
||||
let browser = this._getBrowserForRequest(aRequest);
|
||||
let chromeDoc = browser.ownerDocument;
|
||||
let iconElem = chromeDoc.getElementById("flyweb-publish-server-notification-icon");
|
||||
if (!iconElem) {
|
||||
let notificationPopupBox = chromeDoc.getElementById("notification-popup-box");
|
||||
let notificationIcon = chromeDoc.createElement("image");
|
||||
notificationIcon.setAttribute("id", "flyweb-publish-server-notification-icon");
|
||||
notificationIcon.setAttribute("src", "chrome://flyweb/skin/icon-64.png");
|
||||
notificationIcon.setAttribute("class", "notification-anchor-icon flyweb-publish-server-icon");
|
||||
notificationIcon.setAttribute("style", "filter: url(chrome://browser/skin/filters.svg#fill); fill: currentColor; opacity: .4;");
|
||||
notificationIcon.setAttribute("role", "button");
|
||||
notificationIcon.setAttribute("aria-label", "View the publish-server request");
|
||||
notificationPopupBox.appendChild(notificationIcon);
|
||||
}
|
||||
|
||||
this._showPrompt(aRequest, message, "flyweb-publish-server", actions, "flyweb-publish-server",
|
||||
"flyweb-publish-server-notification-icon", options);
|
||||
},
|
||||
|
||||
_promptWebNotifications : function(aRequest) {
|
||||
var message = gBrowserBundle.GetStringFromName("webNotifications.receiveFromSite");
|
||||
|
||||
var actions;
|
||||
|
||||
var browser = this._getBrowserForRequest(aRequest);
|
||||
// Only show "allow for session" in PB mode, we don't
|
||||
// support "allow for session" in non-PB mode.
|
||||
if (PrivateBrowsingUtils.isBrowserPrivate(browser)) {
|
||||
actions = [
|
||||
{
|
||||
stringId: "webNotifications.receiveForSession",
|
||||
action: Ci.nsIPermissionManager.ALLOW_ACTION,
|
||||
expireType: Ci.nsIPermissionManager.EXPIRE_SESSION,
|
||||
callback: function() {},
|
||||
}
|
||||
];
|
||||
} else {
|
||||
actions = [
|
||||
{
|
||||
stringId: "webNotifications.alwaysReceive",
|
||||
action: Ci.nsIPermissionManager.ALLOW_ACTION,
|
||||
expireType: null,
|
||||
callback: function() {},
|
||||
},
|
||||
{
|
||||
stringId: "webNotifications.neverShow",
|
||||
action: Ci.nsIPermissionManager.DENY_ACTION,
|
||||
expireType: null,
|
||||
callback: function() {},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
var options = {
|
||||
learnMoreURL:
|
||||
Services.urlFormatter.formatURLPref("app.support.baseURL") + "push",
|
||||
eventCallback(type) {
|
||||
if (type == "dismissed") {
|
||||
// Bug 1259148: Hide the doorhanger icon. Unlike other permission
|
||||
// doorhangers, the user can't restore the doorhanger using the icon
|
||||
// in the location bar. Instead, the site will be notified that the
|
||||
// doorhanger was dismissed.
|
||||
this.remove();
|
||||
aRequest.cancel();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
this._showPrompt(aRequest, message, "desktop-notification", actions,
|
||||
"web-notifications",
|
||||
"web-notifications-notification-icon", options);
|
||||
},
|
||||
|
||||
prompt: function CPP_prompt(request) {
|
||||
// Only allow exactly one permission request here.
|
||||
let types = request.types.QueryInterface(Ci.nsIArray);
|
||||
if (types.length != 1) {
|
||||
permissionPrompt.prompt();
|
||||
} catch (ex) {
|
||||
Cu.reportError(ex);
|
||||
request.cancel();
|
||||
return;
|
||||
}
|
||||
let perm = types.queryElementAt(0, Ci.nsIContentPermissionType);
|
||||
|
||||
const kFeatureKeys = { "geolocation" : "geo",
|
||||
"desktop-notification" : "desktop-notification",
|
||||
"flyweb-publish-server": "flyweb-publish-server"
|
||||
};
|
||||
|
||||
// Make sure that we support the request.
|
||||
if (!(perm.type in kFeatureKeys)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var requestingPrincipal = request.principal;
|
||||
var requestingURI = requestingPrincipal.URI;
|
||||
|
||||
// Ignore requests from non-nsIStandardURLs
|
||||
if (!(requestingURI instanceof Ci.nsIStandardURL))
|
||||
return;
|
||||
|
||||
var permissionKey = kFeatureKeys[perm.type];
|
||||
var result = Services.perms.testExactPermissionFromPrincipal(requestingPrincipal, permissionKey);
|
||||
|
||||
if (result == Ci.nsIPermissionManager.DENY_ACTION) {
|
||||
request.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == Ci.nsIPermissionManager.ALLOW_ACTION) {
|
||||
request.allow();
|
||||
return;
|
||||
}
|
||||
|
||||
var browser = this._getBrowserForRequest(request);
|
||||
var chromeWin = browser.ownerGlobal;
|
||||
if (!chromeWin.PopupNotifications)
|
||||
// Ignore requests from browsers hosted in windows that don't support
|
||||
// PopupNotifications.
|
||||
return;
|
||||
|
||||
// Show the prompt.
|
||||
switch (perm.type) {
|
||||
case "geolocation":
|
||||
this._promptGeo(request);
|
||||
break;
|
||||
case "desktop-notification":
|
||||
this._promptWebNotifications(request);
|
||||
break;
|
||||
case "flyweb-publish-server":
|
||||
if (AppConstants.NIGHTLY_BUILD) {
|
||||
this._promptFlyWebPublishServer(request);
|
||||
}
|
||||
break;
|
||||
throw ex;
|
||||
}
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
var DefaultBrowserCheck = {
|
||||
|
588
browser/modules/PermissionUI.jsm
Normal file
588
browser/modules/PermissionUI.jsm
Normal file
@ -0,0 +1,588 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = [
|
||||
"PermissionUI",
|
||||
];
|
||||
|
||||
/**
|
||||
* PermissionUI is responsible for exposing both a prototype
|
||||
* PermissionPrompt that can be used by arbitrary browser
|
||||
* components and add-ons, but also hosts the implementations of
|
||||
* built-in permission prompts.
|
||||
*
|
||||
* If you're developing a feature that requires web content to ask
|
||||
* for special permissions from the user, this module is for you.
|
||||
*
|
||||
* Suppose a system add-on wants to add a new prompt for a new request
|
||||
* for getting more low-level access to the user's sound card, and the
|
||||
* permission request is coming up from content by way of the
|
||||
* nsContentPermissionHelper. The system add-on could then do the following:
|
||||
*
|
||||
* Cu.import("resource://gre/modules/Integration.jsm");
|
||||
* Cu.import("resource:///modules/PermissionUI.jsm");
|
||||
*
|
||||
* const SoundCardIntegration = (base) => ({
|
||||
* __proto__: base,
|
||||
* createPermissionPrompt(type, request) {
|
||||
* if (type != "sound-api") {
|
||||
* return super.createPermissionPrompt(...arguments);
|
||||
* }
|
||||
*
|
||||
* return {
|
||||
* __proto__: PermissionUI.PermissionPromptForRequestPrototype,
|
||||
* get permissionKey() {
|
||||
* return "sound-permission";
|
||||
* }
|
||||
* // etc - see the documentation for PermissionPrompt for
|
||||
* // a better idea of what things one can and should override.
|
||||
* }
|
||||
* },
|
||||
* });
|
||||
*
|
||||
* // Add-on startup:
|
||||
* Integration.contentPermission.register(SoundCardIntegration);
|
||||
* // ...
|
||||
* // Add-on shutdown:
|
||||
* Integration.contentPermission.unregister(SoundCardIntegration);
|
||||
*
|
||||
* Note that PermissionPromptForRequestPrototype must be used as the
|
||||
* prototype, since the prompt is wrapping an nsIContentPermissionRequest,
|
||||
* and going through nsIContentPermissionPrompt.
|
||||
*
|
||||
* It is, however, possible to take advantage of PermissionPrompt without
|
||||
* having to go through nsIContentPermissionPrompt or with a
|
||||
* nsIContentPermissionRequest. The PermissionPromptPrototype can be
|
||||
* imported, subclassed, and have prompt() called directly, without
|
||||
* the caller having called into createPermissionPrompt.
|
||||
*/
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Services",
|
||||
"resource://gre/modules/Services.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
|
||||
"resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "gBrandBundle", function() {
|
||||
return Services.strings
|
||||
.createBundle('chrome://branding/locale/brand.properties');
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() {
|
||||
return Services.strings
|
||||
.createBundle('chrome://browser/locale/browser.properties');
|
||||
});
|
||||
|
||||
this.PermissionUI = {};
|
||||
|
||||
/**
|
||||
* PermissionPromptPrototype should be subclassed by callers that
|
||||
* want to display prompts to the user. See each method and property
|
||||
* below for guidance on what to override.
|
||||
*
|
||||
* Note that if you're creating a prompt for an
|
||||
* nsIContentPermissionRequest, you'll want to subclass
|
||||
* PermissionPromptForRequestPrototype instead.
|
||||
*/
|
||||
this.PermissionPromptPrototype = {
|
||||
/**
|
||||
* Returns the associated <xul:browser> for the request. This should
|
||||
* work for the e10s and non-e10s case.
|
||||
*
|
||||
* Subclasses must override this.
|
||||
*
|
||||
* @return {<xul:browser>}
|
||||
*/
|
||||
get browser() {
|
||||
throw new Error("Not implemented.");
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the nsIPrincipal associated with the request.
|
||||
*
|
||||
* Subclasses must override this.
|
||||
*
|
||||
* @return {nsIPrincipal}
|
||||
*/
|
||||
get principal() {
|
||||
throw new Error("Not implemented.");
|
||||
},
|
||||
|
||||
/**
|
||||
* If the nsIPermissionManager is being queried and written
|
||||
* to for this permission request, set this to the key to be
|
||||
* used. If this is undefined, user permissions will not be
|
||||
* read from or written to.
|
||||
*
|
||||
* Note that if a permission is set, in any follow-up
|
||||
* prompting within the expiry window of that permission,
|
||||
* the prompt will be skipped and the allow or deny choice
|
||||
* will be selected automatically.
|
||||
*/
|
||||
get permissionKey() {
|
||||
return undefined;
|
||||
},
|
||||
|
||||
/**
|
||||
* These are the options that will be passed to the
|
||||
* PopupNotification when it is shown. See the documentation
|
||||
* for PopupNotification for more details.
|
||||
*
|
||||
* Note that prompt() will automatically set displayURI to
|
||||
* be the URI of the requesting pricipal.
|
||||
*/
|
||||
get popupOptions() {
|
||||
return {};
|
||||
},
|
||||
|
||||
/**
|
||||
* PopupNotification requires a unique ID to open the notification.
|
||||
* You must return a unique ID string here, for which PopupNotification
|
||||
* will then create a <xul:popupnotification> node with the ID
|
||||
* "<notificationID>-notification".
|
||||
*
|
||||
* If there's a custom <xul:popupnotification> you're hoping to show,
|
||||
* then you need to make sure its ID has the "-notification" suffix,
|
||||
* and then return the prefix here.
|
||||
*
|
||||
* See PopupNotification.jsm for more details.
|
||||
*
|
||||
* @return {string}
|
||||
* The unique ID that will be used to as the
|
||||
* "<unique ID>-notification" ID for the <xul:popupnotification>
|
||||
* to use or create.
|
||||
*/
|
||||
get notificationID() {
|
||||
throw new Error("Not implemented.");
|
||||
},
|
||||
|
||||
/**
|
||||
* The ID of the element to anchor the PopupNotification to.
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
get anchorID() {
|
||||
return "default-notification-icon";
|
||||
},
|
||||
|
||||
/**
|
||||
* The message to show the user in the PopupNotification. This
|
||||
* is usually a string describing the permission that is being
|
||||
* requested.
|
||||
*
|
||||
* Subclasses must override this.
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
get message() {
|
||||
throw new Error("Not implemented.");
|
||||
},
|
||||
|
||||
/**
|
||||
* This will be called if the request is to be cancelled.
|
||||
*
|
||||
* Subclasses only need to override this if they provide a
|
||||
* permissionKey.
|
||||
*/
|
||||
cancel() {
|
||||
throw new Error("Not implemented.")
|
||||
},
|
||||
|
||||
/**
|
||||
* This will be called if the request is to be allowed.
|
||||
*
|
||||
* Subclasses only need to override this if they provide a
|
||||
* permissionKey.
|
||||
*/
|
||||
allow() {
|
||||
throw new Error("Not implemented.");
|
||||
},
|
||||
|
||||
/**
|
||||
* The actions that will be displayed in the PopupNotification
|
||||
* via a dropdown menu. The first item in this array will be
|
||||
* the default selection. Each action is an Object with the
|
||||
* following properties:
|
||||
*
|
||||
* label (string):
|
||||
* The label that will be displayed for this choice.
|
||||
* accessKey (string):
|
||||
* The access key character that will be used for this choice.
|
||||
* action (Ci.nsIPermissionManager action, optional)
|
||||
* The nsIPermissionManager action that will be associated with
|
||||
* this choice. For example, Ci.nsIPermissionManager.DENY_ACTION.
|
||||
*
|
||||
* If omitted, the nsIPermissionManager will not be written to
|
||||
* when this choice is chosen.
|
||||
* expireType (Ci.nsIPermissionManager expiration policy, optional)
|
||||
* The nsIPermissionManager expiration policy that will be associated
|
||||
* with this choice. For example, Ci.nsIPermissionManager.EXPIRE_SESSION.
|
||||
*
|
||||
* If action is not set, expireType will be ignored.
|
||||
* callback (function, optional)
|
||||
* A callback function that will fire if the user makes this choice.
|
||||
*/
|
||||
get promptActions() {
|
||||
return [];
|
||||
},
|
||||
|
||||
/**
|
||||
* If the prompt will be shown to the user, this callback will
|
||||
* be called just before. Subclasses may want to override this
|
||||
* in order to, for example, bump a counter Telemetry probe for
|
||||
* how often a particular permission request is seen.
|
||||
*/
|
||||
onBeforeShow() {},
|
||||
|
||||
/**
|
||||
* Will determine if a prompt should be shown to the user, and if so,
|
||||
* will show it.
|
||||
*
|
||||
* If a permissionKey is defined prompt() might automatically
|
||||
* allow or cancel itself based on the user's current
|
||||
* permission settings without displaying the prompt.
|
||||
*
|
||||
* If the <xul:browser> that the request is associated with
|
||||
* does not belong to a browser window with the PopupNotifications
|
||||
* global set, the prompt request is ignored.
|
||||
*/
|
||||
prompt() {
|
||||
let chromeWin = this.browser.ownerGlobal;
|
||||
if (!chromeWin.PopupNotifications) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We ignore requests from non-nsIStandardURLs
|
||||
let requestingURI = this.principal.URI;
|
||||
if (!(requestingURI instanceof Ci.nsIStandardURL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.permissionKey) {
|
||||
// If we're reading and setting permissions, then we need
|
||||
// to check to see if we already have a permission setting
|
||||
// for this particular principal.
|
||||
let result =
|
||||
Services.perms.testExactPermissionFromPrincipal(this.principal,
|
||||
this.permissionKey);
|
||||
|
||||
if (result == Ci.nsIPermissionManager.DENY_ACTION) {
|
||||
this.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == Ci.nsIPermissionManager.ALLOW_ACTION) {
|
||||
this.allow();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Transform the PermissionPrompt actions into PopupNotification actions.
|
||||
let popupNotificationActions = [];
|
||||
for (let promptAction of this.promptActions) {
|
||||
// Don't offer action in PB mode if the action remembers permission
|
||||
// for more than a session.
|
||||
if (PrivateBrowsingUtils.isWindowPrivate(chromeWin) &&
|
||||
promptAction.expireType != Ci.nsIPermissionManager.EXPIRE_SESSION &&
|
||||
promptAction.action) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let action = {
|
||||
label: promptAction.label,
|
||||
accessKey: promptAction.accessKey,
|
||||
callback: () => {
|
||||
if (promptAction.callback) {
|
||||
promptAction.callback();
|
||||
}
|
||||
|
||||
if (this.permissionKey) {
|
||||
// Remember permissions.
|
||||
if (promptAction.action) {
|
||||
Services.perms.addFromPrincipal(this.principal,
|
||||
this.permissionKey,
|
||||
promptAction.action,
|
||||
promptAction.expireType);
|
||||
}
|
||||
|
||||
// Grant permission if action is null or ALLOW_ACTION.
|
||||
if (!promptAction.action ||
|
||||
promptAction.action == Ci.nsIPermissionManager.ALLOW_ACTION) {
|
||||
this.allow();
|
||||
} else {
|
||||
this.cancel();
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
popupNotificationActions.push(action);
|
||||
}
|
||||
|
||||
let mainAction = popupNotificationActions.length ?
|
||||
popupNotificationActions[0] : null;
|
||||
let secondaryActions = popupNotificationActions.splice(1);
|
||||
|
||||
let options = this.popupOptions;
|
||||
options.displayURI = this.principal.URI;
|
||||
|
||||
this.onBeforeShow();
|
||||
chromeWin.PopupNotifications.show(this.browser,
|
||||
this.notificationID,
|
||||
this.message,
|
||||
this.anchorID,
|
||||
mainAction,
|
||||
secondaryActions,
|
||||
options);
|
||||
},
|
||||
};
|
||||
|
||||
PermissionUI.PermissionPromptPrototype = PermissionPromptPrototype;
|
||||
|
||||
/**
|
||||
* A subclass of PermissionPromptPrototype that assumes
|
||||
* that this.request is an nsIContentPermissionRequest
|
||||
* and fills in some of the required properties on the
|
||||
* PermissionPrompt. For callers that are wrapping an
|
||||
* nsIContentPermissionRequest, this should be subclassed
|
||||
* rather than PermissionPromptPrototype.
|
||||
*/
|
||||
this.PermissionPromptForRequestPrototype = {
|
||||
__proto__: PermissionPromptPrototype,
|
||||
|
||||
get browser() {
|
||||
// In the e10s-case, the <xul:browser> will be at request.element.
|
||||
// In the single-process case, we have to use some XPCOM incantations
|
||||
// to resolve to the <xul:browser>.
|
||||
if (this.request.element) {
|
||||
return this.request.element;
|
||||
}
|
||||
return this.request
|
||||
.window
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell)
|
||||
.chromeEventHandler;
|
||||
},
|
||||
|
||||
get principal() {
|
||||
return this.request.principal;
|
||||
},
|
||||
|
||||
cancel() {
|
||||
this.request.cancel();
|
||||
},
|
||||
|
||||
allow() {
|
||||
this.request.allow();
|
||||
},
|
||||
};
|
||||
|
||||
PermissionUI.PermissionPromptForRequestPrototype =
|
||||
PermissionPromptForRequestPrototype;
|
||||
|
||||
/**
|
||||
* Creates a PermissionPrompt for a nsIContentPermissionRequest for
|
||||
* the GeoLocation API.
|
||||
*
|
||||
* @param request (nsIContentPermissionRequest)
|
||||
* The request for a permission from content.
|
||||
*/
|
||||
function GeolocationPermissionPrompt(request) {
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
GeolocationPermissionPrompt.prototype = {
|
||||
__proto__: PermissionPromptForRequestPrototype,
|
||||
|
||||
get permissionKey() {
|
||||
return "geo";
|
||||
},
|
||||
|
||||
get popupOptions() {
|
||||
let pref = "browser.geolocation.warning.infoURL";
|
||||
return {
|
||||
learnMoreURL: Services.urlFormatter.formatURLPref(pref),
|
||||
};
|
||||
},
|
||||
|
||||
get notificationID() {
|
||||
return "geolocation";
|
||||
},
|
||||
|
||||
get anchorID() {
|
||||
return "geo-notification-icon";
|
||||
},
|
||||
|
||||
get message() {
|
||||
let message;
|
||||
if (this.principal.URI.schemeIs("file")) {
|
||||
message = gBrowserBundle.GetStringFromName("geolocation.shareWithFile2");
|
||||
} else {
|
||||
message = gBrowserBundle.GetStringFromName("geolocation.shareWithSite2");
|
||||
}
|
||||
return message;
|
||||
},
|
||||
|
||||
get promptActions() {
|
||||
// We collect Telemetry data on Geolocation prompts and how users
|
||||
// respond to them. The probe keys are a bit verbose, so let's alias them.
|
||||
const SHARE_LOCATION =
|
||||
Ci.nsISecurityUITelemetry.WARNING_GEOLOCATION_REQUEST_SHARE_LOCATION;
|
||||
const ALWAYS_SHARE =
|
||||
Ci.nsISecurityUITelemetry.WARNING_GEOLOCATION_REQUEST_ALWAYS_SHARE;
|
||||
const NEVER_SHARE =
|
||||
Ci.nsISecurityUITelemetry.WARNING_GEOLOCATION_REQUEST_NEVER_SHARE;
|
||||
|
||||
let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
|
||||
|
||||
let actions = [{
|
||||
label: gBrowserBundle.GetStringFromName("geolocation.shareLocation"),
|
||||
accessKey:
|
||||
gBrowserBundle.GetStringFromName("geolocation.shareLocation.accesskey"),
|
||||
action: null,
|
||||
expireType: null,
|
||||
callback: function() {
|
||||
secHistogram.add(SHARE_LOCATION);
|
||||
},
|
||||
}];
|
||||
|
||||
if (!this.principal.URI.schemeIs("file")) {
|
||||
// Always share location action.
|
||||
actions.push({
|
||||
label: gBrowserBundle.GetStringFromName("geolocation.alwaysShareLocation"),
|
||||
accessKey:
|
||||
gBrowserBundle.GetStringFromName("geolocation.alwaysShareLocation.accesskey"),
|
||||
action: Ci.nsIPermissionManager.ALLOW_ACTION,
|
||||
expireType: null,
|
||||
callback: function() {
|
||||
secHistogram.add(ALWAYS_SHARE);
|
||||
},
|
||||
});
|
||||
|
||||
// Never share location action.
|
||||
actions.push({
|
||||
label: gBrowserBundle.GetStringFromName("geolocation.neverShareLocation"),
|
||||
accessKey:
|
||||
gBrowserBundle.GetStringFromName("geolocation.neverShareLocation.accesskey"),
|
||||
action: Ci.nsIPermissionManager.DENY_ACTION,
|
||||
expireType: null,
|
||||
callback: function() {
|
||||
secHistogram.add(NEVER_SHARE);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return actions;
|
||||
},
|
||||
|
||||
onBeforeShow() {
|
||||
let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
|
||||
const SHOW_REQUEST = Ci.nsISecurityUITelemetry.WARNING_GEOLOCATION_REQUEST;
|
||||
secHistogram.add(SHOW_REQUEST);
|
||||
},
|
||||
};
|
||||
|
||||
PermissionUI.GeolocationPermissionPrompt = GeolocationPermissionPrompt;
|
||||
|
||||
/**
|
||||
* Creates a PermissionPrompt for a nsIContentPermissionRequest for
|
||||
* the Desktop Notification API.
|
||||
*
|
||||
* @param request (nsIContentPermissionRequest)
|
||||
* The request for a permission from content.
|
||||
* @return {PermissionPrompt} (see documentation in header)
|
||||
*/
|
||||
function DesktopNotificationPermissionPrompt(request) {
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
DesktopNotificationPermissionPrompt.prototype = {
|
||||
__proto__: PermissionPromptForRequestPrototype,
|
||||
|
||||
get permissionKey() {
|
||||
return "desktop-notification";
|
||||
},
|
||||
|
||||
get popupOptions() {
|
||||
let learnMoreURL =
|
||||
Services.urlFormatter.formatURLPref("app.support.baseURL") + "push";
|
||||
|
||||
// The eventCallback is bound to the Notification that's being
|
||||
// shown. We'll stash a reference to this in the closure so that
|
||||
// the request can be cancelled.
|
||||
let prompt = this;
|
||||
|
||||
let eventCallback = function(type) {
|
||||
if (type == "dismissed") {
|
||||
// Bug 1259148: Hide the doorhanger icon. Unlike other permission
|
||||
// doorhangers, the user can't restore the doorhanger using the icon
|
||||
// in the location bar. Instead, the site will be notified that the
|
||||
// doorhanger was dismissed.
|
||||
this.remove();
|
||||
prompt.request.cancel();
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
learnMoreURL,
|
||||
eventCallback,
|
||||
};
|
||||
},
|
||||
|
||||
get notificationID() {
|
||||
return "web-notifications";
|
||||
},
|
||||
|
||||
get anchorID() {
|
||||
return "web-notifications-notification-icon";
|
||||
},
|
||||
|
||||
get message() {
|
||||
return gBrowserBundle.GetStringFromName("webNotifications.receiveFromSite");
|
||||
},
|
||||
|
||||
get promptActions() {
|
||||
let promptActions;
|
||||
// Only show "allow for session" in PB mode, we don't
|
||||
// support "allow for session" in non-PB mode.
|
||||
if (PrivateBrowsingUtils.isBrowserPrivate(this.browser)) {
|
||||
promptActions = [
|
||||
{
|
||||
label: gBrowserBundle.GetStringFromName("webNotifications.receiveForSession"),
|
||||
accessKey:
|
||||
gBrowserBundle.GetStringFromName("webNotifications.receiveForSession.accesskey"),
|
||||
action: Ci.nsIPermissionManager.ALLOW_ACTION,
|
||||
expireType: Ci.nsIPermissionManager.EXPIRE_SESSION,
|
||||
}
|
||||
];
|
||||
} else {
|
||||
promptActions = [
|
||||
{
|
||||
label: gBrowserBundle.GetStringFromName("webNotifications.alwaysReceive"),
|
||||
accessKey:
|
||||
gBrowserBundle.GetStringFromName("webNotifications.alwaysReceive.accesskey"),
|
||||
action: Ci.nsIPermissionManager.ALLOW_ACTION,
|
||||
expireType: null,
|
||||
},
|
||||
{
|
||||
label: gBrowserBundle.GetStringFromName("webNotifications.neverShow"),
|
||||
accessKey:
|
||||
gBrowserBundle.GetStringFromName("webNotifications.neverShow.accesskey"),
|
||||
action: Ci.nsIPermissionManager.DENY_ACTION,
|
||||
expireType: null,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return promptActions;
|
||||
},
|
||||
};
|
||||
|
||||
PermissionUI.DesktopNotificationPermissionPrompt =
|
||||
DesktopNotificationPermissionPrompt;
|
@ -34,6 +34,7 @@ EXTRA_JS_MODULES += [
|
||||
'NetworkPrioritizer.jsm',
|
||||
'offlineAppCache.jsm',
|
||||
'PanelFrame.jsm',
|
||||
'PermissionUI.jsm',
|
||||
'PluginContent.jsm',
|
||||
'ProcessHangMonitor.jsm',
|
||||
'ReaderParent.jsm',
|
||||
|
Loading…
Reference in New Issue
Block a user