Bug 548066 - JavaScript strict warning: clientData.js, line 194: reference to undefined property this.clients[id] [r=mconnor]

Get rid of get/setInfo on ClientEngine and ClientStore and expose functions to read/modify client data: stats, clearCommands, sendCommand. Also expose the local client information as local[ID,Name,Type,Commands] and rework the storage to use these instead of trying to keep the JS object clients entry in sync with prefs, etc. Update users of the old interface (service/tabs/chrome) to use the new local*. Set the client type based on app id instead of from each app's overlay.
This commit is contained in:
Edward Lee 2010-03-16 16:39:08 -07:00
parent 15e1f02e87
commit 21f7747109
7 changed files with 154 additions and 185 deletions

View File

@ -34,62 +34,94 @@
*
* ***** END LICENSE BLOCK ***** */
const EXPORTED_SYMBOLS = ['Clients'];
const EXPORTED_SYMBOLS = ["Clients"];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://weave/constants.js");
Cu.import("resource://weave/util.js");
Cu.import("resource://weave/engines.js");
Cu.import("resource://weave/stores.js");
Cu.import("resource://weave/trackers.js");
Cu.import("resource://weave/type_records/clientData.js");
Cu.import("resource://weave/type_records/clients.js");
Utils.lazy(this, 'Clients', ClientEngine);
Utils.lazy(this, "Clients", ClientEngine);
function ClientEngine() {
SyncEngine.call(this, "Clients");
// Reset the client on every startup so that we fetch recent clients
this._resetClient();
Utils.prefs.addObserver("", this, false);
}
ClientEngine.prototype = {
__proto__: SyncEngine.prototype,
_storeObj: ClientStore,
_trackerObj: ClientTracker,
_recordObj: ClientRecord,
_recordObj: ClientsRec,
// get and set info for clients
// Aggregate some stats on the composition of clients on this account
get stats() {
let stats = {
hasMobile: this.localType == "mobile",
names: [this.localName],
numClients: 1,
};
// FIXME: callers must use the setInfo interface or changes won't get synced,
// which is unintuitive
for each (let {name, type} in this._store._remoteClients) {
stats.hasMobile = stats.hasMobile || type == "mobile";
stats.names.push(name);
stats.numClients++;
}
getClients: function ClientEngine_getClients() {
return this._store.clients;
return stats;
},
getInfo: function ClientEngine_getInfo(id) {
return this._store.getInfo(id);
// Remove any commands for the local client and mark it for upload
clearCommands: function clearCommands() {
delete this.localCommands;
this._tracker.addChangedID(this.localID);
},
setInfo: function ClientEngine_setInfo(id, info) {
this._store.setInfo(id, info);
this._tracker.addChangedID(id);
// Send a command+args pair to each remote client
sendCommand: function sendCommand(command, args) {
// Helper to determine if the client already has this command
let notDupe = function(other) other.command != command ||
JSON.stringify(other.args) != JSON.stringify(args);
// Package the command/args pair into an object
let action = {
command: command,
args: args,
};
// Send the command to each remote client
for (let [id, client] in Iterator(this._store._remoteClients)) {
// Set the action to be a new commands array if none exists
if (client.commands == null)
client.commands = [action];
// Add the new action if there are no duplicates
else if (client.commands.every(notDupe))
client.commands.push(action);
// Must have been a dupe.. skip!
else
continue;
this._log.trace("Client " + id + " got a new action: " + [command, args]);
this._tracker.addChangedID(id);
}
},
// helpers for getting/setting this client's info directly
get clientID() {
if (!Svc.Prefs.get("client.GUID"))
Svc.Prefs.set("client.GUID", Utils.makeGUID());
return Svc.Prefs.get("client.GUID");
get localID() {
// Generate a random GUID id we don't have one
let localID = Svc.Prefs.get("client.GUID", "");
return localID == "" ? this.localID = Utils.makeGUID() : localID;
},
set localID(value) Svc.Prefs.set("client.GUID", value),
get clientName() {
if (Svc.Prefs.isSet("client.name"))
return Svc.Prefs.get("client.name");
get localName() {
let localName = Svc.Prefs.get("client.name", "");
if (localName != "")
return localName;
// Generate a client name if we don't have a useful one yet
let user = Svc.Env.get("USER") || Svc.Env.get("USERNAME");
@ -109,38 +141,26 @@ ClientEngine.prototype = {
}
}
return this.clientName = Str.sync.get("client.name", [user, app, host]);
return this.localName = Str.sync.get("client.name", [user, app, host]);
},
set clientName(value) { Svc.Prefs.set("client.name", value); },
set localName(value) Svc.Prefs.set("client.name", value),
get clientType() { return Svc.Prefs.get("client.type", "desktop"); },
set clientType(value) { Svc.Prefs.set("client.type", value); },
updateLocalInfo: function ClientEngine_updateLocalInfo(info) {
// Grab data from the store if we weren't given something to start with
if (!info)
info = this.getInfo(this.clientID);
// Overwrite any existing values with the ones from the pref
info.name = this.clientName;
info.type = this.clientType;
return info;
},
observe: function ClientEngine_observe(subject, topic, data) {
switch (topic) {
case "nsPref:changed":
switch (data) {
case "client.name":
case "client.type":
// Update the store and tracker on pref changes
this.setInfo(this.clientID, this.updateLocalInfo());
break;
get localType() {
// Figure out if we have a type previously set
let localType = Svc.Prefs.get("client.type", "");
if (localType == "") {
// Assume we're desktop-like unless the app is for mobiles
localType = "desktop";
switch (Svc.AppInfo.ID) {
case FENNEC_ID:
localType = "mobile";
break;
}
break;
this.localType = localType;
}
return localType;
},
set localType(value) Svc.Prefs.set("client.type", value),
// Always process incoming items because they might have commands
_reconcile: function _reconcile() {
@ -153,9 +173,6 @@ ClientEngine.prototype = {
_wipeClient: function _wipeClient() {
SyncEngine.prototype._resetClient.call(this);
this._store.wipe();
// Make sure the local client exists after wiping
this.setInfo(this.clientID, this.updateLocalInfo({}));
}
};
@ -163,72 +180,47 @@ function ClientStore(name) {
Store.call(this, name);
}
ClientStore.prototype = {
//////////////////////////////////////////////////////////////////////////////
// ClientStore Attributes
clients: {},
__proto__: Store.prototype,
//////////////////////////////////////////////////////////////////////////////
// ClientStore Methods
create: function create(record) this.update(record),
/**
* Get the client by guid
*/
getInfo: function ClientStore_getInfo(id) this.clients[id],
/**
* Set the client data for a guid. Use Engine.setInfo to update tracker.
*/
setInfo: function ClientStore_setInfo(id, info) {
this._log.debug("Setting client " + id + ": " + JSON.stringify(info));
this.clients[id] = info;
},
//////////////////////////////////////////////////////////////////////////////
// Store.prototype Methods
changeItemID: function ClientStore_changeItemID(oldID, newID) {
this._log.debug("Changing id from " + oldId + " to " + newID);
this.clients[newID] = this.clients[oldID];
delete this.clients[oldID];
},
create: function ClientStore_create(record) {
this.update(record);
update: function update(record) {
// Unpack the individual components of the local client
if (record.id == Clients.localID) {
Clients.localName = record.name;
Clients.localType = record.type;
Clients.localCommands = record.commands;
}
else
this._remoteClients[record.id] = record.cleartext;
},
createRecord: function createRecord(guid) {
let record = new ClientRecord();
record.cleartext = this.clients[guid];
let record = new ClientsRec();
// Package the individual components into a record for the local client
if (guid == Clients.localID) {
record.name = Clients.localName;
record.type = Clients.localType;
record.commands = Clients.localCommands;
}
else
record.cleartext = this._remoteClients[guid];
return record;
},
getAllIDs: function ClientStore_getAllIDs() this.clients,
itemExists: function itemExists(id) id in this.getAllIDs(),
itemExists: function ClientStore_itemExists(id) id in this.clients,
remove: function ClientStore_remove(record) {
this._log.debug("Removing client " + record.id);
delete this.clients[record.id];
getAllIDs: function getAllIDs() {
let ids = {};
ids[Clients.localID] = true;
for (let id in this._remoteClients)
ids[id] = true;
return ids;
},
update: function ClientStore_update(record) {
this._log.debug("Updating client " + record.id);
this.clients[record.id] = record.cleartext;
},
wipe: function ClientStore_wipe() {
this._log.debug("Wiping local clients store")
this.clients = {};
wipe: function wipe() {
this._remoteClients = {};
},
};
function ClientTracker(name) {
Tracker.call(this, name);
}
ClientTracker.prototype = {
__proto__: Tracker.prototype,
get score() 100 // always sync
};

View File

@ -47,7 +47,7 @@ Cu.import("resource://weave/engines.js");
Cu.import("resource://weave/stores.js");
Cu.import("resource://weave/trackers.js");
Cu.import("resource://weave/type_records/tabs.js");
Cu.import("resource://weave/engines/clientData.js");
Cu.import("resource://weave/engines/clients.js");
function TabEngine() {
SyncEngine.call(this, "Tabs");
@ -102,13 +102,11 @@ function TabStore(name) {
}
TabStore.prototype = {
__proto__: Store.prototype,
_remoteClients: {},
itemExists: function TabStore_itemExists(id) {
return id == Clients.clientID;
return id == Clients.localID;
},
getAllTabs: function getAllTabs(filter) {
let filteredUrls = new RegExp(Svc.Prefs.get("engine.tabs.filteredUrls"), "i");
@ -147,7 +145,7 @@ TabStore.prototype = {
createRecord: function createRecord(guid) {
let record = new TabSetRecord();
record.clientName = Clients.clientName;
record.clientName = Clients.localName;
// Sort tabs in descending-used order to grab the most recently used
record.tabs = this.getAllTabs(true).sort(function(a, b) {
@ -162,7 +160,7 @@ TabStore.prototype = {
getAllIDs: function TabStore_getAllIds() {
let ids = {};
ids[Clients.clientID] = true;
ids[Clients.localID] = true;
return ids;
},
@ -241,7 +239,7 @@ TabTracker.prototype = {
onTab: function onTab(event) {
this._log.trace(event.type);
this.score += 1;
this._changedIDs[Clients.clientID] = true;
this._changedIDs[Clients.localID] = true;
// Store a timestamp in the tab to track when it was last used
Svc.Session.setTabValue(event.originalTarget, "weaveLastUsed",

View File

@ -62,7 +62,7 @@ Cu.import("resource://weave/base_records/keys.js");
Cu.import("resource://weave/engines.js");
Cu.import("resource://weave/identity.js");
Cu.import("resource://weave/status.js");
Cu.import("resource://weave/engines/clientData.js");
Cu.import("resource://weave/engines/clients.js");
// for export
let Weave = {};
@ -78,7 +78,7 @@ Cu.import("resource://weave/stores.js", Weave);
Cu.import("resource://weave/engines.js", Weave);
Cu.import("resource://weave/engines/bookmarks.js", Weave);
Cu.import("resource://weave/engines/clientData.js", Weave);
Cu.import("resource://weave/engines/clients.js", Weave);
Cu.import("resource://weave/engines/forms.js", Weave);
Cu.import("resource://weave/engines/history.js", Weave);
Cu.import("resource://weave/engines/prefs.js", Weave);
@ -1143,7 +1143,7 @@ WeaveSvc.prototype = {
Clients.sync();
// Process the incoming commands if we have any
if (Clients.getClients()[Clients.clientID].commands) {
if (Clients.localCommands) {
try {
if (!(this.processCommands())) {
Status.sync = ABORT_SYNC_COMMAND;
@ -1189,16 +1189,8 @@ WeaveSvc.prototype = {
* Process the locally stored clients list to figure out what mode to be in
*/
_updateClientMode: function _updateClientMode() {
let numClients = 0;
let hasMobile = false;
// Check how many and what type of clients we have
for each (let {type} in Clients.getClients()) {
numClients++;
hasMobile = hasMobile || type == "mobile";
}
// Nothing to do if it's the same amount
let {numClients, hasMobile} = Clients.stats;
if (this.numClients == numClients)
return;
@ -1441,12 +1433,9 @@ WeaveSvc.prototype = {
*/
processCommands: function WeaveSvc_processCommands()
this._notify("process-commands", "", function() {
let info = Clients.getInfo(Clients.clientID);
let commands = info.commands;
// Immediately clear out the commands as we've got them locally
delete info.commands;
Clients.setInfo(Clients.clientID, info);
let commands = Clients.localCommands;
Clients.clearCommands();
// Process each command in order
for each ({command: command, args: args} in commands) {
@ -1502,40 +1491,9 @@ WeaveSvc.prototype = {
return;
}
// Package the command/args pair into an object
let action = {
command: command,
args: args,
};
let actionStr = command + "(" + args + ")";
// Convert args into a string to simplify array comparisons
let jsonArgs = JSON.stringify(args);
let notDupe = function(action) action.command != command ||
JSON.stringify(action.args) != jsonArgs;
this._log.info("Sending clients: " + actionStr + "; " + commandData.desc);
// Add the action to each remote client
for (let guid in Clients.getClients()) {
// Don't send commands to the local client
if (guid == Clients.clientID)
continue;
let info = Clients.getInfo(guid);
// Set the action to be a new commands array if none exists
if (info.commands == null)
info.commands = [action];
// Add the new action if there are no duplicates
else if (info.commands.every(notDupe))
info.commands.push(action);
// Must have been a dupe.. skip!
else
continue;
Clients.setInfo(guid, info);
this._log.trace("Client " + guid + " got a new action: " + actionStr);
}
// Send the command to all remote clients
this._log.debug("Sending clients: " + [command, args, commandData.desc]);
Clients.sendCommand(command, args);
},
};

View File

@ -34,7 +34,7 @@
*
* ***** END LICENSE BLOCK ***** */
const EXPORTED_SYMBOLS = ['ClientRecord'];
const EXPORTED_SYMBOLS = ["ClientsRec"];
const Cc = Components.classes;
const Ci = Components.interfaces;
@ -44,10 +44,12 @@ const Cu = Components.utils;
Cu.import("resource://weave/util.js");
Cu.import("resource://weave/base_records/crypto.js");
function ClientRecord(uri) {
function ClientsRec(uri) {
CryptoWrapper.call(this, uri);
}
ClientRecord.prototype = {
ClientsRec.prototype = {
__proto__: CryptoWrapper.prototype,
_logName: "Record.Client",
_logName: "Record.Clients",
};
Utils.deferGetSet(ClientsRec, "cleartext", ["name", "type", "commands"]);

View File

@ -5,7 +5,6 @@ pref("extensions.weave.miscURL", "misc/");
pref("extensions.weave.pwChangeURL", "https://auth.services.mozilla.com/weave-password-reset");
pref("extensions.weave.termsURL", "https://mozillalabs.com/weave/tos/");
pref("extensions.weave.client.name", "Firefox");
pref("extensions.weave.lastversion", "firstrun");
pref("extensions.weave.autoconnect", true);

View File

@ -1,16 +1,35 @@
Cu.import("resource://weave/engines/clientData.js");
Cu.import("resource://weave/engines/clients.js");
Cu.import("resource://weave/util.js");
Cu.import("resource://weave/base_records/keys.js");
Cu.import("resource://weave/base_records/crypto.js");
function run_test() {
let passphrase = { password: "passphrase" };
let baseUri = "http://fakebase/";
let pubUri = baseUri + "pubkey";
let privUri = baseUri + "privkey";
let cryptoUri = baseUri + "crypto";
_("Setting up fake pub/priv keypair and symkey for encrypt/decrypt");
PubKeys.defaultKeyUri = baseUri + "pubkey";
let {pubkey, privkey} = PubKeys.createKeypair(passphrase, pubUri, privUri);
PubKeys.set(pubUri, pubkey);
PrivKeys.set(privUri, privkey);
let cryptoMeta = new CryptoMeta(cryptoUri);
cryptoMeta.addUnwrappedKey(pubkey, Svc.Crypto.generateRandomKey());
CryptoMetas.set(cryptoUri, cryptoMeta);
_("Test that serializing client records results in uploadable ascii");
Clients.setInfo("ascii", {
name: "wéävê"
});
Clients.__defineGetter__("cryptoMetaURL", function() cryptoUri);
Clients.localID = "ascii";
Clients.localName = "wéävê";
_("Make sure we have the expected record");
let record = Clients._store.createRecord("ascii");
let record = Clients._createRecord("ascii");
do_check_eq(record.id, "ascii");
do_check_eq(record.payload.name, "wéävê");
do_check_eq(record.name, "wéävê");
record.encrypt(passphrase)
let serialized = JSON.stringify(record);
let checkCount = 0;
_("Checking for all ASCII:", serialized);
@ -25,11 +44,12 @@ function run_test() {
do_check_eq(checkCount, serialized.length);
_("Making sure the record still looks like it did before");
record.decrypt(passphrase)
do_check_eq(record.id, "ascii");
do_check_eq(record.payload.name, "wéävê");
do_check_eq(record.name, "wéävê");
_("Sanity check that creating the record also gives the same");
record = Clients._store.createRecord("ascii");
record = Clients._createRecord("ascii");
do_check_eq(record.id, "ascii");
do_check_eq(record.payload.name, "wéävê");
do_check_eq(record.name, "wéävê");
}

View File

@ -6,7 +6,7 @@ const modules = [
"base_records/wbo.js",
"constants.js",
"engines/bookmarks.js",
"engines/clientData.js",
"engines/clients.js",
"engines/forms.js",
"engines/history.js",
"engines/passwords.js",
@ -23,7 +23,7 @@ const modules = [
"stores.js",
"trackers.js",
"type_records/bookmark.js",
"type_records/clientData.js",
"type_records/clients.js",
"type_records/forms.js",
"type_records/history.js",
"type_records/passwords.js",