/* 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/. */ "use strict"; const {classes: Cc, interfaces: Ci, utils: Cu} = Components; this.EXPORTED_SYMBOLS = ["SystemUpdateService"]; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); const CATEGORY_SYSTEM_UPDATE_PROVIDER = "system-update-provider"; const PROVIDER_ACTIVITY_IDLE = 0; const PROVIDER_ACTIVITY_CHECKING = 1; const PROVIDER_ACTIVITY_DOWNLOADING = 1 << 1; const PROVIDER_ACTIVITY_APPLYING = 1 << 2; var debug = Services.prefs.getBoolPref("dom.system_update.debug") ? (aMsg) => dump("-*- SystemUpdateService.jsm : " + aMsg + "\n") : (aMsg) => {}; XPCOMUtils.defineLazyServiceGetter(this, "ppmm", "@mozilla.org/parentprocessmessagemanager;1", "nsIMessageBroadcaster"); function ActiveProvider(aProvider) { this.id = aProvider.id; this._instance = Components.classesByID[aProvider.id].getService(Ci.nsISystemUpdateProvider); this._instance.setListener(this); } ActiveProvider.prototype = { QueryInterface: XPCOMUtils.generateQI([Ci.nsISystemUpdateListener]), _activity: PROVIDER_ACTIVITY_IDLE, destroy: function() { if (this._instance) { this._instance.unsetListener(); this._instance = null; } this.id = null; }, checkForUpdate: function() { this._execFuncIfNotInActivity(PROVIDER_ACTIVITY_CHECKING, this._instance.checkForUpdate); }, startDownload: function() { this._execFuncIfNotInActivity(PROVIDER_ACTIVITY_DOWNLOADING, this._instance.startDownload); }, stopDownload: function() { this._execFuncIfNotInActivity(PROVIDER_ACTIVITY_DOWNLOADING, this._instance.stopDownload); }, applyUpdate: function() { this._execFuncIfNotInActivity(PROVIDER_ACTIVITY_APPLYING, this._instance.applyUpdate); }, setParameter: function(aName, aValue) { return this._instance.setParameter(aName, aValue); }, getParameter: function(aName) { return this._instance.getParameter(aName); }, // nsISystemUpdateListener onUpdateAvailable: function(aType, aVersion, aDescription, aBuildDate, aSize) { this._execFuncIfActiveAndInAction(PROVIDER_ACTIVITY_CHECKING, function() { ppmm.broadcastAsyncMessage("SystemUpdate:OnUpdateAvailable", { uuid: this.id, packageInfo: { type: aType, version: aVersion, description: aDescription, buildDate: aBuildDate, size: aSize, } }); this._unsetActivity(PROVIDER_ACTIVITY_CHECKING); }.bind(this)); }, onProgress: function(aLoaded, aTotal) { this._execFuncIfActiveAndInAction(PROVIDER_ACTIVITY_DOWNLOADING, function() { ppmm.broadcastAsyncMessage("SystemUpdate:OnProgress", { uuid: this.id, loaded: aLoaded, total: aTotal, }); }.bind(this)); }, onUpdateReady: function() { this._execFuncIfActiveAndInAction(PROVIDER_ACTIVITY_DOWNLOADING, function() { ppmm.broadcastAsyncMessage("SystemUpdate:OnUpdateReady", { uuid: this.id, }); this._unsetActivity(PROVIDER_ACTIVITY_DOWNLOADING); }.bind(this)); }, onError: function(aErrMsg) { if (!SystemUpdateService._isActiveProviderId(this.id)) { return; } ppmm.broadcastAsyncMessage("SystemUpdate:OnError", { uuid: this.id, message: aErrMsg, }); this._activity = PROVIDER_ACTIVITY_IDLE; }, isIdle: function() { return this._activity === PROVIDER_ACTIVITY_IDLE; }, _isInActivity: function(aActivity) { return (this._activity & aActivity) !== PROVIDER_ACTIVITY_IDLE; }, _setActivity: function(aActivity) { this._activity |= aActivity; }, _unsetActivity: function(aActivity) { this._activity &= ~aActivity; }, _execFuncIfNotInActivity: function(aActivity, aFunc) { if (!this._isInActivity(aActivity)) { this._setActivity(aActivity); aFunc(); } }, _execFuncIfActiveAndInAction: function(aActivity, aFunc) { if (!SystemUpdateService._isActiveProviderId(this.id)) { return; } if (this._isInActivity(aActivity)) { aFunc(); } }, }; this.SystemUpdateService = { _providers: [], _activeProvider: null, _updateActiveProvider: function(aProvider) { if (this._activeProvider) { this._activeProvider.destroy(); } this._activeProvider = new ActiveProvider(aProvider); }, _isActiveProviderId: function(aId) { return (this._activeProvider && this._activeProvider.id === aId); }, init: function() { debug("init"); let messages = ["SystemUpdate:GetProviders", "SystemUpdate:GetActiveProvider", "SystemUpdate:SetActiveProvider", "SystemUpdate:CheckForUpdate", "SystemUpdate:StartDownload", "SystemUpdate:StopDownload", "SystemUpdate:ApplyUpdate", "SystemUpdate:SetParameter", "SystemUpdate:GetParameter"]; messages.forEach((function(aMsgName) { ppmm.addMessageListener(aMsgName, this); }).bind(this)); // load available provider list let catMan = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager); let entries = catMan.enumerateCategory(CATEGORY_SYSTEM_UPDATE_PROVIDER); while (entries.hasMoreElements()) { let name = entries.getNext().QueryInterface(Ci.nsISupportsCString).data; let [contractId, id] = catMan.getCategoryEntry(CATEGORY_SYSTEM_UPDATE_PROVIDER, name).split(","); this._providers.push({ id: id, name: name, contractId: contractId }); } debug("available providers: " + JSON.stringify(this._providers)); // setup default active provider let defaultActive; try { defaultActive = Services.prefs.getCharPref("dom.system_update.active"); } catch (e) {} if (defaultActive) { let defaultProvider = this._providers.find(function(aProvider) { return aProvider.contractId === defaultActive; }); if (defaultProvider) { this._updateActiveProvider(defaultProvider); } } }, addProvider: function(aClassId, aContractId, aName) { debug("addProvider"); //did not allow null or empty string to add. if(!aClassId || !aContractId || !aName) { return; } let existedProvider = this._providers.find(function(provider) { return provider.id === aClassId; }); //skip if adding the existed provider. if (existedProvider) { debug("existing providers: " + JSON.stringify(existedProvider)); return; } //dynamically add the provider info to list. this._providers.push({ id: aClassId, name: aName, contractId: aContractId }); debug("available providers: " + JSON.stringify(this._providers)); }, getProviders: function(aData, aMm) { debug("getProviders"); aData.providers = []; for (let provider of this._providers) { aData.providers.push({ name: provider.name, uuid: provider.id }); } aMm.sendAsyncMessage("SystemUpdate:GetProviders:Result:OK", aData); }, getActiveProvider: function(aData, aMm) { debug("getActiveProvider"); let self = this; let providerInfo = this._providers.find(function(provider) { return self._isActiveProviderId(provider.id); }); if (!providerInfo) { aData.error = "NotFoundError"; aMm.sendAsyncMessage("SystemUpdate:GetActiveProvider:Result:Error", aData); return; } aData.provider = { name: providerInfo.name, uuid: providerInfo.id }; aMm.sendAsyncMessage("SystemUpdate:GetActiveProvider:Result:OK", aData); }, setActiveProvider: function(aData, aMm) { debug("setActiveProvider"); let self = this; let selectedProvider = this._providers.find(function(provider) { return provider.id === aData.uuid; }); if (!selectedProvider) { aData.error = "DataError"; aMm.sendAsyncMessage("SystemUpdate:SetActiveProvider:Result:Error", aData); return; } if (!this._isActiveProviderId(selectedProvider.id)) { // not allow changing active provider while there is an ongoing update activity if (this.activeProvider && !this._activeProvider.isIdle()) { aData.error = "DataError"; aMm.sendAsyncMessage("SystemUpdate:SetActiveProvider:Result:Error", aData); return; } this._updateActiveProvider(selectedProvider); Services.prefs.setCharPref("dom.system_update.active", selectedProvider.contractId); } aData.provider = { name: selectedProvider.name, uuid: selectedProvider.id }; aMm.sendAsyncMessage("SystemUpdate:SetActiveProvider:Result:OK", aData); }, receiveMessage: function(aMessage) { if (!aMessage.target.assertPermission("system-update")) { debug("receive message " + aMessage.name + " from a content process with no 'system-update' privileges."); return null; } let msg = aMessage.data || {}; let mm = aMessage.target; switch (aMessage.name) { case "SystemUpdate:GetProviders": { this.getProviders(msg, mm); break; } case "SystemUpdate:GetActiveProvider": { this.getActiveProvider(msg, mm); break; } case "SystemUpdate:SetActiveProvider": { this.setActiveProvider(msg, mm); break; } case "SystemUpdate:CheckForUpdate": { if (this._isActiveProviderId(msg.uuid)) { this._activeProvider.checkForUpdate(); } break; } case "SystemUpdate:StartDownload": { if (this._isActiveProviderId(msg.uuid)) { this._activeProvider.startDownload(); } break; } case "SystemUpdate:StopDownload": { if (this._isActiveProviderId(msg.uuid)) { this._activeProvider.stopDownload(); } break; } case "SystemUpdate:ApplyUpdate": { if (this._isActiveProviderId(msg.uuid)) { this._activeProvider.applyUpdate(); } break; } case "SystemUpdate:SetParameter": { if (this._isActiveProviderId(msg.uuid)) { return this._activeProvider.setParameter(msg.name, msg.value); } break; } case "SystemUpdate:GetParameter": { if (this._isActiveProviderId(msg.uuid)) { return this._activeProvider.getParameter(msg.name); } break; } } }, }; SystemUpdateService.init();