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 (!this.isSupportedPrincipal(principal)) {
291
return result;
292
}
293
294
let permissions = Services.perms.getAllForPrincipal(principal);
295
while (permissions.hasMoreElements()) {
296
let permission = permissions.getNext();
297
298
// filter out unknown permissions
299
if (gPermissionObject[permission.type]) {
300
// Hide canvas permission when privacy.resistFingerprinting is false.
301
if (permission.type == "canvas" && !this.resistFingerprinting) {
302
continue;
303
}
304
305
let scope = this.SCOPE_PERSISTENT;
306
if (permission.expireType == Services.perms.EXPIRE_SESSION) {
307
scope = this.SCOPE_SESSION;
308
} else if (permission.expireType == Services.perms.EXPIRE_POLICY) {
309
scope = this.SCOPE_POLICY;
310
}
311
312
result.push({
313
id: permission.type,
314
scope,
315
state: permission.capability,
316
});
317
}
318
}
319
320
return result;
321
},
322
323
/**
324
* Returns all custom permissions for a given browser.
325
*
326
* To receive a more detailed, albeit less performant listing see
327
* SitePermissions.getAllPermissionDetailsForBrowser().
328
*
329
* @param {Browser} browser
330
* The browser to fetch permission for.
331
*
332
* @return {Array} a list of objects with the keys:
333
* - id: the permissionId of the permission
334
* - state: a constant representing the current permission state
335
* (e.g. SitePermissions.ALLOW)
336
* - scope: a constant representing how long the permission will
337
* be kept.
338
*/
339
getAllForBrowser(browser) {
340
let permissions = {};
341
342
for (let permission of TemporaryPermissions.getAll(browser)) {
343
permission.scope = this.SCOPE_TEMPORARY;
344
permissions[permission.id] = permission;
345
}
346
347
for (let permission of GloballyBlockedPermissions.getAll(browser)) {
348
permissions[permission.id] = permission;
349
}
350
351
for (let permission of this.getAllByPrincipal(browser.contentPrincipal)) {
352
permissions[permission.id] = permission;
353
}
354
355
return Object.values(permissions);
356
},
357
358
/**
359
* Returns a list of objects with detailed information on all permissions
360
* that are currently set for the given browser.
361
*
362
* @param {Browser} browser
363
* The browser to fetch permission for.
364
*
365
* @return {Array<Object>} a list of objects with the keys:
366
* - id: the permissionID of the permission
367
* - state: a constant representing the current permission state
368
* (e.g. SitePermissions.ALLOW)
369
* - scope: a constant representing how long the permission will
370
* be kept.
371
* - label: the localized label, or null if none is available.
372
*/
373
getAllPermissionDetailsForBrowser(browser) {
374
return this.getAllForBrowser(browser).map(({ id, scope, state }) => ({
375
id,
376
scope,
377
state,
378
label: this.getPermissionLabel(id),
379
}));
380
},
381
382
/**
383
* Checks whether a UI for managing permissions should be exposed for a given
384
* principal. This excludes file URIs, for instance, as they don't have a host,
385
* even though nsIPermissionManager can still handle them.
386
*
387
* @param {nsIPrincipal} principal
388
* The principal to check.
389
*
390
* @return {boolean} if the principal is supported.
391
*/
392
isSupportedPrincipal(principal) {
393
return (
394
principal &&
395
["http", "https", "moz-extension"].some(scheme =>
396
principal.schemeIs(scheme)
397
)
398
);
399
},
400
401
/**
402
* Gets an array of all permission IDs.
403
*
404
* @return {Array<String>} an array of all permission IDs.
405
*/
406
listPermissions() {
407
if (this._permissionsArray === null) {
408
let permissions = Object.keys(gPermissionObject);
409
410
// Hide canvas permission when privacy.resistFingerprinting is false.
411
if (!this.resistFingerprinting) {
412
permissions = permissions.filter(permission => permission !== "canvas");
413
}
414
this._permissionsArray = permissions;
415
}
416
417
return this._permissionsArray;
418
},
419
420
/**
421
* Called when the privacy.resistFingerprinting preference changes its value.
422
*
423
* @param {string} data
424
* The last argument passed to the preference change observer
425
* @param {string} previous
426
* The previous value of the preference
427
* @param {string} latest
428
* The latest value of the preference
429
*/
430
onResistFingerprintingChanged(data, previous, latest) {
431
// Ensure that listPermissions() will reconstruct its return value the next
432
// time it's called.
433
this._permissionsArray = null;
434
},
435
436
/**
437
* Returns an array of permission states to be exposed to the user for a
438
* permission with the given ID.
439
*
440
* @param {string} permissionID
441
* The ID to get permission states for.
442
*
443
* @return {Array<SitePermissions state>} an array of all permission states.
444
*/
445
getAvailableStates(permissionID) {
446
if (
447
permissionID in gPermissionObject &&
448
gPermissionObject[permissionID].states
449
) {
450
return gPermissionObject[permissionID].states;
451
}
452
453
/* Since the permissions we are dealing with have adopted the convention
454
* of treating UNKNOWN == PROMPT, we only include one of either UNKNOWN
455
* or PROMPT in this list, to avoid duplicating states. */
456
if (this.getDefault(permissionID) == this.UNKNOWN) {
457
return [
458
SitePermissions.UNKNOWN,
459
SitePermissions.ALLOW,
460
SitePermissions.BLOCK,
461
];
462
}
463
464
return [
465
SitePermissions.PROMPT,
466
SitePermissions.ALLOW,
467
SitePermissions.BLOCK,
468
];
469
},
470
471
/**
472
* Returns the default state of a particular permission.
473
*
474
* @param {string} permissionID
475
* The ID to get the default for.
476
*
477
* @return {SitePermissions.state} the default state.
478
*/
479
getDefault(permissionID) {
480
// If the permission has custom logic for getting its default value,
481
// try that first.
482
if (
483
permissionID in gPermissionObject &&
484
gPermissionObject[permissionID].getDefault
485
) {
486
return gPermissionObject[permissionID].getDefault();
487
}
488
489
// Otherwise try to get the default preference for that permission.
490
return this._defaultPrefBranch.getIntPref(permissionID, this.UNKNOWN);
491
},
492
493
/**
494
* Set the default state of a particular permission.
495
*
496
* @param {string} permissionID
497
* The ID to set the default for.
498
*
499
* @param {string} state
500
* The state to set.
501
*/
502
setDefault(permissionID, state) {
503
if (
504
permissionID in gPermissionObject &&
505
gPermissionObject[permissionID].setDefault
506
) {
507
return gPermissionObject[permissionID].setDefault(state);
508
}
509
let key = "permissions.default." + permissionID;
510
return Services.prefs.setIntPref(key, state);
511
},
512
513
/**
514
* Returns the state and scope of a particular permission for a given principal.
515
*
516
* This method will NOT dispatch a "PermissionStateChange" event on the specified
517
* browser if a temporary permission was removed because it has expired.
518
*
519
* @param {nsIPrincipal} principal
520
* The principal to check.
521
* @param {String} permissionID
522
* The id of the permission.
523
* @param {Browser} browser (optional)
524
* The browser object to check for temporary permissions.
525
*
526
* @return {Object} an object with the keys:
527
* - state: The current state of the permission
528
* (e.g. SitePermissions.ALLOW)
529
* - scope: The scope of the permission
530
* (e.g. SitePermissions.SCOPE_PERSISTENT)
531
*/
532
getForPrincipal(principal, permissionID, browser) {
533
let defaultState = this.getDefault(permissionID);
534
let result = { state: defaultState, scope: this.SCOPE_PERSISTENT };
535
if (this.isSupportedPrincipal(principal)) {
536
let permission = null;
537
if (
538
permissionID in gPermissionObject &&
539
gPermissionObject[permissionID].exactHostMatch
540
) {
541
permission = Services.perms.getPermissionObject(
542
principal,
543
permissionID,
544
true
545
);
546
} else {
547
permission = Services.perms.getPermissionObject(
548
principal,
549
permissionID,
550
false
551
);
552
}
553
554
if (permission) {
555
result.state = permission.capability;
556
if (permission.expireType == Services.perms.EXPIRE_SESSION) {
557
result.scope = this.SCOPE_SESSION;
558
} else if (permission.expireType == Services.perms.EXPIRE_POLICY) {
559
result.scope = this.SCOPE_POLICY;
560
}
561
}
562
}
563
564
if (result.state == defaultState) {
565
// If there's no persistent permission saved, check if we have something
566
// set temporarily.
567
let value = TemporaryPermissions.get(browser, permissionID);
568
569
if (value) {
570
result.state = value.state;
571
result.scope = this.SCOPE_TEMPORARY;
572
}
573
}
574
575
return result;
576
},
577
578
/**
579
* Sets the state of a particular permission for a given principal or browser.
580
* This method will dispatch a "PermissionStateChange" event on the specified
581
* browser if a temporary permission was set
582
*
583
* @param {nsIPrincipal} principal
584
* The principal to set the permission for.
585
* Note that this will be ignored if the scope is set to SCOPE_TEMPORARY
586
* @param {String} permissionID
587
* The id of the permission.
588
* @param {SitePermissions state} state
589
* The state of the permission.
590
* @param {SitePermissions scope} scope (optional)
591
* The scope of the permission. Defaults to SCOPE_PERSISTENT.
592
* @param {Browser} browser (optional)
593
* The browser object to set temporary permissions on.
594
* This needs to be provided if the scope is SCOPE_TEMPORARY!
595
*/
596
setForPrincipal(
597
principal,
598
permissionID,
599
state,
600
scope = this.SCOPE_PERSISTENT,
601
browser = null
602
) {
603
if (scope == this.SCOPE_GLOBAL && state == this.BLOCK) {
604
GloballyBlockedPermissions.set(browser, permissionID);
605
browser.dispatchEvent(
606
new browser.ownerGlobal.CustomEvent("PermissionStateChange")
607
);
608
return;
609
}
610
611
if (state == this.UNKNOWN || state == this.getDefault(permissionID)) {
612
// Because they are controlled by two prefs with many states that do not
613
// correspond to the classical ALLOW/DENY/PROMPT model, we want to always
614
// allow the user to add exceptions to their cookie rules without removing them.
615
if (permissionID != "cookie") {
616
this.removeFromPrincipal(principal, permissionID, browser);
617
return;
618
}
619
}
620
621
if (state == this.ALLOW_COOKIES_FOR_SESSION && permissionID != "cookie") {
622
throw new Error(
623
"ALLOW_COOKIES_FOR_SESSION can only be set on the cookie permission"
624
);
625
}
626
627
// Save temporary permissions.
628
if (scope == this.SCOPE_TEMPORARY) {
629
// We do not support setting temp ALLOW for security reasons.
630
// In its current state, this permission could be exploited by subframes
631
// on the same page. This is because for BLOCK we ignore the request
632
// principal and only consider the current browser principal, to avoid notification spamming.
633
//
634
// If you ever consider removing this line, you likely want to implement
635
// a more fine-grained TemporaryPermissions that temporarily blocks for the
636
// entire browser, but temporarily allows only for specific frames.
637
if (state != this.BLOCK) {
638
throw new Error(
639
"'Block' is the only permission we can save temporarily on a browser"
640
);
641
}
642
643
if (!browser) {
644
throw new Error(
645
"TEMPORARY scoped permissions require a browser object"
646
);
647
}
648
649
TemporaryPermissions.set(browser, permissionID, state);
650
651
browser.dispatchEvent(
652
new browser.ownerGlobal.CustomEvent("PermissionStateChange")
653
);
654
} else if (this.isSupportedPrincipal(principal)) {
655
let perms_scope = Services.perms.EXPIRE_NEVER;
656
if (scope == this.SCOPE_SESSION) {
657
perms_scope = Services.perms.EXPIRE_SESSION;
658
} else if (scope == this.SCOPE_POLICY) {
659
perms_scope = Services.perms.EXPIRE_POLICY;
660
}
661
662
Services.perms.addFromPrincipal(
663
principal,
664
permissionID,
665
state,
666
perms_scope
667
);
668
}
669
},
670
671
/**
672
* Removes the saved state of a particular permission for a given principal and/or browser.
673
* This method will dispatch a "PermissionStateChange" event on the specified
674
* browser if a temporary permission was removed.
675
*
676
* @param {nsIPrincipal} principal
677
* The principal to remove the permission for.
678
* @param {String} permissionID
679
* The id of the permission.
680
* @param {Browser} browser (optional)
681
* The browser object to remove temporary permissions on.
682
*/
683
removeFromPrincipal(principal, permissionID, browser) {
684
if (this.isSupportedPrincipal(principal)) {
685
Services.perms.removeFromPrincipal(principal, permissionID);
686
}
687
688
// TemporaryPermissions.get() deletes expired permissions automatically,
689
if (TemporaryPermissions.get(browser, permissionID)) {
690
// If it exists but has not expired, remove it explicitly.
691
TemporaryPermissions.remove(browser, permissionID);
692
// Send a PermissionStateChange event only if the permission hasn't expired.
693
browser.dispatchEvent(
694
new browser.ownerGlobal.CustomEvent("PermissionStateChange")
695
);
696
}
697
},
698
699
/**
700
* Clears all permissions that were temporarily saved.
701
*
702
* @param {Browser} browser
703
* The browser object to clear.
704
*/
705
clearTemporaryPermissions(browser) {
706
TemporaryPermissions.clear(browser);
707
},
708
709
/**
710
* Copy all permissions that were temporarily saved on one
711
* browser object to a new browser.
712
*
713
* @param {Browser} browser
714
* The browser object to copy from.
715
* @param {Browser} newBrowser
716
* The browser object to copy to.
717
*/
718
copyTemporaryPermissions(browser, newBrowser) {
719
TemporaryPermissions.copy(browser, newBrowser);
720
GloballyBlockedPermissions.copy(browser, newBrowser);
721
},
722
723
/**
724
* Returns the localized label for the permission with the given ID, to be
725
* used in a UI for managing permissions.
726
*
727
* @param {string} permissionID
728
* The permission to get the label for.
729
*
730
* @return {String} the localized label or null if none is available.
731
*/
732
getPermissionLabel(permissionID) {
733
if (!(permissionID in gPermissionObject)) {
734
// Permission can't be found.
735
return null;
736
}
737
if (
738
"labelID" in gPermissionObject[permissionID] &&
739
gPermissionObject[permissionID].labelID === null
740
) {
741
// Permission doesn't support having a label.
742
return null;
743
}
744
let labelID = gPermissionObject[permissionID].labelID || permissionID;
745
return gStringBundle.GetStringFromName("permission." + labelID + ".label");
746
},
747
748
/**
749
* Returns the localized label for the given permission state, to be used in
750
* a UI for managing permissions.
751
*
752
* @param {SitePermissions state} state
753
* The state to get the label for.
754
*
755
* @return {String|null} the localized label or null if an
756
* unknown state was passed.
757
*/
758
getMultichoiceStateLabel(permissionID, state) {
759
// If the permission has custom logic for getting its default value,
760
// try that first.
761
if (
762
permissionID in gPermissionObject &&
763
gPermissionObject[permissionID].getMultichoiceStateLabel
764
) {
765
return gPermissionObject[permissionID].getMultichoiceStateLabel(state);
766
}
767
768
switch (state) {
769
case this.UNKNOWN:
770
case this.PROMPT:
771
return gStringBundle.GetStringFromName("state.multichoice.alwaysAsk");
772
case this.ALLOW:
773
return gStringBundle.GetStringFromName("state.multichoice.allow");
774
case this.ALLOW_COOKIES_FOR_SESSION:
775
return gStringBundle.GetStringFromName(
776
"state.multichoice.allowForSession"
777
);
778
case this.BLOCK:
779
return gStringBundle.GetStringFromName("state.multichoice.block");
780
default:
781
return null;
782
}
783
},
784
785
/**
786
* Returns the localized label for a permission's current state.
787
*
788
* @param {SitePermissions state} state
789
* The state to get the label for.
790
* @param {string} id
791
* The permission to get the state label for.
792
* @param {SitePermissions scope} scope (optional)
793
* The scope to get the label for.
794
*
795
* @return {String|null} the localized label or null if an
796
* unknown state was passed.
797
*/
798
getCurrentStateLabel(state, id, scope = null) {
799
switch (state) {
800
case this.PROMPT:
801
return gStringBundle.GetStringFromName("state.current.prompt");
802
case this.ALLOW:
803
if (
804
scope &&
805
scope != this.SCOPE_PERSISTENT &&
806
scope != this.SCOPE_POLICY
807
) {
808
return gStringBundle.GetStringFromName(
809
"state.current.allowedTemporarily"
810
);
811
}
812
return gStringBundle.GetStringFromName("state.current.allowed");
813
case this.ALLOW_COOKIES_FOR_SESSION:
814
return gStringBundle.GetStringFromName(
815
"state.current.allowedForSession"
816
);
817
case this.BLOCK:
818
if (
819
scope &&
820
scope != this.SCOPE_PERSISTENT &&
821
scope != this.SCOPE_POLICY &&
822
scope != this.SCOPE_GLOBAL
823
) {
824
return gStringBundle.GetStringFromName(
825
"state.current.blockedTemporarily"
826
);
827
}
828
return gStringBundle.GetStringFromName("state.current.blocked");
829
default:
830
return null;
831
}
832
},
833
};
834
835
var gPermissionObject = {
836
/* Holds permission ID => options pairs.
837
*
838
* Supported options:
839
*
840
* - exactHostMatch
841
* Allows sub domains to have their own permissions.
842
* Defaults to false.
843
*
844
* - getDefault
845
* Called to get the permission's default state.
846
* Defaults to UNKNOWN, indicating that the user will be asked each time
847
* a page asks for that permissions.
848
*
849
* - labelID
850
* Use the given ID instead of the permission name for looking up strings.
851
* e.g. "desktop-notification2" to use permission.desktop-notification2.label
852
*
853
* - states
854
* Array of permission states to be exposed to the user.
855
* Defaults to ALLOW, BLOCK and the default state (see getDefault).
856
*/
857
858
"autoplay-media": {
859
exactHostMatch: true,
860
getDefault() {
861
let pref = Services.prefs.getIntPref(
862
"media.autoplay.default",
863
Ci.nsIAutoplay.BLOCKED
864
);
865
if (pref == Ci.nsIAutoplay.ALLOWED) {
866
return SitePermissions.ALLOW;
867
}
868
if (pref == Ci.nsIAutoplay.BLOCKED_ALL) {
869
return SitePermissions.AUTOPLAY_BLOCKED_ALL;
870
}
871
return SitePermissions.BLOCK;
872
},
873
setDefault(value) {
874
let prefValue = Ci.nsIAutoplay.BLOCKED;
875
if (value == SitePermissions.ALLOW) {
876
prefValue = Ci.nsIAutoplay.ALLOWED;
877
} else if (value == SitePermissions.AUTOPLAY_BLOCKED_ALL) {
878
prefValue = Ci.nsIAutoplay.BLOCKED_ALL;
879
}
880
Services.prefs.setIntPref("media.autoplay.default", prefValue);
881
},
882
labelID: "autoplay",
883
states: [
884
SitePermissions.ALLOW,
885
SitePermissions.BLOCK,
886
SitePermissions.AUTOPLAY_BLOCKED_ALL,
887
],
888
getMultichoiceStateLabel(state) {
889
switch (state) {
890
case SitePermissions.AUTOPLAY_BLOCKED_ALL:
891
return gStringBundle.GetStringFromName(
892
"state.multichoice.autoplayblockall"
893
);
894
case SitePermissions.BLOCK:
895
return gStringBundle.GetStringFromName(
896
"state.multichoice.autoplayblock"
897
);
898
case SitePermissions.ALLOW:
899
return gStringBundle.GetStringFromName(
900
"state.multichoice.autoplayallow"
901
);
902
}
903
throw new Error(`Unkown state: ${state}`);
904
},
905
},
906
907
cookie: {
908
states: [
909
SitePermissions.ALLOW,
910
SitePermissions.ALLOW_COOKIES_FOR_SESSION,
911
SitePermissions.BLOCK,
912
],
913
getDefault() {
914
if (
915
Services.prefs.getIntPref("network.cookie.cookieBehavior") ==
916
Ci.nsICookieService.BEHAVIOR_REJECT
917
) {
918
return SitePermissions.BLOCK;
919
}
920
921
if (
922
Services.prefs.getIntPref("network.cookie.lifetimePolicy") ==
923
Ci.nsICookieService.ACCEPT_SESSION
924
) {
925
return SitePermissions.ALLOW_COOKIES_FOR_SESSION;
926
}
927
928
return SitePermissions.ALLOW;
929
},
930
},
931
932
"desktop-notification": {
933
exactHostMatch: true,
934
labelID: "desktop-notification3",
935
},
936
937
camera: {
938
exactHostMatch: true,
939
},
940
941
microphone: {
942
exactHostMatch: true,
943
},
944
945
screen: {
946
exactHostMatch: true,
947
states: [SitePermissions.UNKNOWN, SitePermissions.BLOCK],
948
},
949
950
popup: {
951
getDefault() {
952
return Services.prefs.getBoolPref("dom.disable_open_during_load")
953
? SitePermissions.BLOCK
954
: SitePermissions.ALLOW;
955
},
956
states: [SitePermissions.ALLOW, SitePermissions.BLOCK],
957
},
958
959
install: {
960
getDefault() {
961
return Services.prefs.getBoolPref("xpinstall.whitelist.required")
962
? SitePermissions.UNKNOWN
963
: SitePermissions.ALLOW;
964
},
965
},
966
967
geo: {
968
exactHostMatch: true,
969
},
970
971
"focus-tab-by-prompt": {
972
exactHostMatch: true,
973
states: [SitePermissions.UNKNOWN, SitePermissions.ALLOW],
974
},
975
"persistent-storage": {
976
exactHostMatch: true,
977
},
978
979
shortcuts: {
980
states: [SitePermissions.ALLOW, SitePermissions.BLOCK],
981
},
982
983
canvas: {},
984
985
midi: {
986
exactHostMatch: true,
987
},
988
989
"midi-sysex": {
990
exactHostMatch: true,
991
},
992
993
"storage-access": {
994
labelID: null,
995
getDefault() {
996
return SitePermissions.UNKNOWN;
997
},
998
},
999
};
1000
1001
if (!Services.prefs.getBoolPref("dom.webmidi.enabled")) {
1002
// ESLint gets angry about array versus dot notation here, but some permission
1003
// names use hyphens. Disabling rule for line to keep things consistent.
1004
// eslint-disable-next-line dot-notation
1005
delete gPermissionObject["midi"];
1006
delete gPermissionObject["midi-sysex"];
1007
}
1008
1009
XPCOMUtils.defineLazyPreferenceGetter(
1010
SitePermissions,
1011
"temporaryPermissionExpireTime",
1012
"privacy.temporary_permission_expire_time_ms",
1013
3600 * 1000
1014
);
1015
XPCOMUtils.defineLazyPreferenceGetter(
1016
SitePermissions,
1017
"resistFingerprinting",
1018
"privacy.resistFingerprinting",
1019
false,
1020
SitePermissions.onResistFingerprintingChanged.bind(SitePermissions)
1021
);