b2g/components/ContentPermissionPrompt.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
michael@0 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 "use strict"
michael@0 6
michael@0 7 function debug(str) {
michael@0 8 //dump("-*- ContentPermissionPrompt: " + str + "\n");
michael@0 9 }
michael@0 10
michael@0 11 const Ci = Components.interfaces;
michael@0 12 const Cr = Components.results;
michael@0 13 const Cu = Components.utils;
michael@0 14 const Cc = Components.classes;
michael@0 15
michael@0 16 const PROMPT_FOR_UNKNOWN = ["audio-capture",
michael@0 17 "desktop-notification",
michael@0 18 "geolocation",
michael@0 19 "video-capture"];
michael@0 20 // Due to privary issue, permission requests like GetUserMedia should prompt
michael@0 21 // every time instead of providing session persistence.
michael@0 22 const PERMISSION_NO_SESSION = ["audio-capture", "video-capture"];
michael@0 23 const ALLOW_MULTIPLE_REQUESTS = ["audio-capture", "video-capture"];
michael@0 24
michael@0 25 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 26 Cu.import("resource://gre/modules/Services.jsm");
michael@0 27 Cu.import("resource://gre/modules/Webapps.jsm");
michael@0 28 Cu.import("resource://gre/modules/AppsUtils.jsm");
michael@0 29 Cu.import("resource://gre/modules/PermissionsInstaller.jsm");
michael@0 30 Cu.import("resource://gre/modules/PermissionsTable.jsm");
michael@0 31
michael@0 32 var permissionManager = Cc["@mozilla.org/permissionmanager;1"].getService(Ci.nsIPermissionManager);
michael@0 33 var secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(Ci.nsIScriptSecurityManager);
michael@0 34
michael@0 35 let permissionSpecificChecker = {};
michael@0 36
michael@0 37 XPCOMUtils.defineLazyServiceGetter(this,
michael@0 38 "AudioManager",
michael@0 39 "@mozilla.org/telephony/audiomanager;1",
michael@0 40 "nsIAudioManager");
michael@0 41
michael@0 42 XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
michael@0 43 "resource://gre/modules/SystemAppProxy.jsm");
michael@0 44
michael@0 45 /**
michael@0 46 * aTypesInfo is an array of {permission, access, action, deny} which keeps
michael@0 47 * the information of each permission. This arrary is initialized in
michael@0 48 * ContentPermissionPrompt.prompt and used among functions.
michael@0 49 *
michael@0 50 * aTypesInfo[].permission : permission name
michael@0 51 * aTypesInfo[].access : permission name + request.access
michael@0 52 * aTypesInfo[].action : the default action of this permission
michael@0 53 * aTypesInfo[].deny : true if security manager denied this app's origin
michael@0 54 * principal.
michael@0 55 * Note:
michael@0 56 * aTypesInfo[].permission will be sent to prompt only when
michael@0 57 * aTypesInfo[].action is PROMPT_ACTION and aTypesInfo[].deny is false.
michael@0 58 */
michael@0 59 function rememberPermission(aTypesInfo, aPrincipal, aSession)
michael@0 60 {
michael@0 61 function convertPermToAllow(aPerm, aPrincipal)
michael@0 62 {
michael@0 63 let type =
michael@0 64 permissionManager.testExactPermissionFromPrincipal(aPrincipal, aPerm);
michael@0 65 if (type == Ci.nsIPermissionManager.PROMPT_ACTION ||
michael@0 66 (type == Ci.nsIPermissionManager.UNKNOWN_ACTION &&
michael@0 67 PROMPT_FOR_UNKNOWN.indexOf(aPerm) >= 0)) {
michael@0 68 debug("add " + aPerm + " to permission manager with ALLOW_ACTION");
michael@0 69 if (!aSession) {
michael@0 70 permissionManager.addFromPrincipal(aPrincipal,
michael@0 71 aPerm,
michael@0 72 Ci.nsIPermissionManager.ALLOW_ACTION);
michael@0 73 } else if (PERMISSION_NO_SESSION.indexOf(aPerm) < 0) {
michael@0 74 permissionManager.addFromPrincipal(aPrincipal,
michael@0 75 aPerm,
michael@0 76 Ci.nsIPermissionManager.ALLOW_ACTION,
michael@0 77 Ci.nsIPermissionManager.EXPIRE_SESSION, 0);
michael@0 78 }
michael@0 79 }
michael@0 80 }
michael@0 81
michael@0 82 for (let i in aTypesInfo) {
michael@0 83 // Expand the permission to see if we have multiple access properties
michael@0 84 // to convert
michael@0 85 let perm = aTypesInfo[i].permission;
michael@0 86 let access = PermissionsTable[perm].access;
michael@0 87 if (access) {
michael@0 88 for (let idx in access) {
michael@0 89 convertPermToAllow(perm + "-" + access[idx], aPrincipal);
michael@0 90 }
michael@0 91 } else {
michael@0 92 convertPermToAllow(perm, aPrincipal);
michael@0 93 }
michael@0 94 }
michael@0 95 }
michael@0 96
michael@0 97 function ContentPermissionPrompt() {}
michael@0 98
michael@0 99 ContentPermissionPrompt.prototype = {
michael@0 100
michael@0 101 handleExistingPermission: function handleExistingPermission(request,
michael@0 102 typesInfo) {
michael@0 103 typesInfo.forEach(function(type) {
michael@0 104 type.action =
michael@0 105 Services.perms.testExactPermissionFromPrincipal(request.principal,
michael@0 106 type.access);
michael@0 107 if (type.action == Ci.nsIPermissionManager.UNKNOWN_ACTION &&
michael@0 108 PROMPT_FOR_UNKNOWN.indexOf(type.access) >= 0) {
michael@0 109 type.action = Ci.nsIPermissionManager.PROMPT_ACTION;
michael@0 110 }
michael@0 111 });
michael@0 112
michael@0 113 // If all permissions are allowed already, call allow() without prompting.
michael@0 114 let checkAllowPermission = function(type) {
michael@0 115 if (type.action == Ci.nsIPermissionManager.ALLOW_ACTION) {
michael@0 116 return true;
michael@0 117 }
michael@0 118 return false;
michael@0 119 }
michael@0 120 if (typesInfo.every(checkAllowPermission)) {
michael@0 121 debug("all permission requests are allowed");
michael@0 122 request.allow();
michael@0 123 return true;
michael@0 124 }
michael@0 125
michael@0 126 // If all permissions are DENY_ACTION or UNKNOWN_ACTION, call cancel()
michael@0 127 // without prompting.
michael@0 128 let checkDenyPermission = function(type) {
michael@0 129 if (type.action == Ci.nsIPermissionManager.DENY_ACTION ||
michael@0 130 type.action == Ci.nsIPermissionManager.UNKNOWN_ACTION) {
michael@0 131 return true;
michael@0 132 }
michael@0 133 return false;
michael@0 134 }
michael@0 135 if (typesInfo.every(checkDenyPermission)) {
michael@0 136 debug("all permission requests are denied");
michael@0 137 request.cancel();
michael@0 138 return true;
michael@0 139 }
michael@0 140 return false;
michael@0 141 },
michael@0 142
michael@0 143 // multiple requests should be audio and video
michael@0 144 checkMultipleRequest: function checkMultipleRequest(typesInfo) {
michael@0 145 if (typesInfo.length == 1) {
michael@0 146 return true;
michael@0 147 } else if (typesInfo.length > 1) {
michael@0 148 let checkIfAllowMultiRequest = function(type) {
michael@0 149 return (ALLOW_MULTIPLE_REQUESTS.indexOf(type.access) !== -1);
michael@0 150 }
michael@0 151 if (typesInfo.every(checkIfAllowMultiRequest)) {
michael@0 152 debug("legal multiple requests");
michael@0 153 return true;
michael@0 154 }
michael@0 155 }
michael@0 156
michael@0 157 return false;
michael@0 158 },
michael@0 159
michael@0 160 handledByApp: function handledByApp(request, typesInfo) {
michael@0 161 if (request.principal.appId == Ci.nsIScriptSecurityManager.NO_APP_ID ||
michael@0 162 request.principal.appId == Ci.nsIScriptSecurityManager.UNKNOWN_APP_ID) {
michael@0 163 // This should not really happen
michael@0 164 request.cancel();
michael@0 165 return true;
michael@0 166 }
michael@0 167
michael@0 168 let appsService = Cc["@mozilla.org/AppsService;1"]
michael@0 169 .getService(Ci.nsIAppsService);
michael@0 170 let app = appsService.getAppByLocalId(request.principal.appId);
michael@0 171
michael@0 172 // Check each permission if it's denied by permission manager with app's
michael@0 173 // URL.
michael@0 174 let notDenyAppPrincipal = function(type) {
michael@0 175 let url = Services.io.newURI(app.origin, null, null);
michael@0 176 let principal = secMan.getAppCodebasePrincipal(url,
michael@0 177 request.principal.appId,
michael@0 178 /*mozbrowser*/false);
michael@0 179 let result = Services.perms.testExactPermissionFromPrincipal(principal,
michael@0 180 type.access);
michael@0 181
michael@0 182 if (result == Ci.nsIPermissionManager.ALLOW_ACTION ||
michael@0 183 result == Ci.nsIPermissionManager.PROMPT_ACTION) {
michael@0 184 type.deny = false;
michael@0 185 }
michael@0 186 return !type.deny;
michael@0 187 }
michael@0 188 if (typesInfo.filter(notDenyAppPrincipal).length === 0) {
michael@0 189 request.cancel();
michael@0 190 return true;
michael@0 191 }
michael@0 192
michael@0 193 return false;
michael@0 194 },
michael@0 195
michael@0 196 handledByPermissionType: function handledByPermissionType(request, typesInfo) {
michael@0 197 for (let i in typesInfo) {
michael@0 198 if (permissionSpecificChecker.hasOwnProperty(typesInfo[i].permission) &&
michael@0 199 permissionSpecificChecker[typesInfo[i].permission](request)) {
michael@0 200 return true;
michael@0 201 }
michael@0 202 }
michael@0 203
michael@0 204 return false;
michael@0 205 },
michael@0 206
michael@0 207 _id: 0,
michael@0 208 prompt: function(request) {
michael@0 209 if (secMan.isSystemPrincipal(request.principal)) {
michael@0 210 request.allow();
michael@0 211 return;
michael@0 212 }
michael@0 213
michael@0 214 // Initialize the typesInfo and set the default value.
michael@0 215 let typesInfo = [];
michael@0 216 let perms = request.types.QueryInterface(Ci.nsIArray);
michael@0 217 for (let idx = 0; idx < perms.length; idx++) {
michael@0 218 let perm = perms.queryElementAt(idx, Ci.nsIContentPermissionType);
michael@0 219 let tmp = {
michael@0 220 permission: perm.type,
michael@0 221 access: (perm.access && perm.access !== "unused") ?
michael@0 222 perm.type + "-" + perm.access : perm.type,
michael@0 223 options: [],
michael@0 224 deny: true,
michael@0 225 action: Ci.nsIPermissionManager.UNKNOWN_ACTION
michael@0 226 };
michael@0 227
michael@0 228 // Append available options, if any.
michael@0 229 let options = perm.options.QueryInterface(Ci.nsIArray);
michael@0 230 for (let i = 0; i < options.length; i++) {
michael@0 231 let option = options.queryElementAt(i, Ci.nsISupportsString).data;
michael@0 232 tmp.options.push(option);
michael@0 233 }
michael@0 234 typesInfo.push(tmp);
michael@0 235 }
michael@0 236
michael@0 237 if (typesInfo.length == 0) {
michael@0 238 request.cancel();
michael@0 239 return;
michael@0 240 }
michael@0 241
michael@0 242 if(!this.checkMultipleRequest(typesInfo)) {
michael@0 243 request.cancel();
michael@0 244 return;
michael@0 245 }
michael@0 246
michael@0 247 if (this.handledByApp(request, typesInfo) ||
michael@0 248 this.handledByPermissionType(request, typesInfo)) {
michael@0 249 return;
michael@0 250 }
michael@0 251
michael@0 252 // returns true if the request was handled
michael@0 253 if (this.handleExistingPermission(request, typesInfo)) {
michael@0 254 return;
michael@0 255 }
michael@0 256
michael@0 257 // prompt PROMPT_ACTION request only.
michael@0 258 typesInfo.forEach(function(aType, aIndex) {
michael@0 259 if (aType.action != Ci.nsIPermissionManager.PROMPT_ACTION || aType.deny) {
michael@0 260 typesInfo.splice(aIndex);
michael@0 261 }
michael@0 262 });
michael@0 263
michael@0 264 let frame = request.element;
michael@0 265 let requestId = this._id++;
michael@0 266
michael@0 267 if (!frame) {
michael@0 268 this.delegatePrompt(request, requestId, typesInfo);
michael@0 269 return;
michael@0 270 }
michael@0 271
michael@0 272 frame = frame.wrappedJSObject;
michael@0 273 var cancelRequest = function() {
michael@0 274 frame.removeEventListener("mozbrowservisibilitychange", onVisibilityChange);
michael@0 275 request.cancel();
michael@0 276 }
michael@0 277
michael@0 278 var self = this;
michael@0 279 var onVisibilityChange = function(evt) {
michael@0 280 if (evt.detail.visible === true)
michael@0 281 return;
michael@0 282
michael@0 283 self.cancelPrompt(request, requestId, typesInfo);
michael@0 284 cancelRequest();
michael@0 285 }
michael@0 286
michael@0 287 // If the request was initiated from a hidden iframe
michael@0 288 // we don't forward it to content and cancel it right away
michael@0 289 let domRequest = frame.getVisible();
michael@0 290 domRequest.onsuccess = function gv_success(evt) {
michael@0 291 if (!evt.target.result) {
michael@0 292 cancelRequest();
michael@0 293 return;
michael@0 294 }
michael@0 295
michael@0 296 // Monitor the frame visibility and cancel the request if the frame goes
michael@0 297 // away but the request is still here.
michael@0 298 frame.addEventListener("mozbrowservisibilitychange", onVisibilityChange);
michael@0 299
michael@0 300 self.delegatePrompt(request, requestId, typesInfo, function onCallback() {
michael@0 301 frame.removeEventListener("mozbrowservisibilitychange", onVisibilityChange);
michael@0 302 });
michael@0 303 };
michael@0 304
michael@0 305 // Something went wrong. Let's cancel the request just in case.
michael@0 306 domRequest.onerror = function gv_error() {
michael@0 307 cancelRequest();
michael@0 308 }
michael@0 309 },
michael@0 310
michael@0 311 cancelPrompt: function(request, requestId, typesInfo) {
michael@0 312 this.sendToBrowserWindow("cancel-permission-prompt", request, requestId,
michael@0 313 typesInfo);
michael@0 314 },
michael@0 315
michael@0 316 delegatePrompt: function(request, requestId, typesInfo, callback) {
michael@0 317
michael@0 318 this.sendToBrowserWindow("permission-prompt", request, requestId, typesInfo,
michael@0 319 function(type, remember, choices) {
michael@0 320 if (type == "permission-allow") {
michael@0 321 rememberPermission(typesInfo, request.principal, !remember);
michael@0 322 if (callback) {
michael@0 323 callback();
michael@0 324 }
michael@0 325 request.allow(choices);
michael@0 326 return;
michael@0 327 }
michael@0 328
michael@0 329 let addDenyPermission = function(type) {
michael@0 330 debug("add " + type.permission +
michael@0 331 " to permission manager with DENY_ACTION");
michael@0 332 if (remember) {
michael@0 333 Services.perms.addFromPrincipal(request.principal, type.access,
michael@0 334 Ci.nsIPermissionManager.DENY_ACTION);
michael@0 335 } else if (PERMISSION_NO_SESSION.indexOf(type.access) < 0) {
michael@0 336 Services.perms.addFromPrincipal(request.principal, type.access,
michael@0 337 Ci.nsIPermissionManager.DENY_ACTION,
michael@0 338 Ci.nsIPermissionManager.EXPIRE_SESSION,
michael@0 339 0);
michael@0 340 }
michael@0 341 }
michael@0 342 typesInfo.forEach(addDenyPermission);
michael@0 343
michael@0 344 if (callback) {
michael@0 345 callback();
michael@0 346 }
michael@0 347 request.cancel();
michael@0 348 });
michael@0 349 },
michael@0 350
michael@0 351 sendToBrowserWindow: function(type, request, requestId, typesInfo, callback) {
michael@0 352 if (callback) {
michael@0 353 SystemAppProxy.addEventListener("mozContentEvent", function contentEvent(evt) {
michael@0 354 let detail = evt.detail;
michael@0 355 if (detail.id != requestId)
michael@0 356 return;
michael@0 357 SystemAppProxy.removeEventListener("mozContentEvent", contentEvent);
michael@0 358
michael@0 359 callback(detail.type, detail.remember, detail.choices);
michael@0 360 })
michael@0 361 }
michael@0 362
michael@0 363 let principal = request.principal;
michael@0 364 let isApp = principal.appStatus != Ci.nsIPrincipal.APP_STATUS_NOT_INSTALLED;
michael@0 365 let remember = (principal.appStatus == Ci.nsIPrincipal.APP_STATUS_PRIVILEGED ||
michael@0 366 principal.appStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED)
michael@0 367 ? true
michael@0 368 : request.remember;
michael@0 369 let permissions = {};
michael@0 370 for (let i in typesInfo) {
michael@0 371 debug("prompt " + typesInfo[i].permission);
michael@0 372 permissions[typesInfo[i].permission] = typesInfo[i].options;
michael@0 373 }
michael@0 374
michael@0 375 let details = {
michael@0 376 type: type,
michael@0 377 permissions: permissions,
michael@0 378 id: requestId,
michael@0 379 origin: principal.origin,
michael@0 380 isApp: isApp,
michael@0 381 remember: remember
michael@0 382 };
michael@0 383
michael@0 384 if (isApp) {
michael@0 385 details.manifestURL = DOMApplicationRegistry.getManifestURLByLocalId(principal.appId);
michael@0 386 }
michael@0 387 SystemAppProxy.dispatchEvent(details);
michael@0 388 },
michael@0 389
michael@0 390 classID: Components.ID("{8c719f03-afe0-4aac-91ff-6c215895d467}"),
michael@0 391
michael@0 392 QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionPrompt])
michael@0 393 };
michael@0 394
michael@0 395 (function() {
michael@0 396 // Do not allow GetUserMedia while in call.
michael@0 397 permissionSpecificChecker["audio-capture"] = function(request) {
michael@0 398 if (AudioManager.phoneState === Ci.nsIAudioManager.PHONE_STATE_IN_CALL) {
michael@0 399 request.cancel();
michael@0 400 return true;
michael@0 401 } else {
michael@0 402 return false;
michael@0 403 }
michael@0 404 };
michael@0 405 })();
michael@0 406
michael@0 407 //module initialization
michael@0 408 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ContentPermissionPrompt]);

mercurial