fune/services/settings/RemoteSettingsWorker.js
Cristian Tuns b3bf09cc0d Backed out 6 changesets (bug 1816934, bug 1817182, bug 1817179, bug 1817183) for causing dt failures in browser_jsterm_autocomplete_null.js CLOSED TREE
Backed out changeset 17d4c013ed92 (bug 1817183)
Backed out changeset cfed8d9c23f3 (bug 1817183)
Backed out changeset 62fe2f589efe (bug 1817182)
Backed out changeset 557bd773fb85 (bug 1817179)
Backed out changeset 7f8a7865868b (bug 1816934)
Backed out changeset d6c1d4c0d2a0 (bug 1816934)
2023-02-17 10:51:33 -05:00

204 lines
6.2 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/. */
/* eslint-env mozilla/chrome-worker */
"use strict";
/**
* A worker dedicated to Remote Settings.
*/
/* import-globals-from /toolkit/components/workerloader/require.js */
/* import-globals-from /toolkit/modules/CanonicalJSON.jsm */
/* import-globals-from IDBHelpers.jsm */
/* import-globals-from SharedUtils.jsm */
/* import-globals-from /toolkit/modules/third_party/jsesc/jsesc.js */
importScripts(
"resource://gre/modules/workers/require.js",
"resource://gre/modules/CanonicalJSON.jsm",
"resource://services-settings/IDBHelpers.jsm",
"resource://services-settings/SharedUtils.jsm",
"resource://gre/modules/third_party/jsesc/jsesc.js"
);
const IDB_RECORDS_STORE = "records";
const IDB_TIMESTAMPS_STORE = "timestamps";
let gShutdown = false;
const Agent = {
/**
* Return the canonical JSON serialization of the specified records.
* It has to match what is done on the server (See Kinto/kinto-signer).
*
* @param {Array<Object>} records
* @param {String} timestamp
* @returns {String}
*/
async canonicalStringify(records, timestamp) {
// Sort list by record id.
let allRecords = records.sort((a, b) => {
if (a.id < b.id) {
return -1;
}
return a.id > b.id ? 1 : 0;
});
// All existing records are replaced by the version from the server
// and deleted records are removed.
for (let i = 0; i < allRecords.length /* no increment! */; ) {
const rec = allRecords[i];
const next = allRecords[i + 1];
if ((next && rec.id == next.id) || rec.deleted) {
allRecords.splice(i, 1); // remove local record
} else {
i++;
}
}
const toSerialize = {
last_modified: "" + timestamp,
data: allRecords,
};
return CanonicalJSON.stringify(toSerialize, jsesc);
},
/**
* If present, import the JSON file into the Remote Settings IndexedDB
* for the specified bucket and collection.
* (eg. blocklists/certificates, main/onboarding)
* @param {String} bucket
* @param {String} collection
* @returns {int} Number of records loaded from dump or -1 if no dump found.
*/
async importJSONDump(bucket, collection) {
const { data: records, timestamp } = await SharedUtils.loadJSONDump(
bucket,
collection
);
if (records === null) {
// Return -1 if file is missing.
return -1;
}
if (gShutdown) {
throw new Error("Can't import when we've started shutting down.");
}
await importDumpIDB(bucket, collection, records, timestamp);
return records.length;
},
/**
* Check that the specified file matches the expected size and SHA-256 hash.
* @param {String} fileUrl file URL to read from
* @param {Number} size expected file size
* @param {String} size expected file SHA-256 as hex string
* @returns {boolean}
*/
async checkFileHash(fileUrl, size, hash) {
let resp;
try {
resp = await fetch(fileUrl);
} catch (e) {
// File does not exist.
return false;
}
const buffer = await resp.arrayBuffer();
return SharedUtils.checkContentHash(buffer, size, hash);
},
async prepareShutdown() {
gShutdown = true;
// Ensure we can iterate and abort (which may delete items) by cloning
// the list.
let transactions = Array.from(gPendingTransactions);
for (let transaction of transactions) {
try {
transaction.abort();
} catch (ex) {
// We can hit this case if the transaction has finished but
// we haven't heard about it yet.
}
}
},
_test_only_import(bucket, collection, records, timestamp) {
return importDumpIDB(bucket, collection, records, timestamp);
},
};
/**
* Wrap worker invocations in order to return the `callbackId` along
* the result. This will allow to transform the worker invocations
* into promises in `RemoteSettingsWorker.jsm`.
*/
self.onmessage = event => {
const { callbackId, method, args = [] } = event.data;
Agent[method](...args)
.then(result => {
self.postMessage({ callbackId, result });
})
.catch(error => {
console.log(`RemoteSettingsWorker error: ${error}`);
self.postMessage({ callbackId, error: "" + error });
});
};
let gPendingTransactions = new Set();
/**
* Import the records into the Remote Settings Chrome IndexedDB.
*
* Note: This duplicates some logics from `kinto-offline-client.js`.
*
* @param {String} bucket
* @param {String} collection
* @param {Array<Object>} records
* @param {Number} timestamp
*/
async function importDumpIDB(bucket, collection, records, timestamp) {
// Open the DB. It will exist since if we are running this, it means
// we already tried to read the timestamp in `remote-settings.js`
const db = await IDBHelpers.openIDB(false /* do not allow upgrades */);
// try...finally to ensure we always close the db.
try {
if (gShutdown) {
throw new Error("Can't import when we've started shutting down.");
}
// Each entry of the dump will be stored in the records store.
// They are indexed by `_cid`.
const cid = bucket + "/" + collection;
// We can just modify the items in-place, as we got them from SharedUtils.loadJSONDump().
records.forEach(item => {
item._cid = cid;
});
// Store the collection timestamp.
let { transaction, promise } = IDBHelpers.executeIDB(
db,
[IDB_RECORDS_STORE, IDB_TIMESTAMPS_STORE],
"readwrite",
([recordsStore, timestampStore], rejectTransaction) => {
// Wipe before loading
recordsStore.delete(IDBKeyRange.bound([cid], [cid, []], false, true));
IDBHelpers.bulkOperationHelper(
recordsStore,
{
reject: rejectTransaction,
completion() {
timestampStore.put({ cid, value: timestamp });
},
},
"put",
records
);
}
);
gPendingTransactions.add(transaction);
promise = promise.finally(() => gPendingTransactions.delete(transaction));
await promise;
} finally {
// Close now that we're done.
db.close();
}
}