Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | this.EXPORTED_SYMBOLS = [ |
michael@0 | 6 | "ClientEngine", |
michael@0 | 7 | "ClientsRec" |
michael@0 | 8 | ]; |
michael@0 | 9 | |
michael@0 | 10 | const {classes: Cc, interfaces: Ci, utils: Cu} = Components; |
michael@0 | 11 | |
michael@0 | 12 | Cu.import("resource://services-common/stringbundle.js"); |
michael@0 | 13 | Cu.import("resource://services-sync/constants.js"); |
michael@0 | 14 | Cu.import("resource://services-sync/engines.js"); |
michael@0 | 15 | Cu.import("resource://services-sync/record.js"); |
michael@0 | 16 | Cu.import("resource://services-sync/util.js"); |
michael@0 | 17 | |
michael@0 | 18 | const CLIENTS_TTL = 1814400; // 21 days |
michael@0 | 19 | const CLIENTS_TTL_REFRESH = 604800; // 7 days |
michael@0 | 20 | |
michael@0 | 21 | const SUPPORTED_PROTOCOL_VERSIONS = ["1.1", "1.5"]; |
michael@0 | 22 | |
michael@0 | 23 | this.ClientsRec = function ClientsRec(collection, id) { |
michael@0 | 24 | CryptoWrapper.call(this, collection, id); |
michael@0 | 25 | } |
michael@0 | 26 | ClientsRec.prototype = { |
michael@0 | 27 | __proto__: CryptoWrapper.prototype, |
michael@0 | 28 | _logName: "Sync.Record.Clients", |
michael@0 | 29 | ttl: CLIENTS_TTL |
michael@0 | 30 | }; |
michael@0 | 31 | |
michael@0 | 32 | Utils.deferGetSet(ClientsRec, "cleartext", ["name", "type", "commands", "version", "protocols"]); |
michael@0 | 33 | |
michael@0 | 34 | |
michael@0 | 35 | this.ClientEngine = function ClientEngine(service) { |
michael@0 | 36 | SyncEngine.call(this, "Clients", service); |
michael@0 | 37 | |
michael@0 | 38 | // Reset the client on every startup so that we fetch recent clients |
michael@0 | 39 | this._resetClient(); |
michael@0 | 40 | } |
michael@0 | 41 | ClientEngine.prototype = { |
michael@0 | 42 | __proto__: SyncEngine.prototype, |
michael@0 | 43 | _storeObj: ClientStore, |
michael@0 | 44 | _recordObj: ClientsRec, |
michael@0 | 45 | _trackerObj: ClientsTracker, |
michael@0 | 46 | |
michael@0 | 47 | // Always sync client data as it controls other sync behavior |
michael@0 | 48 | get enabled() true, |
michael@0 | 49 | |
michael@0 | 50 | get lastRecordUpload() { |
michael@0 | 51 | return Svc.Prefs.get(this.name + ".lastRecordUpload", 0); |
michael@0 | 52 | }, |
michael@0 | 53 | set lastRecordUpload(value) { |
michael@0 | 54 | Svc.Prefs.set(this.name + ".lastRecordUpload", Math.floor(value)); |
michael@0 | 55 | }, |
michael@0 | 56 | |
michael@0 | 57 | // Aggregate some stats on the composition of clients on this account |
michael@0 | 58 | get stats() { |
michael@0 | 59 | let stats = { |
michael@0 | 60 | hasMobile: this.localType == "mobile", |
michael@0 | 61 | names: [this.localName], |
michael@0 | 62 | numClients: 1, |
michael@0 | 63 | }; |
michael@0 | 64 | |
michael@0 | 65 | for each (let {name, type} in this._store._remoteClients) { |
michael@0 | 66 | stats.hasMobile = stats.hasMobile || type == "mobile"; |
michael@0 | 67 | stats.names.push(name); |
michael@0 | 68 | stats.numClients++; |
michael@0 | 69 | } |
michael@0 | 70 | |
michael@0 | 71 | return stats; |
michael@0 | 72 | }, |
michael@0 | 73 | |
michael@0 | 74 | /** |
michael@0 | 75 | * Obtain information about device types. |
michael@0 | 76 | * |
michael@0 | 77 | * Returns a Map of device types to integer counts. |
michael@0 | 78 | */ |
michael@0 | 79 | get deviceTypes() { |
michael@0 | 80 | let counts = new Map(); |
michael@0 | 81 | |
michael@0 | 82 | counts.set(this.localType, 1); |
michael@0 | 83 | |
michael@0 | 84 | for each (let record in this._store._remoteClients) { |
michael@0 | 85 | let type = record.type; |
michael@0 | 86 | if (!counts.has(type)) { |
michael@0 | 87 | counts.set(type, 0); |
michael@0 | 88 | } |
michael@0 | 89 | |
michael@0 | 90 | counts.set(type, counts.get(type) + 1); |
michael@0 | 91 | } |
michael@0 | 92 | |
michael@0 | 93 | return counts; |
michael@0 | 94 | }, |
michael@0 | 95 | |
michael@0 | 96 | get localID() { |
michael@0 | 97 | // Generate a random GUID id we don't have one |
michael@0 | 98 | let localID = Svc.Prefs.get("client.GUID", ""); |
michael@0 | 99 | return localID == "" ? this.localID = Utils.makeGUID() : localID; |
michael@0 | 100 | }, |
michael@0 | 101 | set localID(value) Svc.Prefs.set("client.GUID", value), |
michael@0 | 102 | |
michael@0 | 103 | get localName() { |
michael@0 | 104 | let localName = Svc.Prefs.get("client.name", ""); |
michael@0 | 105 | if (localName != "") |
michael@0 | 106 | return localName; |
michael@0 | 107 | |
michael@0 | 108 | // Generate a client name if we don't have a useful one yet |
michael@0 | 109 | let env = Cc["@mozilla.org/process/environment;1"] |
michael@0 | 110 | .getService(Ci.nsIEnvironment); |
michael@0 | 111 | let user = env.get("USER") || env.get("USERNAME") || |
michael@0 | 112 | Svc.Prefs.get("account") || Svc.Prefs.get("username"); |
michael@0 | 113 | |
michael@0 | 114 | let appName; |
michael@0 | 115 | let brand = new StringBundle("chrome://branding/locale/brand.properties"); |
michael@0 | 116 | let brandName = brand.get("brandShortName"); |
michael@0 | 117 | try { |
michael@0 | 118 | let syncStrings = new StringBundle("chrome://browser/locale/sync.properties"); |
michael@0 | 119 | appName = syncStrings.getFormattedString("sync.defaultAccountApplication", [brandName]); |
michael@0 | 120 | } catch (ex) {} |
michael@0 | 121 | appName = appName || brandName; |
michael@0 | 122 | |
michael@0 | 123 | let system = |
michael@0 | 124 | // 'device' is defined on unix systems |
michael@0 | 125 | Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2).get("device") || |
michael@0 | 126 | // hostname of the system, usually assigned by the user or admin |
michael@0 | 127 | Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2).get("host") || |
michael@0 | 128 | // fall back on ua info string |
michael@0 | 129 | Cc["@mozilla.org/network/protocol;1?name=http"].getService(Ci.nsIHttpProtocolHandler).oscpu; |
michael@0 | 130 | |
michael@0 | 131 | return this.localName = Str.sync.get("client.name2", [user, appName, system]); |
michael@0 | 132 | }, |
michael@0 | 133 | set localName(value) Svc.Prefs.set("client.name", value), |
michael@0 | 134 | |
michael@0 | 135 | get localType() Svc.Prefs.get("client.type", "desktop"), |
michael@0 | 136 | set localType(value) Svc.Prefs.set("client.type", value), |
michael@0 | 137 | |
michael@0 | 138 | isMobile: function isMobile(id) { |
michael@0 | 139 | if (this._store._remoteClients[id]) |
michael@0 | 140 | return this._store._remoteClients[id].type == "mobile"; |
michael@0 | 141 | return false; |
michael@0 | 142 | }, |
michael@0 | 143 | |
michael@0 | 144 | _syncStartup: function _syncStartup() { |
michael@0 | 145 | // Reupload new client record periodically. |
michael@0 | 146 | if (Date.now() / 1000 - this.lastRecordUpload > CLIENTS_TTL_REFRESH) { |
michael@0 | 147 | this._tracker.addChangedID(this.localID); |
michael@0 | 148 | this.lastRecordUpload = Date.now() / 1000; |
michael@0 | 149 | } |
michael@0 | 150 | SyncEngine.prototype._syncStartup.call(this); |
michael@0 | 151 | }, |
michael@0 | 152 | |
michael@0 | 153 | // Always process incoming items because they might have commands |
michael@0 | 154 | _reconcile: function _reconcile() { |
michael@0 | 155 | return true; |
michael@0 | 156 | }, |
michael@0 | 157 | |
michael@0 | 158 | // Treat reset the same as wiping for locally cached clients |
michael@0 | 159 | _resetClient: function _resetClient() this._wipeClient(), |
michael@0 | 160 | |
michael@0 | 161 | _wipeClient: function _wipeClient() { |
michael@0 | 162 | SyncEngine.prototype._resetClient.call(this); |
michael@0 | 163 | this._store.wipe(); |
michael@0 | 164 | }, |
michael@0 | 165 | |
michael@0 | 166 | removeClientData: function removeClientData() { |
michael@0 | 167 | let res = this.service.resource(this.engineURL + "/" + this.localID); |
michael@0 | 168 | res.delete(); |
michael@0 | 169 | }, |
michael@0 | 170 | |
michael@0 | 171 | // Override the default behavior to delete bad records from the server. |
michael@0 | 172 | handleHMACMismatch: function handleHMACMismatch(item, mayRetry) { |
michael@0 | 173 | this._log.debug("Handling HMAC mismatch for " + item.id); |
michael@0 | 174 | |
michael@0 | 175 | let base = SyncEngine.prototype.handleHMACMismatch.call(this, item, mayRetry); |
michael@0 | 176 | if (base != SyncEngine.kRecoveryStrategy.error) |
michael@0 | 177 | return base; |
michael@0 | 178 | |
michael@0 | 179 | // It's a bad client record. Save it to be deleted at the end of the sync. |
michael@0 | 180 | this._log.debug("Bad client record detected. Scheduling for deletion."); |
michael@0 | 181 | this._deleteId(item.id); |
michael@0 | 182 | |
michael@0 | 183 | // Neither try again nor error; we're going to delete it. |
michael@0 | 184 | return SyncEngine.kRecoveryStrategy.ignore; |
michael@0 | 185 | }, |
michael@0 | 186 | |
michael@0 | 187 | /** |
michael@0 | 188 | * A hash of valid commands that the client knows about. The key is a command |
michael@0 | 189 | * and the value is a hash containing information about the command such as |
michael@0 | 190 | * number of arguments and description. |
michael@0 | 191 | */ |
michael@0 | 192 | _commands: { |
michael@0 | 193 | resetAll: { args: 0, desc: "Clear temporary local data for all engines" }, |
michael@0 | 194 | resetEngine: { args: 1, desc: "Clear temporary local data for engine" }, |
michael@0 | 195 | wipeAll: { args: 0, desc: "Delete all client data for all engines" }, |
michael@0 | 196 | wipeEngine: { args: 1, desc: "Delete all client data for engine" }, |
michael@0 | 197 | logout: { args: 0, desc: "Log out client" }, |
michael@0 | 198 | displayURI: { args: 3, desc: "Instruct a client to display a URI" }, |
michael@0 | 199 | }, |
michael@0 | 200 | |
michael@0 | 201 | /** |
michael@0 | 202 | * Remove any commands for the local client and mark it for upload. |
michael@0 | 203 | */ |
michael@0 | 204 | clearCommands: function clearCommands() { |
michael@0 | 205 | delete this.localCommands; |
michael@0 | 206 | this._tracker.addChangedID(this.localID); |
michael@0 | 207 | }, |
michael@0 | 208 | |
michael@0 | 209 | /** |
michael@0 | 210 | * Sends a command+args pair to a specific client. |
michael@0 | 211 | * |
michael@0 | 212 | * @param command Command string |
michael@0 | 213 | * @param args Array of arguments/data for command |
michael@0 | 214 | * @param clientId Client to send command to |
michael@0 | 215 | */ |
michael@0 | 216 | _sendCommandToClient: function sendCommandToClient(command, args, clientId) { |
michael@0 | 217 | this._log.trace("Sending " + command + " to " + clientId); |
michael@0 | 218 | |
michael@0 | 219 | let client = this._store._remoteClients[clientId]; |
michael@0 | 220 | if (!client) { |
michael@0 | 221 | throw new Error("Unknown remote client ID: '" + clientId + "'."); |
michael@0 | 222 | } |
michael@0 | 223 | |
michael@0 | 224 | // notDupe compares two commands and returns if they are not equal. |
michael@0 | 225 | let notDupe = function(other) { |
michael@0 | 226 | return other.command != command || !Utils.deepEquals(other.args, args); |
michael@0 | 227 | }; |
michael@0 | 228 | |
michael@0 | 229 | let action = { |
michael@0 | 230 | command: command, |
michael@0 | 231 | args: args, |
michael@0 | 232 | }; |
michael@0 | 233 | |
michael@0 | 234 | if (!client.commands) { |
michael@0 | 235 | client.commands = [action]; |
michael@0 | 236 | } |
michael@0 | 237 | // Add the new action if there are no duplicates. |
michael@0 | 238 | else if (client.commands.every(notDupe)) { |
michael@0 | 239 | client.commands.push(action); |
michael@0 | 240 | } |
michael@0 | 241 | // It must be a dupe. Skip. |
michael@0 | 242 | else { |
michael@0 | 243 | return; |
michael@0 | 244 | } |
michael@0 | 245 | |
michael@0 | 246 | this._log.trace("Client " + clientId + " got a new action: " + [command, args]); |
michael@0 | 247 | this._tracker.addChangedID(clientId); |
michael@0 | 248 | }, |
michael@0 | 249 | |
michael@0 | 250 | /** |
michael@0 | 251 | * Check if the local client has any remote commands and perform them. |
michael@0 | 252 | * |
michael@0 | 253 | * @return false to abort sync |
michael@0 | 254 | */ |
michael@0 | 255 | processIncomingCommands: function processIncomingCommands() { |
michael@0 | 256 | return this._notify("clients:process-commands", "", function() { |
michael@0 | 257 | let commands = this.localCommands; |
michael@0 | 258 | |
michael@0 | 259 | // Immediately clear out the commands as we've got them locally. |
michael@0 | 260 | this.clearCommands(); |
michael@0 | 261 | |
michael@0 | 262 | // Process each command in order. |
michael@0 | 263 | for each ({command: command, args: args} in commands) { |
michael@0 | 264 | this._log.debug("Processing command: " + command + "(" + args + ")"); |
michael@0 | 265 | |
michael@0 | 266 | let engines = [args[0]]; |
michael@0 | 267 | switch (command) { |
michael@0 | 268 | case "resetAll": |
michael@0 | 269 | engines = null; |
michael@0 | 270 | // Fallthrough |
michael@0 | 271 | case "resetEngine": |
michael@0 | 272 | this.service.resetClient(engines); |
michael@0 | 273 | break; |
michael@0 | 274 | case "wipeAll": |
michael@0 | 275 | engines = null; |
michael@0 | 276 | // Fallthrough |
michael@0 | 277 | case "wipeEngine": |
michael@0 | 278 | this.service.wipeClient(engines); |
michael@0 | 279 | break; |
michael@0 | 280 | case "logout": |
michael@0 | 281 | this.service.logout(); |
michael@0 | 282 | return false; |
michael@0 | 283 | case "displayURI": |
michael@0 | 284 | this._handleDisplayURI.apply(this, args); |
michael@0 | 285 | break; |
michael@0 | 286 | default: |
michael@0 | 287 | this._log.debug("Received an unknown command: " + command); |
michael@0 | 288 | break; |
michael@0 | 289 | } |
michael@0 | 290 | } |
michael@0 | 291 | |
michael@0 | 292 | return true; |
michael@0 | 293 | })(); |
michael@0 | 294 | }, |
michael@0 | 295 | |
michael@0 | 296 | /** |
michael@0 | 297 | * Validates and sends a command to a client or all clients. |
michael@0 | 298 | * |
michael@0 | 299 | * Calling this does not actually sync the command data to the server. If the |
michael@0 | 300 | * client already has the command/args pair, it won't receive a duplicate |
michael@0 | 301 | * command. |
michael@0 | 302 | * |
michael@0 | 303 | * @param command |
michael@0 | 304 | * Command to invoke on remote clients |
michael@0 | 305 | * @param args |
michael@0 | 306 | * Array of arguments to give to the command |
michael@0 | 307 | * @param clientId |
michael@0 | 308 | * Client ID to send command to. If undefined, send to all remote |
michael@0 | 309 | * clients. |
michael@0 | 310 | */ |
michael@0 | 311 | sendCommand: function sendCommand(command, args, clientId) { |
michael@0 | 312 | let commandData = this._commands[command]; |
michael@0 | 313 | // Don't send commands that we don't know about. |
michael@0 | 314 | if (!commandData) { |
michael@0 | 315 | this._log.error("Unknown command to send: " + command); |
michael@0 | 316 | return; |
michael@0 | 317 | } |
michael@0 | 318 | // Don't send a command with the wrong number of arguments. |
michael@0 | 319 | else if (!args || args.length != commandData.args) { |
michael@0 | 320 | this._log.error("Expected " + commandData.args + " args for '" + |
michael@0 | 321 | command + "', but got " + args); |
michael@0 | 322 | return; |
michael@0 | 323 | } |
michael@0 | 324 | |
michael@0 | 325 | if (clientId) { |
michael@0 | 326 | this._sendCommandToClient(command, args, clientId); |
michael@0 | 327 | } else { |
michael@0 | 328 | for (let id in this._store._remoteClients) { |
michael@0 | 329 | this._sendCommandToClient(command, args, id); |
michael@0 | 330 | } |
michael@0 | 331 | } |
michael@0 | 332 | }, |
michael@0 | 333 | |
michael@0 | 334 | /** |
michael@0 | 335 | * Send a URI to another client for display. |
michael@0 | 336 | * |
michael@0 | 337 | * A side effect is the score is increased dramatically to incur an |
michael@0 | 338 | * immediate sync. |
michael@0 | 339 | * |
michael@0 | 340 | * If an unknown client ID is specified, sendCommand() will throw an |
michael@0 | 341 | * Error object. |
michael@0 | 342 | * |
michael@0 | 343 | * @param uri |
michael@0 | 344 | * URI (as a string) to send and display on the remote client |
michael@0 | 345 | * @param clientId |
michael@0 | 346 | * ID of client to send the command to. If not defined, will be sent |
michael@0 | 347 | * to all remote clients. |
michael@0 | 348 | * @param title |
michael@0 | 349 | * Title of the page being sent. |
michael@0 | 350 | */ |
michael@0 | 351 | sendURIToClientForDisplay: function sendURIToClientForDisplay(uri, clientId, title) { |
michael@0 | 352 | this._log.info("Sending URI to client: " + uri + " -> " + |
michael@0 | 353 | clientId + " (" + title + ")"); |
michael@0 | 354 | this.sendCommand("displayURI", [uri, this.localID, title], clientId); |
michael@0 | 355 | |
michael@0 | 356 | this._tracker.score += SCORE_INCREMENT_XLARGE; |
michael@0 | 357 | }, |
michael@0 | 358 | |
michael@0 | 359 | /** |
michael@0 | 360 | * Handle a single received 'displayURI' command. |
michael@0 | 361 | * |
michael@0 | 362 | * Interested parties should observe the "weave:engine:clients:display-uri" |
michael@0 | 363 | * topic. The callback will receive an object as the subject parameter with |
michael@0 | 364 | * the following keys: |
michael@0 | 365 | * |
michael@0 | 366 | * uri URI (string) that is requested for display. |
michael@0 | 367 | * clientId ID of client that sent the command. |
michael@0 | 368 | * title Title of page that loaded URI (likely) corresponds to. |
michael@0 | 369 | * |
michael@0 | 370 | * The 'data' parameter to the callback will not be defined. |
michael@0 | 371 | * |
michael@0 | 372 | * @param uri |
michael@0 | 373 | * String URI that was received |
michael@0 | 374 | * @param clientId |
michael@0 | 375 | * ID of client that sent URI |
michael@0 | 376 | * @param title |
michael@0 | 377 | * String title of page that URI corresponds to. Older clients may not |
michael@0 | 378 | * send this. |
michael@0 | 379 | */ |
michael@0 | 380 | _handleDisplayURI: function _handleDisplayURI(uri, clientId, title) { |
michael@0 | 381 | this._log.info("Received a URI for display: " + uri + " (" + title + |
michael@0 | 382 | ") from " + clientId); |
michael@0 | 383 | |
michael@0 | 384 | let subject = {uri: uri, client: clientId, title: title}; |
michael@0 | 385 | Svc.Obs.notify("weave:engine:clients:display-uri", subject); |
michael@0 | 386 | } |
michael@0 | 387 | }; |
michael@0 | 388 | |
michael@0 | 389 | function ClientStore(name, engine) { |
michael@0 | 390 | Store.call(this, name, engine); |
michael@0 | 391 | } |
michael@0 | 392 | ClientStore.prototype = { |
michael@0 | 393 | __proto__: Store.prototype, |
michael@0 | 394 | |
michael@0 | 395 | create: function create(record) this.update(record), |
michael@0 | 396 | |
michael@0 | 397 | update: function update(record) { |
michael@0 | 398 | // Only grab commands from the server; local name/type always wins |
michael@0 | 399 | if (record.id == this.engine.localID) |
michael@0 | 400 | this.engine.localCommands = record.commands; |
michael@0 | 401 | else |
michael@0 | 402 | this._remoteClients[record.id] = record.cleartext; |
michael@0 | 403 | }, |
michael@0 | 404 | |
michael@0 | 405 | createRecord: function createRecord(id, collection) { |
michael@0 | 406 | let record = new ClientsRec(collection, id); |
michael@0 | 407 | |
michael@0 | 408 | // Package the individual components into a record for the local client |
michael@0 | 409 | if (id == this.engine.localID) { |
michael@0 | 410 | record.name = this.engine.localName; |
michael@0 | 411 | record.type = this.engine.localType; |
michael@0 | 412 | record.commands = this.engine.localCommands; |
michael@0 | 413 | record.version = Services.appinfo.version; |
michael@0 | 414 | record.protocols = SUPPORTED_PROTOCOL_VERSIONS; |
michael@0 | 415 | } |
michael@0 | 416 | else |
michael@0 | 417 | record.cleartext = this._remoteClients[id]; |
michael@0 | 418 | |
michael@0 | 419 | return record; |
michael@0 | 420 | }, |
michael@0 | 421 | |
michael@0 | 422 | itemExists: function itemExists(id) id in this.getAllIDs(), |
michael@0 | 423 | |
michael@0 | 424 | getAllIDs: function getAllIDs() { |
michael@0 | 425 | let ids = {}; |
michael@0 | 426 | ids[this.engine.localID] = true; |
michael@0 | 427 | for (let id in this._remoteClients) |
michael@0 | 428 | ids[id] = true; |
michael@0 | 429 | return ids; |
michael@0 | 430 | }, |
michael@0 | 431 | |
michael@0 | 432 | wipe: function wipe() { |
michael@0 | 433 | this._remoteClients = {}; |
michael@0 | 434 | }, |
michael@0 | 435 | }; |
michael@0 | 436 | |
michael@0 | 437 | function ClientsTracker(name, engine) { |
michael@0 | 438 | Tracker.call(this, name, engine); |
michael@0 | 439 | Svc.Obs.add("weave:engine:start-tracking", this); |
michael@0 | 440 | Svc.Obs.add("weave:engine:stop-tracking", this); |
michael@0 | 441 | } |
michael@0 | 442 | ClientsTracker.prototype = { |
michael@0 | 443 | __proto__: Tracker.prototype, |
michael@0 | 444 | |
michael@0 | 445 | _enabled: false, |
michael@0 | 446 | |
michael@0 | 447 | observe: function observe(subject, topic, data) { |
michael@0 | 448 | switch (topic) { |
michael@0 | 449 | case "weave:engine:start-tracking": |
michael@0 | 450 | if (!this._enabled) { |
michael@0 | 451 | Svc.Prefs.observe("client.name", this); |
michael@0 | 452 | this._enabled = true; |
michael@0 | 453 | } |
michael@0 | 454 | break; |
michael@0 | 455 | case "weave:engine:stop-tracking": |
michael@0 | 456 | if (this._enabled) { |
michael@0 | 457 | Svc.Prefs.ignore("clients.name", this); |
michael@0 | 458 | this._enabled = false; |
michael@0 | 459 | } |
michael@0 | 460 | break; |
michael@0 | 461 | case "nsPref:changed": |
michael@0 | 462 | this._log.debug("client.name preference changed"); |
michael@0 | 463 | this.addChangedID(Svc.Prefs.get("client.GUID")); |
michael@0 | 464 | this.score += SCORE_INCREMENT_XLARGE; |
michael@0 | 465 | break; |
michael@0 | 466 | } |
michael@0 | 467 | } |
michael@0 | 468 | }; |