forked from mirrors/gecko-dev
This commit adds a new crate for bridging Rust Sync engines to Desktop, and a `mozIBridgedSyncEngine` for accessing the bridge via JS. Naturally, the bridge is called Golden Gate. 😊 For more information on how to use it, please see `golden_gate/src/lib.rs`. Other changes include: * Ensuring the test Sync server uses UTF-8 for requests and responses. * Renaming `mozISyncedBookmarksMirrorLogger` to `mozIServicesLogger`, and moving it into the shared Sync interfaces. The `BridgedEngine` trait lives in its own crate, called `golden_gate_traits`, to make it easier to eventually move into a-s. `Interruptee` and `Interrupted` already exist in a-s, and are duplicated in this crate for now. Differential Revision: https://phabricator.services.mozilla.com/D65268 --HG-- extra : moz-landing-system : lando
265 lines
7 KiB
JavaScript
265 lines
7 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
const { BridgedEngine } = ChromeUtils.import(
|
|
"resource://services-sync/bridged_engine.js"
|
|
);
|
|
const { Service } = ChromeUtils.import("resource://services-sync/service.js");
|
|
|
|
// Wraps an `object` in a proxy so that its methods are bound to it. This
|
|
// simulates how XPCOM class instances have all their methods bound.
|
|
function withBoundMethods(object) {
|
|
return new Proxy(object, {
|
|
get(target, key) {
|
|
let value = target[key];
|
|
return typeof value == "function" ? value.bind(target) : value;
|
|
},
|
|
});
|
|
}
|
|
|
|
add_task(async function test_interface() {
|
|
class TestBridge {
|
|
constructor() {
|
|
this.storageVersion = 2;
|
|
this.syncID = "syncID111111";
|
|
this.wasInitialized = false;
|
|
this.clear();
|
|
}
|
|
|
|
clear() {
|
|
this.lastSyncMillis = 0;
|
|
this.incomingRecords = [];
|
|
this.uploadedIDs = [];
|
|
this.wasSynced = false;
|
|
this.wasReset = false;
|
|
this.wasWiped = false;
|
|
}
|
|
|
|
// `mozIBridgedSyncEngine` methods.
|
|
|
|
initialize(callback) {
|
|
ok(
|
|
!this.wasInitialized,
|
|
"Shouldn't initialize a bridged engine more than once"
|
|
);
|
|
this.wasInitialized = true;
|
|
CommonUtils.nextTick(() => callback.handleSuccess());
|
|
}
|
|
|
|
getLastSync(callback) {
|
|
ok(
|
|
this.wasInitialized,
|
|
"Should initialize before getting last sync time"
|
|
);
|
|
CommonUtils.nextTick(() => callback.handleSuccess(this.lastSyncMillis));
|
|
}
|
|
|
|
setLastSync(millis, callback) {
|
|
ok(
|
|
this.wasInitialized,
|
|
"Should initialize before setting last sync time"
|
|
);
|
|
this.lastSyncMillis = millis;
|
|
CommonUtils.nextTick(() => callback.handleSuccess());
|
|
}
|
|
|
|
resetSyncId(callback) {
|
|
ok(this.wasInitialized, "Should initialize before resetting sync ID");
|
|
CommonUtils.nextTick(() => callback.handleSuccess(this.syncID));
|
|
}
|
|
|
|
ensureCurrentSyncId(newSyncId, callback) {
|
|
ok(
|
|
this.wasInitialized,
|
|
"Should initialize before ensuring current sync ID"
|
|
);
|
|
equal(newSyncId, this.syncID, "Local and new sync IDs should match");
|
|
CommonUtils.nextTick(() => callback.handleSuccess(this.syncID));
|
|
}
|
|
|
|
storeIncoming(records, callback) {
|
|
ok(
|
|
this.wasInitialized,
|
|
"Should initialize before storing incoming records"
|
|
);
|
|
this.incomingRecords.push(...records.map(r => JSON.parse(r)));
|
|
CommonUtils.nextTick(() => callback.handleSuccess());
|
|
}
|
|
|
|
apply(callback) {
|
|
ok(this.wasInitialized, "Should initialize before applying records");
|
|
let outgoingRecords = [
|
|
{
|
|
id: "hanson",
|
|
data: {
|
|
plants: ["seed", "flower 💐", "rose"],
|
|
canYouTell: false,
|
|
},
|
|
},
|
|
{
|
|
id: "sheryl-crow",
|
|
data: {
|
|
today: "winding 🛣",
|
|
tomorrow: "winding 🛣",
|
|
},
|
|
},
|
|
].map(r => JSON.stringify(r));
|
|
CommonUtils.nextTick(() => callback.handleSuccess(outgoingRecords));
|
|
return { cancel() {} };
|
|
}
|
|
|
|
setUploaded(millis, ids, callback) {
|
|
ok(
|
|
this.wasInitialized,
|
|
"Should initialize before setting records as uploaded"
|
|
);
|
|
this.uploadedIDs.push(...ids);
|
|
CommonUtils.nextTick(() => callback.handleSuccess());
|
|
return { cancel() {} };
|
|
}
|
|
|
|
syncFinished(callback) {
|
|
ok(
|
|
this.wasInitialized,
|
|
"Should initialize before flagging sync as finished"
|
|
);
|
|
this.wasSynced = true;
|
|
CommonUtils.nextTick(() => callback.handleSuccess());
|
|
return { cancel() {} };
|
|
}
|
|
|
|
reset(callback) {
|
|
ok(this.wasInitialized, "Should initialize before resetting");
|
|
this.clear();
|
|
this.wasReset = true;
|
|
CommonUtils.nextTick(() => callback.handleSuccess());
|
|
}
|
|
|
|
wipe(callback) {
|
|
ok(this.wasInitialized, "Should initialize before wiping");
|
|
this.clear();
|
|
this.wasWiped = true;
|
|
CommonUtils.nextTick(() => callback.handleSuccess());
|
|
}
|
|
}
|
|
|
|
let bridge = new TestBridge();
|
|
let engine = new BridgedEngine(withBoundMethods(bridge), "Nineties", Service);
|
|
engine.enabled = true;
|
|
|
|
let server = await serverForFoo(engine);
|
|
try {
|
|
await SyncTestingInfrastructure(server);
|
|
|
|
info("Add server records");
|
|
let foo = server.user("foo");
|
|
let collection = foo.collection("nineties");
|
|
let now = new_timestamp();
|
|
collection.insert(
|
|
"backstreet",
|
|
encryptPayload({
|
|
id: "backstreet",
|
|
data: {
|
|
say: "I want it that way",
|
|
when: "never",
|
|
},
|
|
}),
|
|
now
|
|
);
|
|
collection.insert(
|
|
"tlc",
|
|
encryptPayload({
|
|
id: "tlc",
|
|
data: {
|
|
forbidden: ["scrubs 🚫"],
|
|
numberAvailable: false,
|
|
},
|
|
}),
|
|
now + 5
|
|
);
|
|
|
|
info("Sync the engine");
|
|
// Advance the last sync time to skip the Backstreet Boys...
|
|
bridge.lastSyncMillis = now + 2;
|
|
await sync_engine_and_validate_telem(engine, false);
|
|
|
|
let metaGlobal = foo
|
|
.collection("meta")
|
|
.wbo("global")
|
|
.get();
|
|
deepEqual(
|
|
JSON.parse(metaGlobal.payload).engines.nineties,
|
|
{
|
|
version: 2,
|
|
syncID: "syncID111111",
|
|
},
|
|
"Should write storage version and sync ID to m/g"
|
|
);
|
|
|
|
greater(bridge.lastSyncMillis, 0, "Should update last sync time");
|
|
deepEqual(
|
|
bridge.incomingRecords.sort((a, b) => a.id.localeCompare(b.id)),
|
|
[
|
|
{
|
|
id: "tlc",
|
|
data: {
|
|
forbidden: ["scrubs 🚫"],
|
|
numberAvailable: false,
|
|
},
|
|
},
|
|
],
|
|
"Should stage incoming records from server"
|
|
);
|
|
deepEqual(
|
|
bridge.uploadedIDs.sort(),
|
|
["hanson", "sheryl-crow"],
|
|
"Should mark new local records as uploaded"
|
|
);
|
|
ok(bridge.wasSynced, "Should have finished sync after uploading");
|
|
|
|
deepEqual(
|
|
collection.keys().sort(),
|
|
["backstreet", "hanson", "sheryl-crow", "tlc"],
|
|
"Should have all records on server"
|
|
);
|
|
let expectedRecords = [
|
|
{
|
|
id: "sheryl-crow",
|
|
data: {
|
|
today: "winding 🛣",
|
|
tomorrow: "winding 🛣",
|
|
},
|
|
},
|
|
{
|
|
id: "hanson",
|
|
data: {
|
|
plants: ["seed", "flower 💐", "rose"],
|
|
canYouTell: false,
|
|
},
|
|
},
|
|
];
|
|
for (let expected of expectedRecords) {
|
|
let actual = collection.cleartext(expected.id);
|
|
deepEqual(
|
|
actual,
|
|
expected,
|
|
`Should upload record ${expected.id} from bridged engine`
|
|
);
|
|
}
|
|
|
|
await engine.resetClient();
|
|
ok(bridge.wasReset, "Should reset local storage for bridge");
|
|
|
|
await engine.wipeClient();
|
|
ok(bridge.wasWiped, "Should wipe local storage for bridge");
|
|
|
|
await engine.resetSyncID();
|
|
ok(
|
|
!foo.collection("nineties"),
|
|
"Should delete server collection after resetting sync ID"
|
|
);
|
|
} finally {
|
|
await promiseStopServer(server);
|
|
await engine.finalize();
|
|
}
|
|
});
|