353 lines
11 KiB
JavaScript
353 lines
11 KiB
JavaScript
/* 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/. */
|
|
|
|
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
|
import { GeckoViewUtils } from "resource://gre/modules/GeckoViewUtils.sys.mjs";
|
|
|
|
const lazy = {};
|
|
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs",
|
|
PrincipalsCollector: "resource://gre/modules/PrincipalsCollector.sys.mjs",
|
|
});
|
|
|
|
XPCOMUtils.defineLazyPreferenceGetter(
|
|
lazy,
|
|
"serviceMode",
|
|
"cookiebanners.service.mode",
|
|
Ci.nsICookieBannerService.MODE_DISABLED
|
|
);
|
|
|
|
XPCOMUtils.defineLazyPreferenceGetter(
|
|
lazy,
|
|
"serviceModePBM",
|
|
"cookiebanners.service.mode.privateBrowsing",
|
|
Ci.nsICookieBannerService.MODE_DISABLED
|
|
);
|
|
|
|
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 |
|
|
Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXECUTED_RECORD |
|
|
Ci.nsIClearDataService.CLEAR_FINGERPRINTING_PROTECTION_STATE |
|
|
Ci.nsIClearDataService.CLEAR_BOUNCE_TRACKING_PROTECTION_STATE,
|
|
],
|
|
[
|
|
// 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,
|
|
],
|
|
[
|
|
// DOM_STORAGES
|
|
1 << 4,
|
|
Ci.nsIClearDataService.CLEAR_DOM_QUOTA |
|
|
Ci.nsIClearDataService.CLEAR_DOM_PUSH_NOTIFICATIONS |
|
|
Ci.nsIClearDataService.CLEAR_REPORTS |
|
|
Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXECUTED_RECORD |
|
|
Ci.nsIClearDataService.CLEAR_FINGERPRINTING_PROTECTION_STATE |
|
|
Ci.nsIClearDataService.CLEAR_BOUNCE_TRACKING_PROTECTION_STATE,
|
|
],
|
|
[
|
|
// 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 |
|
|
Ci.nsIClearDataService.CLEAR_FINGERPRINTING_PROTECTION_STATE,
|
|
],
|
|
[
|
|
// 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;
|
|
}
|
|
|
|
export 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: lazy.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: lazy.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 = lazy.E10SUtils.deserializePrincipal(aData.principal);
|
|
let key = aData.perm;
|
|
if (key == "storage-access") {
|
|
key = "3rdPartyFrameStorage^" + 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 lazy.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(() => {
|
|
aCallback.onSuccess();
|
|
});
|
|
},
|
|
|
|
clearHostData(aHost, aFlags, aCallback) {
|
|
new Promise(resolve => {
|
|
Services.clearData.deleteDataFromHost(
|
|
aHost,
|
|
/* isUserRequest */ true,
|
|
convertFlags(aFlags),
|
|
resolve
|
|
);
|
|
}).then(() => {
|
|
aCallback.onSuccess();
|
|
});
|
|
},
|
|
|
|
clearBaseDomainData(aBaseDomain, aFlags, aCallback) {
|
|
// Ensure we have a site at this point.
|
|
let schemelessSite = Services.eTLD.getSchemelessSiteFromHost(aBaseDomain);
|
|
new Promise(resolve => {
|
|
Services.clearData.deleteDataFromSite(
|
|
schemelessSite,
|
|
{},
|
|
/* isUserRequest */ true,
|
|
convertFlags(aFlags),
|
|
resolve
|
|
);
|
|
}).then(() => {
|
|
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)
|
|
);
|
|
},
|
|
};
|