tor-browser

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

GeckoViewStorageController.sys.mjs (11009B)


      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 file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
      6 import { GeckoViewUtils } from "resource://gre/modules/GeckoViewUtils.sys.mjs";
      7 
      8 const lazy = {};
      9 
     10 ChromeUtils.defineESModuleGetters(lazy, {
     11  E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs",
     12  PrincipalsCollector: "resource://gre/modules/PrincipalsCollector.sys.mjs",
     13 });
     14 
     15 XPCOMUtils.defineLazyPreferenceGetter(
     16  lazy,
     17  "serviceMode",
     18  "cookiebanners.service.mode",
     19  Ci.nsICookieBannerService.MODE_DISABLED
     20 );
     21 
     22 XPCOMUtils.defineLazyPreferenceGetter(
     23  lazy,
     24  "serviceModePBM",
     25  "cookiebanners.service.mode.privateBrowsing",
     26  Ci.nsICookieBannerService.MODE_DISABLED
     27 );
     28 
     29 const { debug, warn } = GeckoViewUtils.initLogging(
     30  "GeckoViewStorageController"
     31 );
     32 
     33 // Keep in sync with StorageController.ClearFlags and nsIClearDataService.idl.
     34 const ClearFlags = [
     35  [
     36    // COOKIES
     37    1 << 0,
     38    Ci.nsIClearDataService.CLEAR_COOKIES |
     39      Ci.nsIClearDataService.CLEAR_MEDIA_DEVICES |
     40      Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXECUTED_RECORD |
     41      Ci.nsIClearDataService.CLEAR_FINGERPRINTING_PROTECTION_STATE |
     42      Ci.nsIClearDataService.CLEAR_BOUNCE_TRACKING_PROTECTION_STATE,
     43  ],
     44  [
     45    // NETWORK_CACHE
     46    1 << 1,
     47    Ci.nsIClearDataService.CLEAR_NETWORK_CACHE,
     48  ],
     49  [
     50    // IMAGE_CACHE
     51    1 << 2,
     52    Ci.nsIClearDataService.CLEAR_IMAGE_CACHE,
     53  ],
     54  [
     55    // HISTORY
     56    1 << 3,
     57    Ci.nsIClearDataService.CLEAR_HISTORY,
     58  ],
     59  [
     60    // DOM_STORAGES
     61    1 << 4,
     62    Ci.nsIClearDataService.CLEAR_DOM_QUOTA |
     63      Ci.nsIClearDataService.CLEAR_DOM_PUSH_NOTIFICATIONS |
     64      Ci.nsIClearDataService.CLEAR_REPORTS |
     65      Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXECUTED_RECORD |
     66      Ci.nsIClearDataService.CLEAR_FINGERPRINTING_PROTECTION_STATE |
     67      Ci.nsIClearDataService.CLEAR_BOUNCE_TRACKING_PROTECTION_STATE,
     68  ],
     69  [
     70    // AUTH_SESSIONS
     71    1 << 5,
     72    Ci.nsIClearDataService.CLEAR_AUTH_TOKENS |
     73      Ci.nsIClearDataService.CLEAR_AUTH_CACHE,
     74  ],
     75  [
     76    // PERMISSIONS
     77    1 << 6,
     78    Ci.nsIClearDataService.CLEAR_PERMISSIONS,
     79  ],
     80  [
     81    // SITE_SETTINGS
     82    1 << 7,
     83    Ci.nsIClearDataService.CLEAR_CONTENT_PREFERENCES |
     84      Ci.nsIClearDataService.CLEAR_DOM_PUSH_NOTIFICATIONS |
     85      // former a part of SECURITY_SETTINGS_CLEANER
     86      Ci.nsIClearDataService.CLEAR_CLIENT_AUTH_REMEMBER_SERVICE |
     87      Ci.nsIClearDataService.CLEAR_FINGERPRINTING_PROTECTION_STATE,
     88  ],
     89  [
     90    // SITE_DATA
     91    1 << 8,
     92    Ci.nsIClearDataService.CLEAR_EME,
     93    // former a part of SECURITY_SETTINGS_CLEANER
     94    Ci.nsIClearDataService.CLEAR_HSTS,
     95  ],
     96  [
     97    // ALL
     98    1 << 9,
     99    Ci.nsIClearDataService.CLEAR_ALL,
    100  ],
    101 ];
    102 
    103 function convertFlags(aJavaFlags) {
    104  const flags = ClearFlags.filter(cf => {
    105    return cf[0] & aJavaFlags;
    106  }).reduce((acc, cf) => {
    107    return acc | cf[1];
    108  }, 0);
    109  return flags;
    110 }
    111 
    112 export const GeckoViewStorageController = {
    113  onEvent(aEvent, aData, aCallback) {
    114    debug`onEvent ${aEvent} ${aData}`;
    115 
    116    switch (aEvent) {
    117      case "GeckoView:ClearData": {
    118        this.clearData(aData.flags, aCallback);
    119        break;
    120      }
    121      case "GeckoView:ClearSessionContextData": {
    122        this.clearSessionContextData(aData.contextId);
    123        break;
    124      }
    125      case "GeckoView:ClearHostData": {
    126        this.clearHostData(aData.host, aData.flags, aCallback);
    127        break;
    128      }
    129      case "GeckoView:ClearBaseDomainData": {
    130        this.clearBaseDomainData(aData.baseDomain, aData.flags, aCallback);
    131        break;
    132      }
    133      case "GeckoView:GetAllPermissions": {
    134        const rawPerms = Services.perms.all;
    135        const permissions = rawPerms.map(p => {
    136          return {
    137            uri: Services.io.createExposableURI(p.principal.URI).displaySpec,
    138            principal: lazy.E10SUtils.serializePrincipal(p.principal),
    139            perm: p.type,
    140            value: p.capability,
    141            contextId: p.principal.originAttributes.geckoViewSessionContextId,
    142            privateMode: p.principal.privateBrowsingId != 0,
    143          };
    144        });
    145        aCallback.onSuccess({ permissions });
    146        break;
    147      }
    148      case "GeckoView:GetPermissionsByURI": {
    149        const uri = Services.io.newURI(aData.uri);
    150        const principal = Services.scriptSecurityManager.createContentPrincipal(
    151          uri,
    152          aData.contextId
    153            ? {
    154                geckoViewSessionContextId: aData.contextId,
    155                privateBrowsingId: aData.privateBrowsingId,
    156              }
    157            : { privateBrowsingId: aData.privateBrowsingId }
    158        );
    159        const rawPerms = Services.perms.getAllForPrincipal(principal);
    160        const permissions = rawPerms.map(p => {
    161          return {
    162            uri: Services.io.createExposableURI(p.principal.URI).displaySpec,
    163            principal: lazy.E10SUtils.serializePrincipal(p.principal),
    164            perm: p.type,
    165            value: p.capability,
    166            contextId: p.principal.originAttributes.geckoViewSessionContextId,
    167            privateMode: p.principal.privateBrowsingId != 0,
    168          };
    169        });
    170        aCallback.onSuccess({ permissions });
    171        break;
    172      }
    173      case "GeckoView:SetPermission": {
    174        const principal = lazy.E10SUtils.deserializePrincipal(aData.principal);
    175        let key = aData.perm;
    176        if (key == "storage-access") {
    177          key = "3rdPartyFrameStorage^" + aData.thirdPartyOrigin;
    178        }
    179        if (aData.allowPermanentPrivateBrowsing) {
    180          Services.perms.addFromPrincipalAndPersistInPrivateBrowsing(
    181            principal,
    182            key,
    183            aData.newValue
    184          );
    185        } else {
    186          const expirePolicy = aData.privateMode
    187            ? Ci.nsIPermissionManager.EXPIRE_SESSION
    188            : Ci.nsIPermissionManager.EXPIRE_NEVER;
    189          Services.perms.addFromPrincipal(
    190            principal,
    191            key,
    192            aData.newValue,
    193            expirePolicy
    194          );
    195        }
    196        break;
    197      }
    198      case "GeckoView:SetPermissionByURI": {
    199        const uri = Services.io.newURI(aData.uri);
    200        const expirePolicy = aData.privateId
    201          ? Ci.nsIPermissionManager.EXPIRE_SESSION
    202          : Ci.nsIPermissionManager.EXPIRE_NEVER;
    203        const principal = Services.scriptSecurityManager.createContentPrincipal(
    204          uri,
    205          {
    206            geckoViewSessionContextId: aData.contextId ?? undefined,
    207            privateBrowsingId: aData.privateId,
    208          }
    209        );
    210        Services.perms.addFromPrincipal(
    211          principal,
    212          aData.perm,
    213          aData.newValue,
    214          expirePolicy
    215        );
    216        break;
    217      }
    218 
    219      case "GeckoView:SetCookieBannerModeForDomain": {
    220        let exceptionLabel = "SetCookieBannerModeForDomain";
    221        try {
    222          const uri = Services.io.newURI(aData.uri);
    223          if (aData.allowPermanentPrivateBrowsing) {
    224            exceptionLabel = "setDomainPrefAndPersistInPrivateBrowsing";
    225            Services.cookieBanners.setDomainPrefAndPersistInPrivateBrowsing(
    226              uri,
    227              aData.mode
    228            );
    229          } else {
    230            Services.cookieBanners.setDomainPref(
    231              uri,
    232              aData.mode,
    233              aData.isPrivateBrowsing
    234            );
    235          }
    236          aCallback.onSuccess();
    237        } catch (ex) {
    238          debug`Failed ${exceptionLabel} ${ex}`;
    239        }
    240        break;
    241      }
    242 
    243      case "GeckoView:RemoveCookieBannerModeForDomain": {
    244        try {
    245          const uri = Services.io.newURI(aData.uri);
    246          Services.cookieBanners.removeDomainPref(uri, aData.isPrivateBrowsing);
    247          aCallback.onSuccess();
    248        } catch (ex) {
    249          debug`Failed RemoveCookieBannerModeForDomain ${ex}`;
    250        }
    251        break;
    252      }
    253 
    254      case "GeckoView:GetCookieBannerModeForDomain": {
    255        try {
    256          let globalMode;
    257          if (aData.isPrivateBrowsing) {
    258            globalMode = lazy.serviceModePBM;
    259          } else {
    260            globalMode = lazy.serviceMode;
    261          }
    262 
    263          if (globalMode === Ci.nsICookieBannerService.MODE_DISABLED) {
    264            aCallback.onSuccess({ mode: globalMode });
    265            return;
    266          }
    267 
    268          const uri = Services.io.newURI(aData.uri);
    269          const mode = Services.cookieBanners.getDomainPref(
    270            uri,
    271            aData.isPrivateBrowsing
    272          );
    273          if (mode !== Ci.nsICookieBannerService.MODE_UNSET) {
    274            aCallback.onSuccess({ mode });
    275          } else {
    276            aCallback.onSuccess({ mode: globalMode });
    277          }
    278        } catch (ex) {
    279          aCallback.onError(`Unexpected error: ${ex}`);
    280          debug`Failed GetCookieBannerModeForDomain ${ex}`;
    281        }
    282        break;
    283      }
    284    }
    285  },
    286 
    287  async clearData(aFlags, aCallback) {
    288    const flags = convertFlags(aFlags);
    289 
    290    // storageAccessAPI permissions record every site that the user
    291    // interacted with and thus mirror history quite closely. It makes
    292    // sense to clear them when we clear history. However, since their absence
    293    // indicates that we can purge cookies and site data for tracking origins without
    294    // user interaction, we need to ensure that we only delete those permissions that
    295    // do not have any existing storage.
    296    if (flags & Ci.nsIClearDataService.CLEAR_HISTORY) {
    297      const principalsCollector = new lazy.PrincipalsCollector();
    298      const principals = await principalsCollector.getAllPrincipals();
    299      await new Promise(resolve => {
    300        Services.clearData.deleteUserInteractionForClearingHistory(
    301          principals,
    302          0,
    303          resolve
    304        );
    305      });
    306    }
    307 
    308    new Promise(resolve => {
    309      Services.clearData.deleteData(flags, resolve);
    310    }).then(() => {
    311      aCallback.onSuccess();
    312    });
    313  },
    314 
    315  clearHostData(aHost, aFlags, aCallback) {
    316    new Promise(resolve => {
    317      Services.clearData.deleteDataFromHost(
    318        aHost,
    319        /* isUserRequest */ true,
    320        convertFlags(aFlags),
    321        resolve
    322      );
    323    }).then(() => {
    324      aCallback.onSuccess();
    325    });
    326  },
    327 
    328  clearBaseDomainData(aBaseDomain, aFlags, aCallback) {
    329    // Ensure we have a site at this point.
    330    let schemelessSite = Services.eTLD.getSchemelessSiteFromHost(aBaseDomain);
    331    new Promise(resolve => {
    332      Services.clearData.deleteDataFromSite(
    333        schemelessSite,
    334        {},
    335        /* isUserRequest */ true,
    336        convertFlags(aFlags),
    337        resolve
    338      );
    339    }).then(() => {
    340      aCallback.onSuccess();
    341    });
    342  },
    343 
    344  clearSessionContextData(aContextId) {
    345    const pattern = { geckoViewSessionContextId: aContextId };
    346    debug`clearSessionContextData ${pattern}`;
    347    Services.clearData.deleteDataFromOriginAttributesPattern(pattern);
    348    // Call QMS explicitly to work around bug 1537882.
    349    Services.qms.clearStoragesForOriginAttributesPattern(
    350      JSON.stringify(pattern)
    351    );
    352  },
    353 };