tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

trackingProtection.js (10871B)


      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 "use strict";
      6 
      7 /* global ExtensionAPI, ExtensionCommon, ExtensionParent, Services, XPCOMUtils */
      8 
      9 // eslint-disable-next-line mozilla/reject-importGlobalProperties
     10 XPCOMUtils.defineLazyGlobalGetters(this, ["URL", "ChannelWrapper"]);
     11 
     12 class AllowList {
     13  constructor(id) {
     14    this._id = id;
     15  }
     16 
     17  setShims(patterns, notHosts) {
     18    this._shimPatterns = patterns;
     19    this._shimMatcher = new MatchPatternSet(patterns || []);
     20    this._shimNotHosts = notHosts || [];
     21    return this;
     22  }
     23 
     24  setAllows(patterns, hosts) {
     25    this._allowPatterns = patterns;
     26    this._allowMatcher = new MatchPatternSet(patterns || []);
     27    this._allowHosts = hosts || [];
     28    return this;
     29  }
     30 
     31  shims(url, topHost) {
     32    return (
     33      this._shimMatcher?.matches(url) && !this._shimNotHosts?.includes(topHost)
     34    );
     35  }
     36 
     37  allows(url, topHost) {
     38    return (
     39      this._allowMatcher?.matches(url) && this._allowHosts?.includes(topHost)
     40    );
     41  }
     42 }
     43 
     44 class Manager {
     45  constructor() {
     46    this._allowLists = new Map();
     47    this._PBModeAllowLists = new Map();
     48  }
     49 
     50  _getAllowList(id, isPrivateMode) {
     51    const activeAllowLists = isPrivateMode
     52      ? this._PBModeAllowLists
     53      : this._allowLists;
     54 
     55    if (!activeAllowLists.has(id)) {
     56      activeAllowLists.set(id, new AllowList(id));
     57    }
     58    return activeAllowLists.get(id);
     59  }
     60 
     61  _ensureStarted() {
     62    if (this._classifierObserver) {
     63      return;
     64    }
     65 
     66    this._unblockedChannelIds = new Set();
     67    this._PBModeUnblockedChannelIds = new Set();
     68    this._channelClassifier = Cc[
     69      "@mozilla.org/url-classifier/channel-classifier-service;1"
     70    ].getService(Ci.nsIChannelClassifierService);
     71    this._classifierObserver = {};
     72    this._classifierObserver.observe = (subject, topic) => {
     73      switch (topic) {
     74        case "http-on-stop-request": {
     75          const { channelId } = subject.QueryInterface(Ci.nsIIdentChannel);
     76          const isPrivateMode =
     77            subject.loadInfo.browsingContext?.originAttributes
     78              ?.privateBrowsingId;
     79          if (isPrivateMode) {
     80            this._PBModeUnblockedChannelIds.delete(channelId);
     81          } else {
     82            this._unblockedChannelIds.delete(channelId);
     83          }
     84          break;
     85        }
     86        case "urlclassifier-before-block-channel": {
     87          const channel = subject.QueryInterface(
     88            Ci.nsIUrlClassifierBlockedChannel
     89          );
     90          const isPrivateMode = subject.isPrivateBrowsing;
     91          const { channelId, url } = channel;
     92          let topHost;
     93          try {
     94            topHost = new URL(channel.topLevelUrl).hostname;
     95          } catch (_) {
     96            return;
     97          }
     98          const activeAllowLists = isPrivateMode
     99            ? this._PBModeAllowLists
    100            : this._allowLists;
    101          const activeUnblockedChannelIds = isPrivateMode
    102            ? this._PBModeUnblockedChannelIds
    103            : this._unblockedChannelIds;
    104          // If anti-tracking webcompat is disabled, we only permit replacing
    105          // channels, not fully unblocking them.
    106          if (Manager.ENABLE_WEBCOMPAT) {
    107            // if any allowlist unblocks the request entirely, we allow it
    108            for (const allowList of activeAllowLists.values()) {
    109              if (allowList.allows(url, topHost)) {
    110                activeUnblockedChannelIds.add(channelId);
    111                channel.allow();
    112                return;
    113              }
    114            }
    115          }
    116          // otherwise, if any allowlist shims the request we say it's replaced
    117          for (const allowList of activeAllowLists.values()) {
    118            if (allowList.shims(url, topHost)) {
    119              activeUnblockedChannelIds.add(channelId);
    120              channel.replace();
    121              return;
    122            }
    123          }
    124          break;
    125        }
    126      }
    127    };
    128    Services.obs.addObserver(this._classifierObserver, "http-on-stop-request");
    129    this._channelClassifier.addListener(this._classifierObserver);
    130  }
    131 
    132  stop() {
    133    if (!this._classifierObserver) {
    134      return;
    135    }
    136 
    137    Services.obs.removeObserver(
    138      this._classifierObserver,
    139      "http-on-stop-request"
    140    );
    141    this._channelClassifier.removeListener(this._classifierObserver);
    142    delete this._channelClassifier;
    143    delete this._classifierObserver;
    144  }
    145 
    146  wasChannelIdUnblocked(channelId, isPrivateMode) {
    147    const activeUnblockedChannelIds = isPrivateMode
    148      ? this._PBModeUnblockedChannelIds
    149      : this._unblockedChannelIds;
    150    return activeUnblockedChannelIds?.has(channelId);
    151  }
    152 
    153  allow(allowListId, patterns, isPrivateMode, hosts) {
    154    this._ensureStarted();
    155    this._getAllowList(allowListId, isPrivateMode).setAllows(patterns, hosts);
    156  }
    157 
    158  shim(allowListId, patterns, isPrivateMode, notHosts) {
    159    this._ensureStarted();
    160    this._getAllowList(allowListId, isPrivateMode).setShims(patterns, notHosts);
    161  }
    162 
    163  revoke(allowListId) {
    164    this._allowLists.delete(allowListId);
    165    this._PBModeAllowLists.delete(allowListId);
    166  }
    167 }
    168 var manager = new Manager();
    169 
    170 function getChannelId(context, requestId) {
    171  const wrapper = ChannelWrapper.getRegisteredChannel(
    172    requestId,
    173    context.extension.policy,
    174    context.xulBrowser.frameLoader.remoteTab
    175  );
    176  return wrapper?.channel?.QueryInterface(Ci.nsIIdentChannel)?.channelId;
    177 }
    178 
    179 var dFPIPrefName = "network.cookie.cookieBehavior";
    180 var dFPIPbPrefName = "network.cookie.cookieBehavior.pbmode";
    181 var dFPIStatus;
    182 function updateDFPIStatus() {
    183  dFPIStatus = {
    184    nonPbMode: 5 == Services.prefs.getIntPref(dFPIPrefName),
    185    pbMode: 5 == Services.prefs.getIntPref(dFPIPbPrefName),
    186  };
    187 }
    188 
    189 this.trackingProtection = class extends ExtensionAPI {
    190  onShutdown() {
    191    if (manager) {
    192      manager.stop();
    193    }
    194    Services.prefs.removeObserver(dFPIPrefName, updateDFPIStatus);
    195    Services.prefs.removeObserver(dFPIPbPrefName, updateDFPIStatus);
    196  }
    197 
    198  getAPI(context) {
    199    const {
    200      extension: { tabManager },
    201    } = this;
    202    const EventManager = ExtensionCommon.EventManager;
    203    Services.prefs.addObserver(dFPIPrefName, updateDFPIStatus);
    204    Services.prefs.addObserver(dFPIPbPrefName, updateDFPIStatus);
    205    updateDFPIStatus();
    206 
    207    return {
    208      trackingProtection: {
    209        onSmartBlockEmbedUnblock: new EventManager({
    210          context,
    211          name: "trackingProtection.onSmartBlockEmbedUnblock",
    212          register: fire => {
    213            const callback = (subject, topic, data) => {
    214              // chrome tab id needs to be converted to extension tab id
    215              let hostname = subject.linkedBrowser.currentURI.host;
    216              let tabId = tabManager.convert(subject).id;
    217              fire.sync(tabId, data, hostname);
    218            };
    219            Services.obs.addObserver(callback, "smartblock:unblock-embed");
    220            return () => {
    221              Services.obs.removeObserver(callback, "smartblock:unblock-embed");
    222            };
    223          },
    224        }).api(),
    225        onSmartBlockEmbedReblock: new EventManager({
    226          context,
    227          name: "trackingProtection.onSmartBlockEmbedReblock",
    228          register: fire => {
    229            const callback = (subject, _topic, data) => {
    230              // chrome tab id needs to be converted to extension tab id
    231              let hostname = subject.linkedBrowser.currentURI.host;
    232              let tabId = tabManager.convert(subject).id;
    233              fire.sync(tabId, data, hostname);
    234            };
    235            Services.obs.addObserver(callback, "smartblock:reblock-embed");
    236            return () => {
    237              Services.obs.removeObserver(callback, "smartblock:reblock-embed");
    238            };
    239          },
    240        }).api(),
    241        onPrivateSessionEnd: new EventManager({
    242          context,
    243          name: "trackingProtection.onPrivateSessionEnd",
    244          register: fire => {
    245            const callback = (_subject, _topic) => {
    246              fire.sync();
    247            };
    248            Services.obs.addObserver(callback, "last-pb-context-exited");
    249            return () => {
    250              Services.obs.removeObserver(callback, "last-pb-context-exited");
    251            };
    252          },
    253        }).api(),
    254        async shim(allowListId, patterns, notHosts) {
    255          // shim for both PB and non-PB modes
    256          manager.shim(allowListId, patterns, true, notHosts);
    257          manager.shim(allowListId, patterns, false, notHosts);
    258        },
    259        async allow(allowListId, patterns, isPrivate, hosts) {
    260          manager.allow(allowListId, patterns, isPrivate, hosts);
    261        },
    262        async revoke(allowListId) {
    263          manager.revoke(allowListId);
    264        },
    265        async clearResourceCache() {
    266          ChromeUtils.clearResourceCache({ target: "content" });
    267        },
    268        async wasRequestUnblocked(requestId, isPrivate) {
    269          if (!manager) {
    270            return false;
    271          }
    272          const channelId = getChannelId(context, requestId);
    273          if (!channelId) {
    274            return false;
    275          }
    276          return manager.wasChannelIdUnblocked(channelId, isPrivate);
    277        },
    278        async isDFPIActive(isPrivate) {
    279          if (isPrivate) {
    280            return dFPIStatus.pbMode;
    281          }
    282          return dFPIStatus.nonPbMode;
    283        },
    284        openProtectionsPanel(tabId) {
    285          let tab = tabManager.get(tabId);
    286          if (!tab?.active) {
    287            // break if tab is not the active tab
    288            return;
    289          }
    290 
    291          let win = tab?.window;
    292          Services.obs.notifyObservers(
    293            win.gBrowser.selectedBrowser.browsingContext,
    294            "smartblock:open-protections-panel"
    295          );
    296        },
    297        incrementSmartblockEmbedShownTelemetry() {
    298          Glean.securityUiProtectionspopup.smartblockembedsShown.add();
    299        },
    300        async getSmartBlockEmbedFluentString(tabId, shimId, websiteHost) {
    301          let win = tabManager.get(tabId).window;
    302          let document = win.document;
    303 
    304          let { gProtectionsHandler } = win.gBrowser.ownerGlobal;
    305          let { displayName } = gProtectionsHandler.smartblockEmbedInfo.find(
    306            element => element.shimId == shimId
    307          );
    308 
    309          let fluentArgs = [
    310            {
    311              id: "smartblock-placeholder-title",
    312              args: {
    313                trackername: displayName,
    314              },
    315            },
    316            {
    317              id: "smartblock-placeholder-desc",
    318            },
    319            {
    320              id: "smartblock-placeholder-button-text",
    321              args: { websitehost: websiteHost },
    322            },
    323          ];
    324 
    325          return document.l10n.formatValues(fluentArgs);
    326        },
    327      },
    328    };
    329  }
    330 };
    331 
    332 XPCOMUtils.defineLazyPreferenceGetter(
    333  Manager,
    334  "ENABLE_WEBCOMPAT",
    335  "privacy.antitracking.enableWebcompat",
    336  false
    337 );