/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
usecrate::sync::engine::TabsEngine; usecrate::TabsStore; use anyhow::Result; use std::sync::Arc; use sync15::bso::{IncomingBso, OutgoingBso}; use sync15::engine::{BridgedEngine, BridgedEngineAdaptor}; use sync15::ServerTimestamp; use sync_guid::Guid as SyncGuid;
impl TabsStore { // Returns a bridged sync engine for Desktop for this store. pubfn bridged_engine(self: Arc<Self>) -> Arc<TabsBridgedEngine> { let engine = TabsEngine::new(self); let bridged_engine = TabsBridgedEngineAdaptor { engine };
Arc::new(TabsBridgedEngine::new(Box::new(bridged_engine)))
}
}
/// A bridged engine implements all the methods needed to make the /// `storage.sync` store work with Desktop's Sync implementation. /// Conceptually it's very similar to our SyncEngine and there's a BridgedEngineAdaptor /// trait we can implement to get a `BridgedEngine` from a `SyncEngine`, so that's /// what we do. See also #2841, which will finally unify them completely. struct TabsBridgedEngineAdaptor {
engine: TabsEngine,
}
// This is for uniffi to expose, and does nothing than delegate back to the trait. pubstruct TabsBridgedEngine {
bridge_impl: Box<dyn BridgedEngine>,
}
// Decode the JSON-encoded IncomingBso's that UniFFI passes to us fn convert_incoming_bsos(&self, incoming: Vec<String>) -> Result<Vec<IncomingBso>> { letmut bsos = Vec::with_capacity(incoming.len()); for inc in incoming {
bsos.push(serde_json::from_str::<IncomingBso>(&inc)?);
}
Ok(bsos)
}
// Encode OutgoingBso's into JSON for UniFFI fn convert_outgoing_bsos(&self, outgoing: Vec<OutgoingBso>) -> Result<Vec<String>> { letmut bsos = Vec::with_capacity(outgoing.len()); for e in outgoing {
bsos.push(serde_json::to_string(&e)?);
}
Ok(bsos)
}
#[cfg(test)] mod tests { usesuper::*; usecrate::storage::{RemoteTab, TABS_CLIENT_TTL}; usecrate::sync::record::TabsRecordTab; use serde_json::json; use std::collections::HashMap; use sync15::{ClientData, DeviceType, RemoteClient};
// A copy of the normal "engine" tests but which go via the bridge #[test] fn test_sync_via_bridge() {
env_logger::try_init().ok();
let store = Arc::new(TabsStore::new_with_mem_path("test-bridge_incoming"));
// Set some local tabs for our device. let my_tabs = vec![
RemoteTab {
title: "my first tab".to_string(),
url_history: vec!["http://1.com".to_string()],
last_used: 2,
..Default::default()
},
RemoteTab {
title: "my second tab".to_string(),
url_history: vec!["http://2.com".to_string()],
last_used: 1,
..Default::default()
},
];
store.set_local_tabs(my_tabs.clone());
let bridge = store.bridged_engine();
let client_data = ClientData {
local_client_id: "my-device".to_string(),
recent_clients: HashMap::from([
( "my-device".to_string(),
RemoteClient {
fxa_device_id: None,
device_name: "my device".to_string(),
device_type: sync15::DeviceType::Unknown,
},
),
( "device-no-tabs".to_string(),
RemoteClient {
fxa_device_id: None,
device_name: "device with no tabs".to_string(),
device_type: DeviceType::Unknown,
},
),
( "device-with-a-tab".to_string(),
RemoteClient {
fxa_device_id: None,
device_name: "device with a tab".to_string(),
device_type: DeviceType::Unknown,
},
),
]),
};
bridge
.prepare_for_sync(&serde_json::to_string(&client_data).unwrap())
.expect("should work");
let records = vec![ // my-device should be ignored by sync - here it is acting as what our engine last // wrote, but the actual tabs in our store we set above are what should be used.
json!({ "id": "my-device", "clientName": "my device", "tabs": [{ "title": "the title", "urlHistory": [ "https://mozilla.org/"
], "icon": "https://mozilla.org/icon", "lastUsed": 1643764207
}]
}),
json!({ "id": "device-no-tabs", "clientName": "device with no tabs", "tabs": [],
}),
json!({ "id": "device-with-a-tab", "clientName": "device with a tab", "tabs": [{ "title": "the title", "urlHistory": [ "https://mozilla.org/"
], "icon": "https://mozilla.org/icon", "lastUsed": 1643764207
}]
}), // This has the main payload as OK but the tabs part invalid.
json!({ "id": "device-with-invalid-tab", "clientName": "device with a tab", "tabs": [{ "foo": "bar",
}]
}), // We want this to be a valid payload but an invalid tab - so it needs an ID.
json!({ "id": "invalid-tab", "foo": "bar"
}),
];
letmut incoming = Vec::new(); for record in records { // Annoyingly we can't use `IncomingEnvelope` directly as it intentionally doesn't // support Serialize - so need to use explicit json. let envelope = json!({ "id": record.get("id"), "modified": 0, "payload": serde_json::to_string(&record).unwrap(),
});
incoming.push(serde_json::to_string(&envelope).unwrap());
}
assert_eq!(out.len(), 1); let ours = serde_json::from_str::<serde_json::Value>(&out[0]).unwrap(); // As above, can't use `OutgoingEnvelope` as it doesn't Deserialize. // First, convert my_tabs from the local `RemoteTab` to the Sync specific `TabsRecord` let expected_tabs: Vec<TabsRecordTab> =
my_tabs.into_iter().map(|t| t.to_record_tab()).collect(); let expected = json!({ "id": "my-device".to_string(), "payload": json!({ "id": "my-device".to_string(), "clientName": "my device", "tabs": serde_json::to_value(expected_tabs).unwrap(),
}).to_string(), "ttl": TABS_CLIENT_TTL,
});
let store = Arc::new(TabsStore::new_with_mem_path("test-meta")); let bridge = store.bridged_engine();
// Should not error or panic
assert_eq!(bridge.last_sync().unwrap(), 0);
bridge.set_last_sync(3).unwrap();
assert_eq!(bridge.last_sync().unwrap(), 3);
assert!(bridge.sync_id().unwrap().is_none());
bridge.ensure_current_sync_id("some_guid").unwrap();
assert_eq!(bridge.sync_id().unwrap(), Some("some_guid".to_string())); // changing the sync ID should reset the timestamp
assert_eq!(bridge.last_sync().unwrap(), 0);
bridge.set_last_sync(3).unwrap();
bridge.reset_sync_id().unwrap(); // should now be a random guid.
assert_ne!(bridge.sync_id().unwrap(), Some("some_guid".to_string())); // should have reset the last sync timestamp.
assert_eq!(bridge.last_sync().unwrap(), 0);
bridge.set_last_sync(3).unwrap();
// `reset` clears the guid and the timestamp
bridge.reset().unwrap();
assert_eq!(bridge.last_sync().unwrap(), 0);
assert!(bridge.sync_id().unwrap().is_none());
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.11 Sekunden
(vorverarbeitet am 2026-06-18)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.