Source code

Revision control

Other Tools

1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
* License, v. 2.0. If a copy of the MPL was not distributed with this
3
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
var EXPORTED_SYMBOLS = ["SitePermissions"];
6
7
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
8
const { XPCOMUtils } = ChromeUtils.import(
10
);
11
12
var gStringBundle = Services.strings.createBundle(
14
);
15
16
/**
17
* A helper module to manage temporary permissions.
18
*
19
* Permissions are keyed by browser, so methods take a Browser
20
* element to identify the corresponding permission set.
21
*
22
* This uses a WeakMap to key browsers, so that entries are
23
* automatically cleared once the browser stops existing
24
* (once there are no other references to the browser object);
25
*/
26
const TemporaryPermissions = {
27
// This is a three level deep map with the following structure:
28
//
29
// Browser => {
30
// <baseDomain>: {
31
// <permissionID>: {Number} <timeStamp>
32
// }
33
// }
34
//
35
// Only the top level browser elements are stored via WeakMap. The WeakMap
36
// value is an object with URI baseDomains as keys. The keys of that object
37
// are ids that identify permissions that were set for the specific URI.
38
// The final value is an object containing the timestamp of when the permission
39
// was set (in order to invalidate after a certain amount of time has passed).
40
_stateByBrowser: new WeakMap(),
41
42
// Private helper method that bundles some shared behavior for
43
// get() and getAll(), e.g. deleting permissions when they have expired.
44
_get(entry, baseDomain, id, permission) {
45
if (permission == null || permission.timeStamp == null) {
46
delete entry[baseDomain][id];
47
return null;
48
}
49
if (
50
permission.timeStamp + SitePermissions.temporaryPermissionExpireTime <
51
Date.now()
52
) {
53
delete entry[baseDomain][id];
54
return null;
55
}
56
return {
57
id,
58
state: permission.state,
59
scope: SitePermissions.SCOPE_TEMPORARY,
60
};
61
},
62
63
// Extract baseDomain from uri. Fallback to hostname on conversion error.
64
_uriToBaseDomain(uri) {
65
try {
66
return Services.eTLD.getBaseDomain(uri);
67
} catch (error) {
68
if (
69
error.result !== Cr.NS_ERROR_HOST_IS_IP_ADDRESS &&
70
error.result !== Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS
71
) {
72
throw error;
73
}
74
return uri.host;
75
}
76
},
77
78
// Sets a new permission for the specified browser.
79
set(browser, id, state) {
80
if (!browser) {
81
return;
82
}
83
if (!this._stateByBrowser.has(browser)) {
84
this._stateByBrowser.set(browser, {});
85
}
86
let entry = this._stateByBrowser.get(browser);
87
let baseDomain = this._uriToBaseDomain(browser.currentURI);
88
if (!entry[baseDomain]) {
89
entry[baseDomain] = {};
90
}
91
entry[baseDomain][id] = { timeStamp: Date.now(), state };
92
},
93
94
// Removes a permission with the specified id for the specified browser.
95
remove(browser, id) {
96
if (!browser || !this._stateByBrowser.has(browser)) {
97
return;
98
}
99
let entry = this._stateByBrowser.get(browser);
100
let baseDomain = this._uriToBaseDomain(browser.currentURI);
101
if (entry[baseDomain]) {
102
delete entry[baseDomain][id];
103
}
104
},
105
106
// Gets a permission with the specified id for the specified browser.
107
get(browser, id) {
108
if (!browser || !browser.currentURI || !this._stateByBrowser.has(browser)) {
109
return null;
110
}
111
let entry = this._stateByBrowser.get(browser);
112
let baseDomain = this._uriToBaseDomain(browser.currentURI);
113
if (entry[baseDomain]) {
114
let permission = entry[baseDomain][id];
115
return this._get(entry, baseDomain, id, permission);
116
}
117
return null;
118
},
119
120
// Gets all permissions for the specified browser.
121
// Note that only permissions that apply to the current URI
122
// of the passed browser element will be returned.
123
getAll(browser) {
124
let permissions = [];
125
if (!this._stateByBrowser.has(browser)) {
126
return permissions;
127
}
128
let entry = this._stateByBrowser.get(browser);
129
let baseDomain = this._uriToBaseDomain(browser.currentURI);
130
if (entry[baseDomain]) {
131
let timeStamps = entry[baseDomain];
132
for (let id of Object.keys(timeStamps)) {
133
let permission = this._get(entry, baseDomain, id, timeStamps[id]);
134
// _get() returns null when the permission has expired.
135
if (permission) {
136
permissions.push(permission);
137
}
138
}
139
}
140
return permissions;
141
},
142
143
// Clears all permissions for the specified browser.
144
// Unlike other methods, this does NOT clear only for
145
// the currentURI but the whole browser state.
146
clear(browser) {
147
this._stateByBrowser.delete(browser);
148
},
149
150
// Copies the temporary permission state of one browser
151
// into a new entry for the other browser.
152
copy(browser, newBrowser) {
153
let entry = this._stateByBrowser.get(browser);
154
if (entry) {
155
this._stateByBrowser.set(newBrowser, entry);
156
}
157
},
158
};
159
160
// This hold a flag per browser to indicate whether we should show the
161
// user a notification as a permission has been requested that has been
162
// blocked globally. We only want to notify the user in the case that
163
// they actually requested the permission within the current page load
164
// so will clear the flag on navigation.
165
const GloballyBlockedPermissions = {
166
_stateByBrowser: new WeakMap(),
167
168
set(browser, id) {
169
if (!this._stateByBrowser.has(browser)) {
170
this._stateByBrowser.set(browser, {});
171
}
172
let entry = this._stateByBrowser.get(browser);
173
let prePath = browser.currentURI.prePath;
174
if (!entry[prePath]) {
175
entry[prePath] = {};
176
}
177
178
if (entry[prePath][id]) {
179
return;
180
}
181
entry[prePath][id] = true;
182
183
// Clear the flag and remove the listener once the user has navigated.
184
// WebProgress will report various things including hashchanges to us, the
185
// navigation we care about is either leaving the current page or reloading.
186
browser.addProgressListener(
187
{
188
QueryInterface: ChromeUtils.generateQI([
189
Ci.nsIWebProgressListener,
190
Ci.nsISupportsWeakReference,
191
]),
192
onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
193
let hasLeftPage =
194
aLocation.prePath != prePath ||
195
!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT);
196
let isReload = !!(
197
aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_RELOAD
198
);
199
200
if (aWebProgress.isTopLevel && (hasLeftPage || isReload)) {
201
GloballyBlockedPermissions.remove(browser, id, prePath);
202
browser.removeProgressListener(this);
203
}
204
},
205
},
206
Ci.nsIWebProgress.NOTIFY_LOCATION
207
);
208
},
209
210
// Removes a permission with the specified id for the specified browser.
211
remove(browser, id, prePath = null) {
212
let entry = this._stateByBrowser.get(browser);
213
if (!prePath) {
214
prePath = browser.currentURI.prePath;
215
}
216
if (entry && entry[prePath]) {
217
delete entry[prePath][id];
218
}
219
},
220
221
// Gets all permissions for the specified browser.
222
// Note that only permissions that apply to the current URI
223
// of the passed browser element will be returned.
224
getAll(browser) {
225
let permissions = [];
226
let entry = this._stateByBrowser.get(browser);
227
let prePath = browser.currentURI.prePath;
228
if (entry && entry[prePath]) {
229
let timeStamps = entry[prePath];
230
for (let id of Object.keys(timeStamps)) {
231
permissions.push({
232
id,
233
state: gPermissionObject[id].getDefault(),
234
scope: SitePermissions.SCOPE_GLOBAL,
235
});
236
}
237
}
238
return permissions;
239
},
240
241
// Copies the globally blocked permission state of one browser
242
// into a new entry for the other browser.
243
copy(browser, newBrowser) {
244
let entry = this._stateByBrowser.get(browser);
245
if (entry) {
246
this._stateByBrowser.set(newBrowser, entry);
247
}
248
},
249
};
250
251
/**
252
* A module to manage permanent and temporary permissions
253
* by URI and browser.
254
*
255
* Some methods have the side effect of dispatching a "PermissionStateChange"
256
* event on changes to temporary permissions, as mentioned in the respective docs.
257
*/
258
var SitePermissions = {
259
// Permission states.
260
UNKNOWN: Services.perms.UNKNOWN_ACTION,
261
ALLOW: Services.perms.ALLOW_ACTION,
262
BLOCK: Services.perms.DENY_ACTION,
263
PROMPT: Services.perms.PROMPT_ACTION,
264
ALLOW_COOKIES_FOR_SESSION: Ci.nsICookiePermission.ACCESS_SESSION,
265
AUTOPLAY_BLOCKED_ALL: Ci.nsIAutoplay.BLOCKED_ALL,
266
267
// Permission scopes.
268
SCOPE_REQUEST: "{SitePermissions.SCOPE_REQUEST}",
269
SCOPE_TEMPORARY: "{SitePermissions.SCOPE_TEMPORARY}",
270
SCOPE_SESSION: "{SitePermissions.SCOPE_SESSION}",
271
SCOPE_PERSISTENT: "{SitePermissions.SCOPE_PERSISTENT}",
272
SCOPE_POLICY: "{SitePermissions.SCOPE_POLICY}",
273
SCOPE_GLOBAL: "{SitePermissions.SCOPE_GLOBAL}",
274
275
_permissionsArray: null,
276
_defaultPrefBranch: Services.prefs.getBranch("permissions.default."),
277
278
/**
279
* Gets all custom permissions for a given principal.
280
* Install addon permission is excluded, check bug 1303108.
281
*
282
* @return {Array} a list of objects with the keys:
283
* - id: the permissionId of the permission
284
* - scope: the scope of the permission (e.g. SitePermissions.SCOPE_TEMPORARY)
285
* - state: a constant representing the current permission state
286
* (e.g. SitePermissions.ALLOW)
287
*/
288
getAllByPrincipal(principal) {
289
let result = [];
290
if (!principal) {
291
throw new Error("principal argument cannot be null.");
292
}
293
if (!this.isSupportedPrincipal(principal)) {
294
return result;
295
}
296
297
let permissions = Services.perms.getAllForPrincipal(principal);
298
for (let permission of permissions) {
299
// filter out unknown permissions
300
if (gPermissionObject[permission.type]) {
301
// Hide canvas permission when privacy.resistFingerprinting is false.
302
if (permission.type == "canvas" && !this.resistFingerprinting) {
303
continue;
304
}
305
306
let scope = this.SCOPE_PERSISTENT;
307
if (permission.expireType == Services.perms.EXPIRE_SESSION) {
308
scope = this.SCOPE_SESSION;
309
} else if (permission.expireType == Services.perms.EXPIRE_POLICY) {
310
scope = this.SCOPE_POLICY;
311
}
312
313
result.push({
314
id: permission.type,
315
scope,
316
state: permission.capability,
317
});
318
}
319
}
320
321
return result;
322
},
323
324
/**
325
* Returns all custom permissions for a given browser.
326
*
327
* To receive a more detailed, albeit less performant listing see
328
* SitePermissions.getAllPermissionDetailsForBrowser().
329
*
330
* @param {Browser} browser
331
* The browser to fetch permission for.
332
*
333
* @return {Array} a list of objects with the keys:
334
* - id: the permissionId of the permission
335
* - state: a constant representing the current permission state
336
* (e.g. SitePermissions.ALLOW)
337
* - scope: a constant representing how long the permission will
338
* be kept.
339
*/
340
getAllForBrowser(browser) {
341
let permissions = {};
342
343
for (let permission of TemporaryPermissions.getAll(browser)) {
344
permission.scope = this.SCOPE_TEMPORARY;
345
permissions[permission.id] = permission;
346
}
347
348
for (let permission of GloballyBlockedPermissions.getAll(browser)) {
349
permissions[permission.id] = permission;
350
}
351
352
for (let permission of this.getAllByPrincipal(browser.contentPrincipal)) {
353
permissions[permission.id] = permission;
354
}
355
356
return Object.values(permissions);
357
},
358
359
/**
360
* Returns a list of objects with detailed information on all permissions
361
* that are currently set for the given browser.
362
*
363
* @param {Browser} browser
364
* The browser to fetch permission for.
365
*
366
* @return {Array<Object>} a list of objects with the keys:
367
* - id: the permissionID of the permission
368
* - state: a constant representing the current permission state
369
* (e.g. SitePermissions.ALLOW)
370
* - scope: a constant representing how long the permission will
371
* be kept.
372
* - label: the localized label, or null if none is available.
373
*/
374
getAllPermissionDetailsForBrowser(browser) {
375
return this.getAllForBrowser(browser).map(({ id, scope, state }) => ({
376
id,
377
scope,
378
state,
379
label: this.getPermissionLabel(id),
380
}));
381
},
382
383
/**
384
* Checks whether a UI for managing permissions should be exposed for a given
385
* principal.
386
*
387
* @param {nsIPrincipal} principal
388
* The principal to check.
389
*
390
* @return {boolean} if the principal is supported.
391
*/
392
isSupportedPrincipal(principal) {
393
if (!principal) {
394
return false;
395
}
396
if (!(principal instanceof Ci.nsIPrincipal)) {
397
throw new Error(
398
"Argument passed as principal is not an instance of Ci.nsIPrincipal"
399
);
400
}
401
return ["http", "https", "moz-extension", "file"].some(scheme =>
402
principal.schemeIs(scheme)
403
);
404
},
405
406
/**
407
* Gets an array of all permission IDs.
408
*
409
* @return {Array<String>} an array of all permission IDs.
410
*/
411
listPermissions() {
412
if (this._permissionsArray === null) {
413
let permissions = Object.keys(gPermissionObject);
414
415
// Hide canvas permission when privacy.resistFingerprinting is false.
416
if (!this.resistFingerprinting) {
417
permissions = permissions.filter(permission => permission !== "canvas");
418
}
419
this._permissionsArray = permissions;
420
}
421
422
return this._permissionsArray;
423
},
424
425
/**
426
* Called when the privacy.resistFingerprinting preference changes its value.
427
*
428
* @param {string} data
429
* The last argument passed to the preference change observer
430
* @param {string} previous
431
* The previous value of the preference
432
* @param {string} latest
433
* The latest value of the preference
434
*/
435
onResistFingerprintingChanged(data, previous, latest) {
436
// Ensure that listPermissions() will reconstruct its return value the next
437
// time it's called.
438
this._permissionsArray = null;
439
},
440
441
/**
442
* Returns an array of permission states to be exposed to the user for a
443
* permission with the given ID.
444
*
445
* @param {string} permissionID
446
* The ID to get permission states for.
447
*
448
* @return {Array<SitePermissions state>} an array of all permission states.
449
*/
450
getAvailableStates(permissionID) {
451
if (
452
permissionID in gPermissionObject &&
453
gPermissionObject[permissionID].states
454
) {
455
return gPermissionObject[permissionID].states;
456
}
457
458
/* Since the permissions we are dealing with have adopted the convention
459
* of treating UNKNOWN == PROMPT, we only include one of either UNKNOWN
460
* or PROMPT in this list, to avoid duplicating states. */
461
if (this.getDefault(permissionID) == this.UNKNOWN) {
462
return [
463
SitePermissions.UNKNOWN,
464
SitePermissions.ALLOW,
465
SitePermissions.BLOCK,
466
];
467
}
468
469
return [
470
SitePermissions.PROMPT,
471
SitePermissions.ALLOW,
472
SitePermissions.BLOCK,
473
];
474
},
475
476
/**
477
* Returns the default state of a particular permission.
478
*
479
* @param {string} permissionID
480
* The ID to get the default for.
481
*
482
* @return {SitePermissions.state} the default state.
483
*/
484
getDefault(permissionID) {
485
// If the permission has custom logic for getting its default value,
486
// try that first.
487
if (
488
permissionID in gPermissionObject &&
489
gPermissionObject[permissionID].getDefault
490
) {
491
return gPermissionObject[permissionID].getDefault();
492
}
493
494
// Otherwise try to get the default preference for that permission.
495
return this._defaultPrefBranch.getIntPref(permissionID, this.UNKNOWN);
496
},
497
498
/**
499
* Set the default state of a particular permission.
500
*
501
* @param {string} permissionID
502
* The ID to set the default for.
503
*
504
* @param {string} state
505
* The state to set.
506
*/
507
setDefault(permissionID, state) {
508
if (
509
permissionID in gPermissionObject &&
510
gPermissionObject[permissionID].setDefault
511
) {
512
return gPermissionObject[permissionID].setDefault(state);
513
}
514
let key = "permissions.default." + permissionID;
515
return Services.prefs.setIntPref(key, state);
516
},
517
518
/**
519
* Returns the state and scope of a particular permission for a given principal.
520
*
521
* This method will NOT dispatch a "PermissionStateChange" event on the specified
522
* browser if a temporary permission was removed because it has expired.
523
*
524
* @param {nsIPrincipal} principal
525
* The principal to check.
526
* @param {String} permissionID
527
* The id of the permission.
528
* @param {Browser} browser (optional)
529
* The browser object to check for temporary permissions.
530
*
531
* @return {Object} an object with the keys:
532
* - state: The current state of the permission
533
* (e.g. SitePermissions.ALLOW)
534
* - scope: The scope of the permission
535
* (e.g. SitePermissions.SCOPE_PERSISTENT)
536
*/
537
getForPrincipal(principal, permissionID, browser) {
538
if (!principal && !browser) {
539
throw new Error(
540
"Atleast one of the arguments, either principal or browser should not be null."
541
);
542
}
543
let defaultState = this.getDefault(permissionID);
544
let result = { state: defaultState, scope: this.SCOPE_PERSISTENT };
545
if (this.isSupportedPrincipal(principal)) {
546
let permission = null;
547
if (
548
permissionID in gPermissionObject &&
549
gPermissionObject[permissionID].exactHostMatch
550
) {
551
permission = Services.perms.getPermissionObject(
552
principal,
553
permissionID,
554
true
555
);
556
} else {
557
permission = Services.perms.getPermissionObject(
558
principal,
559
permissionID,
560
false
561
);
562
}
563
564
if (permission) {
565
result.state = permission.capability;
566
if (permission.expireType == Services.perms.EXPIRE_SESSION) {
567
result.scope = this.SCOPE_SESSION;
568
} else if (permission.expireType == Services.perms.EXPIRE_POLICY) {
569
result.scope = this.SCOPE_POLICY;
570
}
571
}
572
}
573
574
if (result.state == defaultState) {
575
// If there's no persistent permission saved, check if we have something
576
// set temporarily.
577
let value = TemporaryPermissions.get(browser, permissionID);
578
579
if (value) {
580
result.state = value.state;
581
result.scope = this.SCOPE_TEMPORARY;
582
}
583
}
584
585
return result;
586
},
587
588
/**
589
* Sets the state of a particular permission for a given principal or browser.
590
* This method will dispatch a "PermissionStateChange" event on the specified
591
* browser if a temporary permission was set
592
*
593
* @param {nsIPrincipal} principal
594
* The principal to set the permission for.
595
* Note that this will be ignored if the scope is set to SCOPE_TEMPORARY
596
* @param {String} permissionID
597
* The id of the permission.
598
* @param {SitePermissions state} state
599
* The state of the permission.
600
* @param {SitePermissions scope} scope (optional)
601
* The scope of the permission. Defaults to SCOPE_PERSISTENT.
602
* @param {Browser} browser (optional)
603
* The browser object to set temporary permissions on.
604
* This needs to be provided if the scope is SCOPE_TEMPORARY!
605
*/
606
setForPrincipal(
607
principal,
608
permissionID,
609
state,
610
scope = this.SCOPE_PERSISTENT,
611
browser = null
612
) {
613
if (!principal && !browser) {
614
throw new Error(
615
"Atleast one of the arguments, either principal or browser should not be null."
616
);
617
}
618
if (scope == this.SCOPE_GLOBAL && state == this.BLOCK) {
619
GloballyBlockedPermissions.set(browser, permissionID);
620
browser.dispatchEvent(
621
new browser.ownerGlobal.CustomEvent("PermissionStateChange")
622
);
623
return;
624
}
625
626
if (state == this.UNKNOWN || state == this.getDefault(permissionID)) {
627
// Because they are controlled by two prefs with many states that do not
628
// correspond to the classical ALLOW/DENY/PROMPT model, we want to always
629
// allow the user to add exceptions to their cookie rules without removing them.
630
if (permissionID != "cookie") {
631
this.removeFromPrincipal(principal, permissionID, browser);
632
return;
633
}
634
}
635
636
if (state == this.ALLOW_COOKIES_FOR_SESSION && permissionID != "cookie") {
637
throw new Error(
638
"ALLOW_COOKIES_FOR_SESSION can only be set on the cookie permission"
639
);
640
}
641
642
// Save temporary permissions.
643
if (scope == this.SCOPE_TEMPORARY) {
644
// We do not support setting temp ALLOW for security reasons.
645
// In its current state, this permission could be exploited by subframes
646
// on the same page. This is because for BLOCK we ignore the request
647
// principal and only consider the current browser principal, to avoid notification spamming.
648
//
649
// If you ever consider removing this line, you likely want to implement
650
// a more fine-grained TemporaryPermissions that temporarily blocks for the
651
// entire browser, but temporarily allows only for specific frames.
652
if (state != this.BLOCK) {
653
throw new Error(
654
"'Block' is the only permission we can save temporarily on a browser"
655
);
656
}
657
658
if (!browser) {
659
throw new Error(
660
"TEMPORARY scoped permissions require a browser object"
661
);
662
}
663
664
TemporaryPermissions.set(browser, permissionID, state);
665
666
browser.dispatchEvent(
667
new browser.ownerGlobal.CustomEvent("PermissionStateChange")
668
);
669
} else if (this.isSupportedPrincipal(principal)) {
670
let perms_scope = Services.perms.EXPIRE_NEVER;
671
if (scope == this.SCOPE_SESSION) {
672
perms_scope = Services.perms.EXPIRE_SESSION;
673
} else if (scope == this.SCOPE_POLICY) {
674
perms_scope = Services.perms.EXPIRE_POLICY;
675
}
676
677
Services.perms.addFromPrincipal(
678
principal,
679
permissionID,
680
state,
681
perms_scope
682
);
683
}
684
},
685
686
/**
687
* Removes the saved state of a particular permission for a given principal and/or browser.
688
* This method will dispatch a "PermissionStateChange" event on the specified
689
* browser if a temporary permission was removed.
690
*
691
* @param {nsIPrincipal} principal
692
* The principal to remove the permission for.
693
* @param {String} permissionID
694
* The id of the permission.
695
* @param {Browser} browser (optional)
696
* The browser object to remove temporary permissions on.
697
*/
698
removeFromPrincipal(principal, permissionID, browser) {
699
if (!principal && !browser) {
700
throw new Error(
701
"Atleast one of the arguments, either principal or browser should not be null."
702
);
703
}
704
if (this.isSupportedPrincipal(principal)) {
705
Services.perms.removeFromPrincipal(principal, permissionID);
706
}
707
708
// TemporaryPermissions.get() deletes expired permissions automatically,
709
if (TemporaryPermissions.get(browser, permissionID)) {
710
// If it exists but has not expired, remove it explicitly.
711
TemporaryPermissions.remove(browser, permissionID);
712
// Send a PermissionStateChange event only if the permission hasn't expired.
713
browser.dispatchEvent(
714
new browser.ownerGlobal.CustomEvent("PermissionStateChange")
715
);
716
}
717
},
718
719
/**
720
* Clears all permissions that were temporarily saved.
721
*
722
* @param {Browser} browser
723
* The browser object to clear.
724
*/
725
clearTemporaryPermissions(browser) {
726
TemporaryPermissions.clear(browser);
727
},
728
729
/**
730
* Copy all permissions that were temporarily saved on one
731
* browser object to a new browser.
732
*
733
* @param {Browser} browser
734
* The browser object to copy from.
735
* @param {Browser} newBrowser
736
* The browser object to copy to.
737
*/
738
copyTemporaryPermissions(browser, newBrowser) {
739
TemporaryPermissions.copy(browser, newBrowser);
740
GloballyBlockedPermissions.copy(browser, newBrowser);
741
},
742
743
/**
744
* Returns the localized label for the permission with the given ID, to be
745
* used in a UI for managing permissions.
746
*
747
* @param {string} permissionID
748
* The permission to get the label for.
749
*
750
* @return {String} the localized label or null if none is available.
751
*/
752
getPermissionLabel(permissionID) {
753
if (!(permissionID in gPermissionObject)) {
754
// Permission can't be found.
755
return null;
756
}
757
if (
758
"labelID" in gPermissionObject[permissionID] &&
759
gPermissionObject[permissionID].labelID === null
760
) {
761
// Permission doesn't support having a label.
762
return null;
763
}
764
let labelID = gPermissionObject[permissionID].labelID || permissionID;
765
return gStringBundle.GetStringFromName("permission." + labelID + ".label");
766
},
767
768
/**
769
* Returns the localized label for the given permission state, to be used in
770
* a UI for managing permissions.
771
*
772
* @param {SitePermissions state} state
773
* The state to get the label for.
774
*
775
* @return {String|null} the localized label or null if an
776
* unknown state was passed.
777
*/
778
getMultichoiceStateLabel(permissionID, state) {
779
// If the permission has custom logic for getting its default value,
780
// try that first.
781
if (
782
permissionID in gPermissionObject &&
783
gPermissionObject[permissionID].getMultichoiceStateLabel
784
) {
785
return gPermissionObject[permissionID].getMultichoiceStateLabel(state);
786
}
787
788
switch (state) {
789
case this.UNKNOWN:
790
case this.PROMPT:
791
return gStringBundle.GetStringFromName("state.multichoice.alwaysAsk");
792
case this.ALLOW:
793
return gStringBundle.GetStringFromName("state.multichoice.allow");
794
case this.ALLOW_COOKIES_FOR_SESSION:
795
return gStringBundle.GetStringFromName(
796
"state.multichoice.allowForSession"
797
);
798
case this.BLOCK:
799
return gStringBundle.GetStringFromName("state.multichoice.block");
800
default:
801
return null;
802
}
803
},
804
805
/**
806
* Returns the localized label for a permission's current state.
807
*
808
* @param {SitePermissions state} state
809
* The state to get the label for.
810
* @param {string} id
811
* The permission to get the state label for.
812
* @param {SitePermissions scope} scope (optional)
813
* The scope to get the label for.
814
*
815
* @return {String|null} the localized label or null if an
816
* unknown state was passed.
817
*/
818
getCurrentStateLabel(state, id, scope = null) {
819
switch (state) {
820
case this.PROMPT:
821
return gStringBundle.GetStringFromName("state.current.prompt");
822
case this.ALLOW:
823
if (
824
scope &&
825
scope != this.SCOPE_PERSISTENT &&
826
scope != this.SCOPE_POLICY
827
) {
828
return gStringBundle.GetStringFromName(
829
"state.current.allowedTemporarily"
830
);
831
}
832
return gStringBundle.GetStringFromName("state.current.allowed");
833
case this.ALLOW_COOKIES_FOR_SESSION:
834
return gStringBundle.GetStringFromName(
835
"state.current.allowedForSession"
836
);
837
case this.BLOCK:
838
if (
839
scope &&
840
scope != this.SCOPE_PERSISTENT &&
841
scope != this.SCOPE_POLICY &&
842
scope != this.SCOPE_GLOBAL
843
) {
844
return gStringBundle.GetStringFromName(
845
"state.current.blockedTemporarily"
846
);
847
}
848
return gStringBundle.GetStringFromName("state.current.blocked");
849
default:
850
return null;
851
}
852
},
853
};
854
855
var gPermissionObject = {
856
/* Holds permission ID => options pairs.
857
*
858
* Supported options:
859
*
860
* - exactHostMatch
861
* Allows sub domains to have their own permissions.
862
* Defaults to false.
863
*
864
* - getDefault
865
* Called to get the permission's default state.
866
* Defaults to UNKNOWN, indicating that the user will be asked each time
867
* a page asks for that permissions.
868
*
869
* - labelID
870
* Use the given ID instead of the permission name for looking up strings.
871
* e.g. "desktop-notification2" to use permission.desktop-notification2.label
872
*
873
* - states
874
* Array of permission states to be exposed to the user.
875
* Defaults to ALLOW, BLOCK and the default state (see getDefault).
876
*/
877
878
"autoplay-media": {
879
exactHostMatch: true,
880
getDefault() {
881
let pref = Services.prefs.getIntPref(
882
"media.autoplay.default",
883
Ci.nsIAutoplay.BLOCKED
884
);
885
if (pref == Ci.nsIAutoplay.ALLOWED) {
886
return SitePermissions.ALLOW;
887
}
888
if (pref == Ci.nsIAutoplay.BLOCKED_ALL) {
889
return SitePermissions.AUTOPLAY_BLOCKED_ALL;
890
}
891
return SitePermissions.BLOCK;
892
},
893
setDefault(value) {
894
let prefValue = Ci.nsIAutoplay.BLOCKED;
895
if (value == SitePermissions.ALLOW) {
896
prefValue = Ci.nsIAutoplay.ALLOWED;
897
} else if (value == SitePermissions.AUTOPLAY_BLOCKED_ALL) {
898
prefValue = Ci.nsIAutoplay.BLOCKED_ALL;
899
}
900
Services.prefs.setIntPref("media.autoplay.default", prefValue);
901
},
902
labelID: "autoplay",
903
states: [
904
SitePermissions.ALLOW,
905
SitePermissions.BLOCK,
906
SitePermissions.AUTOPLAY_BLOCKED_ALL,
907
],
908
getMultichoiceStateLabel(state) {
909
switch (state) {
910
case SitePermissions.AUTOPLAY_BLOCKED_ALL:
911
return gStringBundle.GetStringFromName(
912
"state.multichoice.autoplayblockall"
913
);
914
case SitePermissions.BLOCK:
915
return gStringBundle.GetStringFromName(
916
"state.multichoice.autoplayblock"
917
);
918
case SitePermissions.ALLOW:
919
return gStringBundle.GetStringFromName(
920
"state.multichoice.autoplayallow"
921
);
922
}
923
throw new Error(`Unkown state: ${state}`);
924
},
925
},
926
927
cookie: {
928
states: [
929
SitePermissions.ALLOW,
930
SitePermissions.ALLOW_COOKIES_FOR_SESSION,
931
SitePermissions.BLOCK,
932
],
933
getDefault() {
934
if (
935
Services.prefs.getIntPref("network.cookie.cookieBehavior") ==
936
Ci.nsICookieService.BEHAVIOR_REJECT
937
) {
938
return SitePermissions.BLOCK;
939
}
940
941
if (
942
Services.prefs.getIntPref("network.cookie.lifetimePolicy") ==
943
Ci.nsICookieService.ACCEPT_SESSION
944
) {
945
return SitePermissions.ALLOW_COOKIES_FOR_SESSION;
946
}
947
948
return SitePermissions.ALLOW;
949
},
950
},
951
952
"desktop-notification": {
953
exactHostMatch: true,
954
labelID: "desktop-notification3",
955
},
956
957
camera: {
958
exactHostMatch: true,
959
},
960
961
microphone: {
962
exactHostMatch: true,
963
},
964
965
screen: {
966
exactHostMatch: true,
967
states: [SitePermissions.UNKNOWN, SitePermissions.BLOCK],
968
},
969
970
popup: {
971
getDefault() {
972
return Services.prefs.getBoolPref("dom.disable_open_during_load")
973
? SitePermissions.BLOCK
974
: SitePermissions.ALLOW;
975
},
976
states: [SitePermissions.ALLOW, SitePermissions.BLOCK],
977
},
978
979
install: {
980
getDefault() {
981
return Services.prefs.getBoolPref("xpinstall.whitelist.required")
982
? SitePermissions.UNKNOWN
983
: SitePermissions.ALLOW;
984
},
985
},
986
987
geo: {
988
exactHostMatch: true,
989
},
990
991
xr: {
992
exactHostMatch: true,
993
},
994
995
"focus-tab-by-prompt": {
996
exactHostMatch: true,
997
states: [SitePermissions.UNKNOWN, SitePermissions.ALLOW],
998
},
999
"persistent-storage": {
1000
exactHostMatch: true,
1001
},
1002
1003
shortcuts: {
1004
states: [SitePermissions.ALLOW, SitePermissions.BLOCK],
1005
},
1006
1007
canvas: {},
1008
1009
midi: {
1010
exactHostMatch: true,
1011
},
1012
1013
"midi-sysex": {
1014
exactHostMatch: true,
1015
},
1016
1017
"storage-access": {
1018
labelID: null,
1019
getDefault() {
1020
return SitePermissions.UNKNOWN;
1021
},
1022
},
1023
};
1024
1025
if (!Services.prefs.getBoolPref("dom.webmidi.enabled")) {
1026
// ESLint gets angry about array versus dot notation here, but some permission
1027
// names use hyphens. Disabling rule for line to keep things consistent.
1028
// eslint-disable-next-line dot-notation
1029
delete gPermissionObject["midi"];
1030
delete gPermissionObject["midi-sysex"];
1031
}
1032
1033
XPCOMUtils.defineLazyPreferenceGetter(
1034
SitePermissions,
1035
"temporaryPermissionExpireTime",
1036
"privacy.temporary_permission_expire_time_ms",
1037
3600 * 1000
1038
);
1039
XPCOMUtils.defineLazyPreferenceGetter(
1040
SitePermissions,
1041
"resistFingerprinting",
1042
"privacy.resistFingerprinting",
1043
false,
1044
SitePermissions.onResistFingerprintingChanged.bind(SitePermissions)
1045
);