summaryrefslogtreecommitdiffstats
path: root/mobile/android/modules/geckoview/GeckoViewStorageController.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/modules/geckoview/GeckoViewStorageController.jsm')
-rw-r--r--mobile/android/modules/geckoview/GeckoViewStorageController.jsm353
1 files changed, 353 insertions, 0 deletions
diff --git a/mobile/android/modules/geckoview/GeckoViewStorageController.jsm b/mobile/android/modules/geckoview/GeckoViewStorageController.jsm
new file mode 100644
index 0000000000..02124aa3f1
--- /dev/null
+++ b/mobile/android/modules/geckoview/GeckoViewStorageController.jsm
@@ -0,0 +1,353 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["GeckoViewStorageController"];
+
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+const lazy = {};
+
+XPCOMUtils.defineLazyPreferenceGetter(
+ lazy,
+ "serviceMode",
+ "cookiebanners.service.mode",
+ Ci.nsICookieBannerService.MODE_DISABLED
+);
+
+XPCOMUtils.defineLazyPreferenceGetter(
+ lazy,
+ "serviceModePBM",
+ "cookiebanners.service.mode.privateBrowsing",
+ Ci.nsICookieBannerService.MODE_DISABLED
+);
+const { GeckoViewUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/GeckoViewUtils.sys.mjs"
+);
+const { PrincipalsCollector } = ChromeUtils.import(
+ "resource://gre/modules/PrincipalsCollector.jsm"
+);
+const { E10SUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/E10SUtils.sys.mjs"
+);
+
+const { debug, warn } = GeckoViewUtils.initLogging(
+ "GeckoViewStorageController"
+);
+
+// Keep in sync with StorageController.ClearFlags and nsIClearDataService.idl.
+const ClearFlags = [
+ [
+ // COOKIES
+ 1 << 0,
+ Ci.nsIClearDataService.CLEAR_COOKIES |
+ Ci.nsIClearDataService.CLEAR_MEDIA_DEVICES,
+ ],
+ [
+ // NETWORK_CACHE
+ 1 << 1,
+ Ci.nsIClearDataService.CLEAR_NETWORK_CACHE,
+ ],
+ [
+ // IMAGE_CACHE
+ 1 << 2,
+ Ci.nsIClearDataService.CLEAR_IMAGE_CACHE,
+ ],
+ [
+ // HISTORY
+ 1 << 3,
+ Ci.nsIClearDataService.CLEAR_HISTORY |
+ Ci.nsIClearDataService.CLEAR_SESSION_HISTORY,
+ ],
+ [
+ // DOM_STORAGES
+ 1 << 4,
+ Ci.nsIClearDataService.CLEAR_DOM_QUOTA |
+ Ci.nsIClearDataService.CLEAR_DOM_PUSH_NOTIFICATIONS |
+ Ci.nsIClearDataService.CLEAR_REPORTS,
+ ],
+ [
+ // AUTH_SESSIONS
+ 1 << 5,
+ Ci.nsIClearDataService.CLEAR_AUTH_TOKENS |
+ Ci.nsIClearDataService.CLEAR_AUTH_CACHE,
+ ],
+ [
+ // PERMISSIONS
+ 1 << 6,
+ Ci.nsIClearDataService.CLEAR_PERMISSIONS,
+ ],
+ [
+ // SITE_SETTINGS
+ 1 << 7,
+ Ci.nsIClearDataService.CLEAR_CONTENT_PREFERENCES |
+ Ci.nsIClearDataService.CLEAR_DOM_PUSH_NOTIFICATIONS |
+ // former a part of SECURITY_SETTINGS_CLEANER
+ Ci.nsIClearDataService.CLEAR_CLIENT_AUTH_REMEMBER_SERVICE,
+ ],
+ [
+ // SITE_DATA
+ 1 << 8,
+ Ci.nsIClearDataService.CLEAR_EME,
+ // former a part of SECURITY_SETTINGS_CLEANER
+ Ci.nsIClearDataService.CLEAR_HSTS,
+ ],
+ [
+ // ALL
+ 1 << 9,
+ Ci.nsIClearDataService.CLEAR_ALL,
+ ],
+];
+
+function convertFlags(aJavaFlags) {
+ const flags = ClearFlags.filter(cf => {
+ return cf[0] & aJavaFlags;
+ }).reduce((acc, cf) => {
+ return acc | cf[1];
+ }, 0);
+ return flags;
+}
+
+const GeckoViewStorageController = {
+ onEvent(aEvent, aData, aCallback) {
+ debug`onEvent ${aEvent} ${aData}`;
+
+ switch (aEvent) {
+ case "GeckoView:ClearData": {
+ this.clearData(aData.flags, aCallback);
+ break;
+ }
+ case "GeckoView:ClearSessionContextData": {
+ this.clearSessionContextData(aData.contextId);
+ break;
+ }
+ case "GeckoView:ClearHostData": {
+ this.clearHostData(aData.host, aData.flags, aCallback);
+ break;
+ }
+ case "GeckoView:ClearBaseDomainData": {
+ this.clearBaseDomainData(aData.baseDomain, aData.flags, aCallback);
+ break;
+ }
+ case "GeckoView:GetAllPermissions": {
+ const rawPerms = Services.perms.all;
+ const permissions = rawPerms.map(p => {
+ return {
+ uri: Services.io.createExposableURI(p.principal.URI).displaySpec,
+ principal: E10SUtils.serializePrincipal(p.principal),
+ perm: p.type,
+ value: p.capability,
+ contextId: p.principal.originAttributes.geckoViewSessionContextId,
+ privateMode: p.principal.privateBrowsingId != 0,
+ };
+ });
+ aCallback.onSuccess({ permissions });
+ break;
+ }
+ case "GeckoView:GetPermissionsByURI": {
+ const uri = Services.io.newURI(aData.uri);
+ const principal = Services.scriptSecurityManager.createContentPrincipal(
+ uri,
+ aData.contextId
+ ? {
+ geckoViewSessionContextId: aData.contextId,
+ privateBrowsingId: aData.privateBrowsingId,
+ }
+ : { privateBrowsingId: aData.privateBrowsingId }
+ );
+ const rawPerms = Services.perms.getAllForPrincipal(principal);
+ const permissions = rawPerms.map(p => {
+ return {
+ uri: Services.io.createExposableURI(p.principal.URI).displaySpec,
+ principal: E10SUtils.serializePrincipal(p.principal),
+ perm: p.type,
+ value: p.capability,
+ contextId: p.principal.originAttributes.geckoViewSessionContextId,
+ privateMode: p.principal.privateBrowsingId != 0,
+ };
+ });
+ aCallback.onSuccess({ permissions });
+ break;
+ }
+ case "GeckoView:SetPermission": {
+ const principal = E10SUtils.deserializePrincipal(aData.principal);
+ let key = aData.perm;
+ if (key == "storage-access") {
+ key = "3rdPartyStorage^" + aData.thirdPartyOrigin;
+ }
+ if (aData.allowPermanentPrivateBrowsing) {
+ Services.perms.addFromPrincipalAndPersistInPrivateBrowsing(
+ principal,
+ key,
+ aData.newValue
+ );
+ } else {
+ const expirePolicy = aData.privateMode
+ ? Ci.nsIPermissionManager.EXPIRE_SESSION
+ : Ci.nsIPermissionManager.EXPIRE_NEVER;
+ Services.perms.addFromPrincipal(
+ principal,
+ key,
+ aData.newValue,
+ expirePolicy
+ );
+ }
+ break;
+ }
+ case "GeckoView:SetPermissionByURI": {
+ const uri = Services.io.newURI(aData.uri);
+ const expirePolicy = aData.privateId
+ ? Ci.nsIPermissionManager.EXPIRE_SESSION
+ : Ci.nsIPermissionManager.EXPIRE_NEVER;
+ const principal = Services.scriptSecurityManager.createContentPrincipal(
+ uri,
+ {
+ geckoViewSessionContextId: aData.contextId ?? undefined,
+ privateBrowsingId: aData.privateId,
+ }
+ );
+ Services.perms.addFromPrincipal(
+ principal,
+ aData.perm,
+ aData.newValue,
+ expirePolicy
+ );
+ break;
+ }
+
+ case "GeckoView:SetCookieBannerModeForDomain": {
+ let exceptionLabel = "SetCookieBannerModeForDomain";
+ try {
+ const uri = Services.io.newURI(aData.uri);
+ if (aData.allowPermanentPrivateBrowsing) {
+ exceptionLabel = "setDomainPrefAndPersistInPrivateBrowsing";
+ Services.cookieBanners.setDomainPrefAndPersistInPrivateBrowsing(
+ uri,
+ aData.mode
+ );
+ } else {
+ Services.cookieBanners.setDomainPref(
+ uri,
+ aData.mode,
+ aData.isPrivateBrowsing
+ );
+ }
+ aCallback.onSuccess();
+ } catch (ex) {
+ debug`Failed ${exceptionLabel} ${ex}`;
+ }
+ break;
+ }
+
+ case "GeckoView:RemoveCookieBannerModeForDomain": {
+ try {
+ const uri = Services.io.newURI(aData.uri);
+ Services.cookieBanners.removeDomainPref(uri, aData.isPrivateBrowsing);
+ aCallback.onSuccess();
+ } catch (ex) {
+ debug`Failed RemoveCookieBannerModeForDomain ${ex}`;
+ }
+ break;
+ }
+
+ case "GeckoView:GetCookieBannerModeForDomain": {
+ try {
+ let globalMode;
+ if (aData.isPrivateBrowsing) {
+ globalMode = lazy.serviceModePBM;
+ } else {
+ globalMode = lazy.serviceMode;
+ }
+
+ if (globalMode === Ci.nsICookieBannerService.MODE_DISABLED) {
+ aCallback.onSuccess({ mode: globalMode });
+ return;
+ }
+
+ const uri = Services.io.newURI(aData.uri);
+ const mode = Services.cookieBanners.getDomainPref(
+ uri,
+ aData.isPrivateBrowsing
+ );
+ if (mode !== Ci.nsICookieBannerService.MODE_UNSET) {
+ aCallback.onSuccess({ mode });
+ } else {
+ aCallback.onSuccess({ mode: globalMode });
+ }
+ } catch (ex) {
+ aCallback.onError(`Unexpected error: ${ex}`);
+ debug`Failed GetCookieBannerModeForDomain ${ex}`;
+ }
+ break;
+ }
+ }
+ },
+
+ async clearData(aFlags, aCallback) {
+ const flags = convertFlags(aFlags);
+
+ // storageAccessAPI permissions record every site that the user
+ // interacted with and thus mirror history quite closely. It makes
+ // sense to clear them when we clear history. However, since their absence
+ // indicates that we can purge cookies and site data for tracking origins without
+ // user interaction, we need to ensure that we only delete those permissions that
+ // do not have any existing storage.
+ if (flags & Ci.nsIClearDataService.CLEAR_HISTORY) {
+ const principalsCollector = new PrincipalsCollector();
+ const principals = await principalsCollector.getAllPrincipals();
+ await new Promise(resolve => {
+ Services.clearData.deleteUserInteractionForClearingHistory(
+ principals,
+ 0,
+ resolve
+ );
+ });
+ }
+
+ new Promise(resolve => {
+ Services.clearData.deleteData(flags, resolve);
+ }).then(resultFlags => {
+ aCallback.onSuccess();
+ });
+ },
+
+ clearHostData(aHost, aFlags, aCallback) {
+ new Promise(resolve => {
+ Services.clearData.deleteDataFromHost(
+ aHost,
+ /* isUserRequest */ true,
+ convertFlags(aFlags),
+ resolve
+ );
+ }).then(resultFlags => {
+ aCallback.onSuccess();
+ });
+ },
+
+ clearBaseDomainData(aBaseDomain, aFlags, aCallback) {
+ new Promise(resolve => {
+ Services.clearData.deleteDataFromBaseDomain(
+ aBaseDomain,
+ /* isUserRequest */ true,
+ convertFlags(aFlags),
+ resolve
+ );
+ }).then(resultFlags => {
+ aCallback.onSuccess();
+ });
+ },
+
+ clearSessionContextData(aContextId) {
+ const pattern = { geckoViewSessionContextId: aContextId };
+ debug`clearSessionContextData ${pattern}`;
+ Services.clearData.deleteDataFromOriginAttributesPattern(pattern);
+ // Call QMS explicitly to work around bug 1537882.
+ Services.qms.clearStoragesForOriginAttributesPattern(
+ JSON.stringify(pattern)
+ );
+ },
+};