ContentBlockingPrefs.sys.mjs (20854B)
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 const PREFS_CHANGING_CATEGORY = new Set([ 6 "network.cookie.cookieBehavior", 7 "network.cookie.cookieBehavior.pbmode", 8 "network.http.referer.disallowCrossSiteRelaxingDefault", 9 "network.http.referer.disallowCrossSiteRelaxingDefault.top_navigation", 10 "privacy.partition.network_state.ocsp_cache", 11 "privacy.query_stripping.enabled", 12 "privacy.query_stripping.enabled.pbmode", 13 "privacy.fingerprintingProtection", 14 "privacy.fingerprintingProtection.pbmode", 15 ]); 16 17 /** 18 * @class ContentBlockingPrefs 19 * 20 * Manages how the content blocking and anti-tracking preferences relate to the 21 * broad Tracking Protection categories (standard, strict and custom). 22 * 23 * @typedef {"standard"|"strict"|"custom"} CBCategory 24 */ 25 export let ContentBlockingPrefs = { 26 PREF_CB_CATEGORY: "browser.contentblocking.category", 27 PREF_STRICT_DEF: "browser.contentblocking.features.strict", 28 PREF_ALLOW_LIST_BASELINE: 29 "privacy.trackingprotection.allow_list.baseline.enabled", 30 PREF_ALLOW_LIST_CONVENIENCE: 31 "privacy.trackingprotection.allow_list.convenience.enabled", 32 PREF_LNA_ETP_ENABLED: "network.lna.etp.enabled", 33 34 switchingCategory: false, 35 36 /** 37 * Apply a category preference rule to update preference expectations. * 38 * 39 * @param {string} item - The rule to apply (e.g., "tp", "-fp", "lna") 40 * @param {string} type - The category type ("strict", "standard") 41 */ 42 // eslint-disable-next-line complexity 43 applyCategoryPref(item, type) { 44 switch (item) { 45 case "tp": 46 this.CATEGORY_PREFS[type]["privacy.trackingprotection.enabled"] = true; 47 break; 48 case "-tp": 49 this.CATEGORY_PREFS[type]["privacy.trackingprotection.enabled"] = false; 50 break; 51 case "tpPrivate": 52 this.CATEGORY_PREFS[type]["privacy.trackingprotection.pbmode.enabled"] = 53 true; 54 break; 55 case "-tpPrivate": 56 this.CATEGORY_PREFS[type]["privacy.trackingprotection.pbmode.enabled"] = 57 false; 58 break; 59 case "fp": 60 this.CATEGORY_PREFS[type][ 61 "privacy.trackingprotection.fingerprinting.enabled" 62 ] = true; 63 break; 64 case "-fp": 65 this.CATEGORY_PREFS[type][ 66 "privacy.trackingprotection.fingerprinting.enabled" 67 ] = false; 68 break; 69 case "cryptoTP": 70 this.CATEGORY_PREFS[type][ 71 "privacy.trackingprotection.cryptomining.enabled" 72 ] = true; 73 break; 74 case "-cryptoTP": 75 this.CATEGORY_PREFS[type][ 76 "privacy.trackingprotection.cryptomining.enabled" 77 ] = false; 78 break; 79 case "stp": 80 this.CATEGORY_PREFS[type][ 81 "privacy.trackingprotection.socialtracking.enabled" 82 ] = true; 83 break; 84 case "-stp": 85 this.CATEGORY_PREFS[type][ 86 "privacy.trackingprotection.socialtracking.enabled" 87 ] = false; 88 break; 89 case "emailTP": 90 this.CATEGORY_PREFS[type][ 91 "privacy.trackingprotection.emailtracking.enabled" 92 ] = true; 93 break; 94 case "-emailTP": 95 this.CATEGORY_PREFS[type][ 96 "privacy.trackingprotection.emailtracking.enabled" 97 ] = false; 98 break; 99 case "emailTPPrivate": 100 this.CATEGORY_PREFS[type][ 101 "privacy.trackingprotection.emailtracking.pbmode.enabled" 102 ] = true; 103 break; 104 case "-emailTPPrivate": 105 this.CATEGORY_PREFS[type][ 106 "privacy.trackingprotection.emailtracking.pbmode.enabled" 107 ] = false; 108 break; 109 case "consentmanagerSkip": 110 this.CATEGORY_PREFS[type][ 111 "privacy.trackingprotection.consentmanager.skip.enabled" 112 ] = true; 113 break; 114 case "-consentmanagerSkip": 115 this.CATEGORY_PREFS[type][ 116 "privacy.trackingprotection.consentmanager.skip.enabled" 117 ] = false; 118 break; 119 case "consentmanagerSkipPrivate": 120 this.CATEGORY_PREFS[type][ 121 "privacy.trackingprotection.consentmanager.skip.pbmode.enabled" 122 ] = true; 123 break; 124 case "-consentmanagerSkipPrivate": 125 this.CATEGORY_PREFS[type][ 126 "privacy.trackingprotection.consentmanager.skip.pbmode.enabled" 127 ] = false; 128 break; 129 case "lvl2": 130 this.CATEGORY_PREFS[type][ 131 "privacy.annotate_channels.strict_list.enabled" 132 ] = true; 133 break; 134 case "-lvl2": 135 this.CATEGORY_PREFS[type][ 136 "privacy.annotate_channels.strict_list.enabled" 137 ] = false; 138 break; 139 case "rp": 140 this.CATEGORY_PREFS[type][ 141 "network.http.referer.disallowCrossSiteRelaxingDefault" 142 ] = true; 143 break; 144 case "-rp": 145 this.CATEGORY_PREFS[type][ 146 "network.http.referer.disallowCrossSiteRelaxingDefault" 147 ] = false; 148 break; 149 case "rpTop": 150 this.CATEGORY_PREFS[type][ 151 "network.http.referer.disallowCrossSiteRelaxingDefault.top_navigation" 152 ] = true; 153 break; 154 case "-rpTop": 155 this.CATEGORY_PREFS[type][ 156 "network.http.referer.disallowCrossSiteRelaxingDefault.top_navigation" 157 ] = false; 158 break; 159 case "ocsp": 160 this.CATEGORY_PREFS[type][ 161 "privacy.partition.network_state.ocsp_cache" 162 ] = true; 163 break; 164 case "-ocsp": 165 this.CATEGORY_PREFS[type][ 166 "privacy.partition.network_state.ocsp_cache" 167 ] = false; 168 break; 169 case "qps": 170 this.CATEGORY_PREFS[type]["privacy.query_stripping.enabled"] = true; 171 break; 172 case "-qps": 173 this.CATEGORY_PREFS[type]["privacy.query_stripping.enabled"] = false; 174 break; 175 case "qpsPBM": 176 this.CATEGORY_PREFS[type]["privacy.query_stripping.enabled.pbmode"] = 177 true; 178 break; 179 case "-qpsPBM": 180 this.CATEGORY_PREFS[type]["privacy.query_stripping.enabled.pbmode"] = 181 false; 182 break; 183 case "fpp": 184 this.CATEGORY_PREFS[type]["privacy.fingerprintingProtection"] = true; 185 break; 186 case "-fpp": 187 this.CATEGORY_PREFS[type]["privacy.fingerprintingProtection"] = false; 188 break; 189 case "fppPrivate": 190 this.CATEGORY_PREFS[type]["privacy.fingerprintingProtection.pbmode"] = 191 true; 192 break; 193 case "-fppPrivate": 194 this.CATEGORY_PREFS[type]["privacy.fingerprintingProtection.pbmode"] = 195 false; 196 break; 197 case "cookieBehavior0": 198 this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior"] = 199 Ci.nsICookieService.BEHAVIOR_ACCEPT; 200 break; 201 case "cookieBehavior1": 202 this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior"] = 203 Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN; 204 break; 205 case "cookieBehavior2": 206 this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior"] = 207 Ci.nsICookieService.BEHAVIOR_REJECT; 208 break; 209 case "cookieBehavior3": 210 this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior"] = 211 Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN; 212 break; 213 case "cookieBehavior4": 214 this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior"] = 215 Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER; 216 break; 217 case "cookieBehavior5": 218 this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior"] = 219 Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN; 220 break; 221 case "cookieBehaviorPBM0": 222 this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior.pbmode"] = 223 Ci.nsICookieService.BEHAVIOR_ACCEPT; 224 break; 225 case "cookieBehaviorPBM1": 226 this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior.pbmode"] = 227 Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN; 228 break; 229 case "cookieBehaviorPBM2": 230 this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior.pbmode"] = 231 Ci.nsICookieService.BEHAVIOR_REJECT; 232 break; 233 case "cookieBehaviorPBM3": 234 this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior.pbmode"] = 235 Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN; 236 break; 237 case "cookieBehaviorPBM4": 238 this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior.pbmode"] = 239 Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER; 240 break; 241 case "cookieBehaviorPBM5": 242 this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior.pbmode"] = 243 Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN; 244 break; 245 case "3pcd": 246 this.CATEGORY_PREFS[type][ 247 "network.cookie.cookieBehavior.optInPartitioning" 248 ] = true; 249 break; 250 case "-3pcd": 251 this.CATEGORY_PREFS[type][ 252 "network.cookie.cookieBehavior.optInPartitioning" 253 ] = false; 254 break; 255 case "btp": 256 this.CATEGORY_PREFS[type]["privacy.bounceTrackingProtection.mode"] = 257 Ci.nsIBounceTrackingProtection.MODE_ENABLED; 258 break; 259 case "-btp": 260 // We currently consider MODE_ENABLED_DRY_RUN the "off" state. See 261 // nsIBounceTrackingProtection.idl for details. 262 this.CATEGORY_PREFS[type]["privacy.bounceTrackingProtection.mode"] = 263 Ci.nsIBounceTrackingProtection.MODE_ENABLED_DRY_RUN; 264 break; 265 case "lna": 266 // turn on LNA for etp strict only if network.lna.etp.enabled 267 // network.lna.etp.enabled is controlled by nimbus 268 if (Services.prefs.getBoolPref(this.PREF_LNA_ETP_ENABLED, false)) { 269 this.CATEGORY_PREFS[type]["network.lna.blocking"] = true; 270 } 271 break; 272 case "-lna": 273 // currently LNA is only enabled with ETP strict mode with pref network.lna.etp.enabled 274 if (Services.prefs.getBoolPref(this.PREF_LNA_ETP_ENABLED, false)) { 275 this.CATEGORY_PREFS[type]["network.lna.blocking"] = false; 276 } 277 break; 278 default: 279 console.error(`Error: Unknown rule observed ${item}`); 280 } 281 }, 282 283 setPrefExpectations() { 284 // The prefs inside CATEGORY_PREFS are initial values. 285 // If the pref remains null, then it will expect the default value. 286 // The "standard" category is defined as expecting default values of the 287 // listed prefs. The "strict" category lists all prefs that will be set 288 // according to the strict feature pref. 289 this.CATEGORY_PREFS = { 290 strict: { 291 "network.cookie.cookieBehavior": null, 292 "network.cookie.cookieBehavior.pbmode": null, 293 "privacy.trackingprotection.pbmode.enabled": null, 294 "privacy.trackingprotection.enabled": null, 295 "privacy.trackingprotection.socialtracking.enabled": null, 296 "privacy.trackingprotection.fingerprinting.enabled": null, 297 "privacy.trackingprotection.cryptomining.enabled": null, 298 "privacy.trackingprotection.emailtracking.enabled": null, 299 "privacy.trackingprotection.emailtracking.pbmode.enabled": null, 300 "privacy.trackingprotection.consentmanager.skip.enabled": null, 301 "privacy.trackingprotection.consentmanager.skip.pbmode.enabled": null, 302 "privacy.annotate_channels.strict_list.enabled": null, 303 "network.http.referer.disallowCrossSiteRelaxingDefault": null, 304 "network.http.referer.disallowCrossSiteRelaxingDefault.top_navigation": 305 null, 306 "privacy.partition.network_state.ocsp_cache": null, 307 "privacy.query_stripping.enabled": null, 308 "privacy.query_stripping.enabled.pbmode": null, 309 "privacy.fingerprintingProtection": null, 310 "privacy.fingerprintingProtection.pbmode": null, 311 "network.cookie.cookieBehavior.optInPartitioning": null, 312 "privacy.bounceTrackingProtection.mode": null, 313 "network.lna.blocking": null, 314 [this.PREF_ALLOW_LIST_BASELINE]: true, 315 [this.PREF_ALLOW_LIST_CONVENIENCE]: false, 316 }, 317 standard: { 318 "network.cookie.cookieBehavior": null, 319 "network.cookie.cookieBehavior.pbmode": null, 320 "privacy.trackingprotection.pbmode.enabled": null, 321 "privacy.trackingprotection.enabled": null, 322 "privacy.trackingprotection.socialtracking.enabled": null, 323 "privacy.trackingprotection.fingerprinting.enabled": null, 324 "privacy.trackingprotection.cryptomining.enabled": null, 325 "privacy.trackingprotection.emailtracking.enabled": null, 326 "privacy.trackingprotection.emailtracking.pbmode.enabled": null, 327 "privacy.trackingprotection.consentmanager.skip.enabled": null, 328 "privacy.trackingprotection.consentmanager.skip.pbmode.enabled": null, 329 "privacy.annotate_channels.strict_list.enabled": null, 330 "network.http.referer.disallowCrossSiteRelaxingDefault": null, 331 "network.http.referer.disallowCrossSiteRelaxingDefault.top_navigation": 332 null, 333 "privacy.partition.network_state.ocsp_cache": null, 334 "privacy.query_stripping.enabled": null, 335 "privacy.query_stripping.enabled.pbmode": null, 336 "privacy.fingerprintingProtection": null, 337 "privacy.fingerprintingProtection.pbmode": null, 338 "network.cookie.cookieBehavior.optInPartitioning": null, 339 "privacy.bounceTrackingProtection.mode": null, 340 "network.lna.blocking": null, 341 [this.PREF_ALLOW_LIST_BASELINE]: null, 342 [this.PREF_ALLOW_LIST_CONVENIENCE]: null, 343 }, 344 }; 345 let type = "strict"; 346 let rulesArray = Services.prefs 347 .getStringPref(this.PREF_STRICT_DEF) 348 .split(","); 349 for (let item of rulesArray) { 350 this.applyCategoryPref(item, type); 351 } 352 }, 353 354 /** 355 * Checks if CB prefs match perfectly with one of our pre-defined categories. 356 * 357 * @param {CBCategory} category 358 */ 359 prefsMatch(category) { 360 // The category pref must be either unset, or match. 361 if ( 362 Services.prefs.prefHasUserValue(this.PREF_CB_CATEGORY) && 363 Services.prefs.getStringPref(this.PREF_CB_CATEGORY) != category 364 ) { 365 return false; 366 } 367 for (let pref in this.CATEGORY_PREFS[category]) { 368 let value = this.CATEGORY_PREFS[category][pref]; 369 // Ignore allow list prefs, since user is allowed to change them in strict mode. 370 if ( 371 pref == this.PREF_ALLOW_LIST_BASELINE || 372 pref == this.PREF_ALLOW_LIST_CONVENIENCE 373 ) { 374 continue; 375 } 376 if (value == null) { 377 if (Services.prefs.prefHasUserValue(pref)) { 378 return false; 379 } 380 } else { 381 let prefType = Services.prefs.getPrefType(pref); 382 if ( 383 (prefType == Services.prefs.PREF_BOOL && 384 Services.prefs.getBoolPref(pref) != value) || 385 (prefType == Services.prefs.PREF_INT && 386 Services.prefs.getIntPref(pref) != value) || 387 (prefType == Services.prefs.PREF_STRING && 388 Services.prefs.getStringPref(pref) != value) 389 ) { 390 return false; 391 } 392 } 393 } 394 return true; 395 }, 396 397 matchCBCategory() { 398 if (this.switchingCategory) { 399 return; 400 } 401 // If PREF_CB_CATEGORY is not set match users to a Content Blocking category. Check if prefs fit 402 // perfectly into strict or standard, otherwise match with custom. If PREF_CB_CATEGORY has previously been set, 403 // a change of one of these prefs necessarily puts us in "custom". 404 if (this.prefsMatch("standard")) { 405 Services.prefs.setStringPref(this.PREF_CB_CATEGORY, "standard"); 406 } else if (this.prefsMatch("strict")) { 407 Services.prefs.setStringPref(this.PREF_CB_CATEGORY, "strict"); 408 } else { 409 Services.prefs.setStringPref(this.PREF_CB_CATEGORY, "custom"); 410 } 411 412 // If there is a custom policy which changes a related pref, then put the user in custom so 413 // they still have access to other content blocking prefs, and to keep our default definitions 414 // from changing. 415 let policy = Services.policies.getActivePolicies(); 416 if ( 417 policy && 418 ((policy.EnableTrackingProtection && 419 !policy.EnableTrackingProtection.Category) || 420 policy.Cookies) 421 ) { 422 Services.prefs.setStringPref(this.PREF_CB_CATEGORY, "custom"); 423 } 424 }, 425 426 updateCBCategory(preserveAllowListSettings = false) { 427 if ( 428 this.switchingCategory || 429 !Services.prefs.prefHasUserValue(this.PREF_CB_CATEGORY) 430 ) { 431 return; 432 } 433 // Turn on switchingCategory flag, to ensure that when the individual prefs that change as a result 434 // of the category change do not trigger yet another category change. 435 this.switchingCategory = true; 436 let value = Services.prefs.getStringPref(this.PREF_CB_CATEGORY); 437 this.setPrefsToCategory(value, null, preserveAllowListSettings); 438 this.switchingCategory = false; 439 }, 440 441 /** 442 * Sets all user-exposed content blocking preferences to values that match the selected category. 443 * 444 * @param {CBCategory} category 445 * @param {boolean} lockPrefs - Whether to lock prefs after setting them 446 * @param {boolean} preserveAllowListSettings - Whether to preserve existing allow list baseline and 447 * convenience settings 448 */ 449 setPrefsToCategory(category, lockPrefs, preserveAllowListSettings) { 450 // Leave prefs as they were if we are switching to "custom" category. 451 if (category == "custom") { 452 return; 453 } 454 455 let prefBranch = lockPrefs 456 ? Services.prefs.getDefaultBranch(null) 457 : Services.prefs.getBranch(null); 458 459 for (let pref in this.CATEGORY_PREFS[category]) { 460 let value = this.CATEGORY_PREFS[category][pref]; 461 if (!Services.prefs.prefIsLocked(pref)) { 462 if (value == null) { 463 Services.prefs.clearUserPref(pref); 464 } else { 465 // On initialization, do not update "PREF_ALLOW_LIST_BASELINE" and "PREF_ALLOW_LIST_CONVENIENCE" 466 // to make sure user's settings are preserved 467 if ( 468 preserveAllowListSettings && 469 (pref == this.PREF_ALLOW_LIST_BASELINE || 470 pref == this.PREF_ALLOW_LIST_CONVENIENCE) 471 ) { 472 continue; 473 } 474 switch (Services.prefs.getPrefType(pref)) { 475 case Services.prefs.PREF_BOOL: 476 prefBranch.setBoolPref(pref, value); 477 break; 478 case Services.prefs.PREF_INT: 479 prefBranch.setIntPref(pref, value); 480 break; 481 case Services.prefs.PREF_STRING: 482 prefBranch.setStringPref(pref, value); 483 break; 484 } 485 if (lockPrefs) { 486 Services.prefs.lockPref(pref); 487 } 488 } 489 } 490 } 491 }, 492 493 setPrefExpectationsAndUpdate(preserveAllowListSettings = false) { 494 this.setPrefExpectations(); 495 this.updateCBCategory(preserveAllowListSettings); 496 }, 497 498 observe(subject, topic, data) { 499 if (topic != "nsPref:changed") { 500 return; 501 } 502 503 // We need this early return to avoid updating the CB category prematurely. Specifically when 504 // user changes the CB category, the observer for hasUserInteractedWithETPSettings is triggered 505 // before the category is actually changed. If we do not return here, the category may be updated 506 // incorrectly (for example, to custom when it should be strict). 507 if ( 508 data === 509 "privacy.trackingprotection.allow_list.hasUserInteractedWithETPSettings" 510 ) { 511 return; 512 } 513 514 if ( 515 data.startsWith("privacy.trackingprotection") || 516 PREFS_CHANGING_CATEGORY.has(data) 517 ) { 518 this.matchCBCategory(); 519 } 520 521 if (data.startsWith("privacy.trackingprotection")) { 522 this.setPrefExpectations(); 523 } else if (data == this.PREF_CB_CATEGORY) { 524 this.updateCBCategory(); 525 } else if (data == "browser.contentblocking.features.strict") { 526 this.setPrefExpectationsAndUpdate(); 527 } else if (data == this.PREF_LNA_ETP_ENABLED) { 528 // updates tagging of LNA restrictions with ETP strict mode 529 this.setPrefExpectationsAndUpdate(); 530 } 531 }, 532 533 init() { 534 this.setPrefExpectationsAndUpdate(true); 535 this.matchCBCategory(); 536 537 for (let prefix of PREF_PREFIXES_TO_OBSERVE) { 538 Services.prefs.addObserver(prefix, this); 539 } 540 }, 541 542 uninit() { 543 for (let prefix of PREF_PREFIXES_TO_OBSERVE) { 544 Services.prefs.removeObserver(prefix, this); 545 } 546 }, 547 }; 548 549 const PREF_PREFIXES_TO_OBSERVE = new Set([ 550 "privacy.trackingprotection", 551 "network.cookie.cookieBehavior", 552 "network.http.referer.disallowCrossSiteRelaxingDefault", 553 "privacy.partition.network_state.ocsp_cache", 554 "privacy.query_stripping.enabled", 555 "privacy.fingerprintingProtection", 556 ContentBlockingPrefs.PREF_CB_CATEGORY, 557 ContentBlockingPrefs.PREF_STRICT_DEF, 558 ContentBlockingPrefs.PREF_LNA_ETP_ENABLED, 559 ]); 560 561 ContentBlockingPrefs.QueryInterface = ChromeUtils.generateQI([Ci.nsIObserver]);