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 };