summaryrefslogtreecommitdiffstats
path: root/toolkit/components/cleardata/SiteDataTestUtils.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/cleardata/SiteDataTestUtils.sys.mjs')
-rw-r--r--toolkit/components/cleardata/SiteDataTestUtils.sys.mjs411
1 files changed, 411 insertions, 0 deletions
diff --git a/toolkit/components/cleardata/SiteDataTestUtils.sys.mjs b/toolkit/components/cleardata/SiteDataTestUtils.sys.mjs
new file mode 100644
index 0000000000..a777a19b7d
--- /dev/null
+++ b/toolkit/components/cleardata/SiteDataTestUtils.sys.mjs
@@ -0,0 +1,411 @@
+/* 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 { BrowserTestUtils } from "resource://testing-common/BrowserTestUtils.sys.mjs";
+
+const lazy = {};
+
+XPCOMUtils.defineLazyServiceGetter(
+ lazy,
+ "swm",
+ "@mozilla.org/serviceworkers/manager;1",
+ "nsIServiceWorkerManager"
+);
+
+/**
+ * This module assists with tasks around testing functionality that shows
+ * or clears site data.
+ *
+ * Please note that you will have to clean up changes made manually, for
+ * example using SiteDataTestUtils.clear().
+ */
+export var SiteDataTestUtils = {
+ /**
+ * Makes an origin have persistent data storage.
+ *
+ * @param {String} origin - the origin of the site to give persistent storage
+ *
+ * @returns a Promise that resolves when storage was persisted
+ */
+ persist(origin, value = Services.perms.ALLOW_ACTION) {
+ return new Promise(resolve => {
+ let principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin);
+ Services.perms.addFromPrincipal(principal, "persistent-storage", value);
+ Services.qms.persist(principal).callback = () => resolve();
+ });
+ },
+
+ /**
+ * Adds a new blob entry to a dummy indexedDB database for the specified origin.
+ *
+ * @param {String} origin - the origin of the site to add test data for
+ * @param {Number} size [optional] - the size of the entry in bytes
+ *
+ * @returns a Promise that resolves when the data was added successfully.
+ */
+ addToIndexedDB(origin, size = 1024) {
+ return new Promise(resolve => {
+ let principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin);
+ let request = indexedDB.openForPrincipal(principal, "TestDatabase", 1);
+ request.onupgradeneeded = function (e) {
+ let db = e.target.result;
+ db.createObjectStore("TestStore");
+ };
+ request.onsuccess = function (e) {
+ let db = e.target.result;
+ let tx = db.transaction("TestStore", "readwrite");
+ let store = tx.objectStore("TestStore");
+ tx.oncomplete = resolve;
+ let buffer = new ArrayBuffer(size);
+ let blob = new Blob([buffer]);
+ store.add(blob, Cu.now());
+ };
+ });
+ },
+
+ /**
+ * Adds a new cookie for the specified origin or host + path + oa, with the
+ * specified contents. The cookie will be valid for one day.
+ * @param {object} options
+ * @param {String} [options.origin] - Origin of the site to add test data for.
+ * If set, overrides host, path and originAttributes args.
+ * @param {String} [options.host] - Host of the site to add test data for.
+ * @param {String} [options.path] - Path to set cookie for.
+ * @param {Object} [options.originAttributes] - Object of origin attributes to
+ * set cookie for.
+ * @param {String} [options.name] - Cookie name
+ * @param {String} [options.value] - Cookie value
+ */
+ addToCookies({
+ origin,
+ host,
+ path = "path",
+ originAttributes = {},
+ name = "foo",
+ value = "bar",
+ }) {
+ if (origin) {
+ let principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin);
+ host = principal.host;
+ path = principal.URI.pathQueryRef;
+ originAttributes = Object.keys(originAttributes).length
+ ? originAttributes
+ : principal.originAttributes;
+ }
+
+ Services.cookies.add(
+ host,
+ path,
+ name,
+ value,
+ false,
+ false,
+ false,
+ Math.floor(Date.now() / 1000) + 24 * 60 * 60,
+ originAttributes,
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_UNSET
+ );
+ },
+
+ /**
+ * Adds a new localStorage entry for the specified origin, with the specified contents.
+ *
+ * @param {String} origin - the origin of the site to add test data for
+ * @param {String} [key] - the localStorage key
+ * @param {String} [value] - the localStorage value
+ */
+ addToLocalStorage(origin, key = "foo", value = "bar") {
+ let principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin);
+ let storage = Services.domStorageManager.createStorage(
+ null,
+ principal,
+ principal,
+ ""
+ );
+ storage.setItem(key, value);
+ },
+
+ /**
+ * Checks whether the given origin is storing data in localStorage
+ *
+ * @param {String} origin - the origin of the site to check
+ * @param {{key: String, value: String}[]} [testEntries] - An array of entries
+ * to test for.
+ *
+ * @returns {Boolean} whether the origin has localStorage data
+ */
+ hasLocalStorage(origin, testEntries) {
+ let principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin);
+ let storage = Services.domStorageManager.createStorage(
+ null,
+ principal,
+ principal,
+ ""
+ );
+ if (!storage.length) {
+ return false;
+ }
+ if (!testEntries) {
+ return true;
+ }
+ return (
+ storage.length >= testEntries.length &&
+ testEntries.every(({ key, value }) => storage.getItem(key) == value)
+ );
+ },
+
+ /**
+ * Adds a new serviceworker with the specified path. Note that this
+ * method will open a new tab at the domain of the SW path to that effect.
+ *
+ * @param {String} path - the path to the service worker to add.
+ *
+ * @returns a Promise that resolves when the service worker was registered
+ */
+ addServiceWorker(path) {
+ let uri = Services.io.newURI(path);
+ // Register a dummy ServiceWorker.
+ return BrowserTestUtils.withNewTab(uri.prePath, async function (browser) {
+ return browser.ownerGlobal.SpecialPowers.spawn(
+ browser,
+ [{ path }],
+ async ({ path: p }) => {
+ // eslint-disable-next-line no-undef
+ let r = await content.navigator.serviceWorker.register(p);
+ return new Promise(resolve => {
+ let worker = r.installing || r.waiting || r.active;
+ if (worker.state == "activated") {
+ resolve();
+ } else {
+ worker.addEventListener("statechange", () => {
+ if (worker.state == "activated") {
+ resolve();
+ }
+ });
+ }
+ });
+ }
+ );
+ });
+ },
+
+ hasCookies(origin, testEntries = null, testPBMCookies = false) {
+ let principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin);
+
+ let originAttributes = principal.originAttributes;
+
+ if (testPBMCookies) {
+ // Override the origin attributes to set PBM.
+ // This needs to be updated when adding support for multiple PBM contexts.
+ originAttributes = {
+ ...principal.originAttributes,
+ privateBrowsingId: 1,
+ };
+ }
+ // Need to do an additional filter step since getCookiesFromHost returns all
+ // cookies for the base domain (including subdomains). This method takes an
+ // origin so we need to do an exact host match.
+ let cookies = Services.cookies
+ .getCookiesFromHost(principal.baseDomain, originAttributes)
+ .filter(
+ cookie =>
+ cookie.host == principal.host || cookie.host == `.${principal.host}`
+ );
+
+ // If we don't have to filter specific testEntries we can return now.
+ if (!testEntries) {
+ return !!cookies.length;
+ }
+
+ // Check if the returned cookies match testEntries.
+ if (cookies.length < testEntries.length) {
+ return false;
+ }
+
+ // This code isn't very efficient. It should only be used for testing
+ // a small amount of cookies.
+ return testEntries.every(({ key, value }) =>
+ cookies.some(cookie => cookie.name == key && cookie.value == value)
+ );
+ },
+
+ hasIndexedDB(origin) {
+ let principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin);
+ return new Promise(resolve => {
+ let data = true;
+ let request = indexedDB.openForPrincipal(principal, "TestDatabase", 1);
+ request.onupgradeneeded = function (e) {
+ data = false;
+ };
+ request.onsuccess = function (e) {
+ resolve(data);
+ };
+ });
+ },
+
+ _getCacheStorage(where, lci) {
+ switch (where) {
+ case "disk":
+ return Services.cache2.diskCacheStorage(lci);
+ case "memory":
+ return Services.cache2.memoryCacheStorage(lci);
+ case "pin":
+ return Services.cache2.pinningCacheStorage(lci);
+ }
+ return null;
+ },
+
+ hasCacheEntry(path, where, lci = Services.loadContextInfo.default) {
+ let storage = this._getCacheStorage(where, lci);
+ return storage.exists(Services.io.newURI(path), "");
+ },
+
+ addCacheEntry(path, where, lci = Services.loadContextInfo.default) {
+ return new Promise(resolve => {
+ function CacheListener() {}
+ CacheListener.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsICacheEntryOpenCallback"]),
+
+ onCacheEntryCheck(entry) {
+ return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
+ },
+
+ onCacheEntryAvailable(entry, isnew, status) {
+ resolve();
+ },
+ };
+
+ let storage = this._getCacheStorage(where, lci);
+ storage.asyncOpenURI(
+ Services.io.newURI(path),
+ "",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ new CacheListener()
+ );
+ });
+ },
+
+ /**
+ * Checks whether the specified origin has registered ServiceWorkers.
+ *
+ * @param {String} origin - the origin of the site to check
+ *
+ * @returns {Boolean} whether or not the site has ServiceWorkers.
+ */
+ hasServiceWorkers(origin) {
+ let serviceWorkers = lazy.swm.getAllRegistrations();
+ for (let i = 0; i < serviceWorkers.length; i++) {
+ let sw = serviceWorkers.queryElementAt(
+ i,
+ Ci.nsIServiceWorkerRegistrationInfo
+ );
+ if (sw.principal.origin == origin) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+ /**
+ * Waits for a ServiceWorker to be registered.
+ *
+ * @param {String} the url of the ServiceWorker to wait for
+ *
+ * @returns a Promise that resolves when a ServiceWorker at the
+ * specified location has been registered.
+ */
+ promiseServiceWorkerRegistered(url) {
+ if (!(url instanceof Ci.nsIURI)) {
+ url = Services.io.newURI(url);
+ }
+
+ return new Promise(resolve => {
+ let listener = {
+ onRegister: registration => {
+ if (registration.principal.host != url.host) {
+ return;
+ }
+ lazy.swm.removeListener(listener);
+ resolve(registration);
+ },
+ };
+ lazy.swm.addListener(listener);
+ });
+ },
+
+ /**
+ * Waits for a ServiceWorker to be unregistered.
+ *
+ * @param {String} the url of the ServiceWorker to wait for
+ *
+ * @returns a Promise that resolves when a ServiceWorker at the
+ * specified location has been unregistered.
+ */
+ promiseServiceWorkerUnregistered(url) {
+ if (!(url instanceof Ci.nsIURI)) {
+ url = Services.io.newURI(url);
+ }
+
+ return new Promise(resolve => {
+ let listener = {
+ onUnregister: registration => {
+ if (registration.principal.host != url.host) {
+ return;
+ }
+ lazy.swm.removeListener(listener);
+ resolve(registration);
+ },
+ };
+ lazy.swm.addListener(listener);
+ });
+ },
+
+ /**
+ * Gets the current quota usage for the specified origin.
+ *
+ * @returns a Promise that resolves to an integer with the total
+ * amount of disk usage by a given origin.
+ */
+ getQuotaUsage(origin) {
+ return new Promise(resolve => {
+ let principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin);
+ Services.qms.getUsageForPrincipal(principal, request =>
+ resolve(request.result.usage)
+ );
+ });
+ },
+
+ /**
+ * Cleans up all site data.
+ */
+ clear() {
+ return new Promise(resolve => {
+ Services.clearData.deleteData(
+ Ci.nsIClearDataService.CLEAR_COOKIES |
+ Ci.nsIClearDataService.CLEAR_ALL_CACHES |
+ Ci.nsIClearDataService.CLEAR_MEDIA_DEVICES |
+ Ci.nsIClearDataService.CLEAR_DOM_STORAGES |
+ Ci.nsIClearDataService.CLEAR_PREDICTOR_NETWORK_DATA |
+ Ci.nsIClearDataService.CLEAR_CLIENT_AUTH_REMEMBER_SERVICE |
+ Ci.nsIClearDataService.CLEAR_EME |
+ Ci.nsIClearDataService.CLEAR_STORAGE_ACCESS |
+ Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXCEPTION |
+ Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXECUTED_RECORD |
+ Ci.nsIClearDataService.CLEAR_FINGERPRINTING_PROTECTION_STATE |
+ Ci.nsIClearDataService.CLEAR_BOUNCE_TRACKING_PROTECTION_STATE,
+ resolve
+ );
+ });
+ },
+};