From 81e31b2cd0fabd5fae51de19a7c23be4f68606a7 Mon Sep 17 00:00:00 2001 From: "ben%bengoodger.com" Date: Mon, 12 Jul 2004 08:57:18 +0000 Subject: [PATCH] 245953 - software update works poorly, including: - various problems with update wizard - hides app update notification icon for that session after the user has visited the products page - makes sure the app update info is cleared when no newer versions are present - make app update work when extension update is disabled, general code correctness etc. - make sure the browser window that contains the product page is focused. git-svn-id: svn://10.0.0.236/trunk@159051 18797224-902f-48f8-a5cc-f745e15eee43 --- mozilla/browser/app/profile/firefox.js | 1 + mozilla/browser/app/splash.rc | 4 +- mozilla/browser/base/content/browser.js | 3 + mozilla/browser/config/version.txt | 2 +- .../extensions/src/nsExtensionManager.js.in | 46 +-- .../toolkit/mozapps/update/content/update.js | 107 +++++- .../toolkit/mozapps/update/content/update.xul | 1 - .../mozapps/update/content/updates.xml | 43 ++- .../mozapps/update/locale/update.properties | 1 + .../update/public/nsIUpdateService.idl | 5 + .../update/src/nsBackgroundUpdateService.js | 361 ++++++++++++------ 11 files changed, 412 insertions(+), 162 deletions(-) diff --git a/mozilla/browser/app/profile/firefox.js b/mozilla/browser/app/profile/firefox.js index e099ba36301..52d72a6b2b7 100644 --- a/mozilla/browser/app/profile/firefox.js +++ b/mozilla/browser/app/profile/firefox.js @@ -73,6 +73,7 @@ pref("update.extensions.autoUpdate", false); pref("update.interval", 604800000); // every 7 days pref("update.lastUpdateDate", 0); // UTC offset when last update was performed. +pref("update.showSlidingNotification", true); // windows-only slide-up taskbar notification // These prefs relate to the number and severity of updates available. This is a // cache that the browser notification mechanism uses to determine if it should show diff --git a/mozilla/browser/app/splash.rc b/mozilla/browser/app/splash.rc index d314a65ed31..391d62d80bc 100644 --- a/mozilla/browser/app/splash.rc +++ b/mozilla/browser/app/splash.rc @@ -51,8 +51,8 @@ BEGIN ID_DDE_APPLICATION_NAME, "Firefox Debug" IDS_STARTMENU_APPNAME, "Firefox Debug" #else - ID_DDE_APPLICATION_NAME, "Firefox" - IDS_STARTMENU_APPNAME, "Firefox" + ID_DDE_APPLICATION_NAME, "Firefox Debug" + IDS_STARTMENU_APPNAME, "Firefox Debug" #endif END diff --git a/mozilla/browser/base/content/browser.js b/mozilla/browser/base/content/browser.js index 4eddd267e5c..aee524302cd 100644 --- a/mozilla/browser/base/content/browser.js +++ b/mozilla/browser/base/content/browser.js @@ -539,6 +539,9 @@ function delayedStartup() shell.shouldCheckDefaultBrowser = checkEveryTime.value; } #endif + + var updatePanel = document.getElementById("statusbar-updates"); + updatePanel.init(); } function Shutdown() diff --git a/mozilla/browser/config/version.txt b/mozilla/browser/config/version.txt index 0c3f55c9d7b..e0adc5bc7a6 100644 --- a/mozilla/browser/config/version.txt +++ b/mozilla/browser/config/version.txt @@ -1 +1 @@ -0.9.0+ +0.9.1+ diff --git a/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in b/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in index fb0528968e9..6f231e2509d 100644 --- a/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in +++ b/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in @@ -40,7 +40,7 @@ const nsIUpdateService = Components.interfaces.nsIUpdateService; const nsIUpdateItem = Components.interfaces.nsIUpdateItem; const PREF_EM_APP_ID = "app.id"; -const PREF_EM_APP_VERSION = "app.version"; +const PREF_EM_APP_EXTENSIONS_VERSION = "app.extensions.version"; const PREF_EM_APP_BUILDID = "app.build_id"; const PREF_EM_LAST_APP_VERSION = "extensions.lastAppVersion"; const PREF_UPDATE_COUNT = "update.extensions.count"; @@ -161,35 +161,27 @@ const ROOT_THEME = "urn:mozilla:theme:root"; function getItemPrefix(aItemType) { var prefix = ""; - switch (aItemType) { - case nsIUpdateItem.TYPE_EXTENSION: + if (aItemType & nsIUpdateItem.TYPE_EXTENSION) prefix = PREFIX_EXTENSION; - break; - case nsIUpdateItem.TYPE_THEME: + else if (aItemType & nsIUpdateItem.TYPE_THEME) prefix = PREFIX_THEME; - break; - } return prefix; } function getItemRoot(aItemType) { var root = ""; - switch (aItemType) { - case nsIUpdateItem.TYPE_EXTENSION: + if (aItemType & nsIUpdateItem.TYPE_EXTENSION) root = ROOT_EXTENSION; - break; - case nsIUpdateItem.TYPE_THEME: + else if (aItemType & nsIUpdateItem.TYPE_THEME) root = ROOT_THEME; - break; - } return root; } function getItemRoots(aItemType) { var roots = []; - if (aItemType == nsIUpdateItem.TYPE_ADDON) + if (aItemType & nsIUpdateItem.TYPE_ADDON) roots = roots.concat([getItemRoot(nsIUpdateItem.TYPE_EXTENSION), getItemRoot(nsIUpdateItem.TYPE_THEME)]); else @@ -1680,9 +1672,9 @@ nsExtensionManager.prototype = { } if (file.exists()) { - if (aItemType == nsIUpdateItem.TYPE_EXTENSION) + if (aItemType & nsIUpdateItem.TYPE_EXTENSION) this.installExtension(file, nsIExtensionManager.FLAG_INSTALL_GLOBAL); - else if (aItemType == nsIUpdateItem.TYPE_THEME) + else if (aItemType & nsIUpdateItem.TYPE_THEME) this.installTheme(file, nsIExtensionManager.FLAG_INSTALL_GLOBAL); } else @@ -1826,7 +1818,7 @@ nsExtensionManager.prototype = { // now is the same one that was started last time. var pref = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefBranch); - var currAppVersion = pref.getCharPref(PREF_EM_APP_VERSION); + var currAppVersion = pref.getCharPref(PREF_EM_APP_EXTENSIONS_VERSION); try { var lastAppVersion = pref.getCharPref(PREF_EM_LAST_APP_VERSION); } @@ -1843,9 +1835,9 @@ nsExtensionManager.prototype = { for (var i = 0; i < items.length; ++i) { // Now disable the extension so it won't hurt anything. var itemType = getItemType(this._ds._getResourceForItem(items[i].id).Value); - if (itemType == nsIUpdateItem.TYPE_EXTENSION) + if (itemType & nsIUpdateItem.TYPE_EXTENSION) this.disableExtension(items[i].id); - else if (itemType == nsIUpdateItem.TYPE_THEME) { + else if (itemType & nsIUpdateItem.TYPE_THEME) { var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"] .getService(Components.interfaces.nsIXULChromeRegistry); var pref = Components.classes["@mozilla.org/preferences-service;1"] @@ -2263,7 +2255,7 @@ nsExtensionManager.prototype = { var pref = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefBranch); var appID = pref.getCharPref(PREF_EM_APP_ID); - var appVersion = pref.getCharPref(PREF_EM_APP_VERSION); + var appVersion = pref.getCharPref(PREF_EM_APP_EXTENSIONS_VERSION); if (aItems.length == 0) { var addonType = nsIUpdateItem.TYPE_ADDON; @@ -2680,12 +2672,14 @@ nsExtensionItemUpdater.prototype = { var version = aDatasource.GetTarget(extensionRes, versionArc, true); if (!version) { // Report an error if the update manifest is incomplete this.onDatasourceError(aItem, "malformed-rdf"); + // XXXben show console message return; } version = version.QueryInterface(Components.interfaces.nsIRDFLiteral).Value; var updateLink = aDatasource.GetTarget(extensionRes, updateLinkArc, true); if (!updateLink) { // Report an error if the update manifest is incomplete this.onDatasourceError(aItem, "malformed-rdf"); + // XXXben show console message return; } updateLink = updateLink.QueryInterface(Components.interfaces.nsIRDFLiteral).Value; @@ -2832,7 +2826,7 @@ nsExtensionsDataSource.prototype = { var pref = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefBranch); - var appVersion = pref.getCharPref(PREF_EM_APP_VERSION); + var appVersion = pref.getCharPref(PREF_EM_APP_EXTENSIONS_VERSION); var appID = pref.getCharPref(PREF_EM_APP_ID); var targets = aDS.GetTargets(aSource, this._emR("targetApplication"), true); @@ -2871,7 +2865,7 @@ nsExtensionsDataSource.prototype = { var itemType = getItemType(e.Value); var id = stripPrefix(e.Value, itemType); var item = Components.classes["@mozilla.org/updates/item;1"] - .createInstance(Components.interfaces.nsIUpdateItem); + .createInstance(Components.interfaces.nsIUpdateItem); item.init(id, this._getItemProperty(e, "version"), this._getItemProperty(e, "name"), -1, "", "", this._getItemProperty(e, "updateURL"), @@ -2912,7 +2906,7 @@ nsExtensionsDataSource.prototype = { item.init(id, this.getItemProperty(id, "version"), this.getItemProperty(id, "name"), -1, "", "", - this.getItemProperty(id, "updateURL"), aType); + this.getItemProperty(id, "updateURL"), getItemType(roots[i])); items.push(item); } } @@ -3455,7 +3449,7 @@ nsExtensionsDataSource.prototype = { if (aProperty.EqualsNode(this._emR("iconURL"))) { var itemType = getItemType(aSource.Value); - if (itemType == nsIUpdateItem.TYPE_EXTENSION) { + if (itemType & nsIUpdateItem.TYPE_EXTENSION) { var hasIconURL = this._composite.hasArcOut(aSource, aProperty); // If the download entry doesn't have a IconURL property, use a // generic icon URL instead. @@ -3478,7 +3472,7 @@ nsExtensionsDataSource.prototype = { } } } - else if (itemType == nsIUpdateItem.TYPE_THEME) { + else if (itemType & nsIUpdateItem.TYPE_THEME) { var res = this._getThemeJARURL(aSource, "icon.png", "chrome://mozapps/skin/extensions/themeGeneric.png"); if (res) return res; @@ -3486,7 +3480,7 @@ nsExtensionsDataSource.prototype = { } else if (aProperty.EqualsNode(this._emR("previewImage"))) { var itemType = getItemType(aSource.Value); - if (itemType == nsIUpdateItem.TYPE_THEME) { + if (itemType & nsIUpdateItem.TYPE_THEME) { var res = this._getThemeJARURL(aSource, "preview.png", null); if (res) return res; diff --git a/mozilla/toolkit/mozapps/update/content/update.js b/mozilla/toolkit/mozapps/update/content/update.js index d28fe2549b0..051c46d70a4 100644 --- a/mozilla/toolkit/mozapps/update/content/update.js +++ b/mozilla/toolkit/mozapps/update/content/update.js @@ -43,9 +43,14 @@ const nsIUpdateItem = Components.interfaces.nsIUpdateItem; const nsIUpdateService = Components.interfaces.nsIUpdateService; const nsIExtensionManager = Components.interfaces.nsIExtensionManager; -const PREF_APP_ID = "app.id"; -const PREF_UPDATE_APP_UPDATESAVAILABLE = "update.app.updatesAvailable"; -const PREF_UPDATE_EXTENSIONS_ENABLED = "update.extensions.enabled"; +const PREF_APP_ID = "app.id"; +const PREF_UPDATE_EXTENSIONS_ENABLED = "update.extensions.enabled"; +const PREF_UPDATE_APP_UPDATESAVAILABLE = "update.app.updatesAvailable"; +const PREF_UPDATE_APP_UPDATEVERSION = "update.app.updateVersion"; +const PREF_UPDATE_APP_UPDATEDESCRIPTION = "update.app.updateDescription"; +const PREF_UPDATE_APP_UPDATEURL = "update.app.updateURL"; + +const PREF_UPDATE_EXTENSIONS_COUNT = "update.extensions.count"; var gSourceEvent = null; var gUpdateTypes = null; @@ -64,6 +69,7 @@ var gUpdateWizard = { shouldAutoCheck: false, updatingApp: false, + remainingExtensionUpdateCount: 0, init: function () { @@ -85,11 +91,6 @@ var gUpdateWizard = { gMismatchPage.init(); }, - uninit: function () - { - gUpdatePage.uninit(); - }, - onWizardFinish: function () { if (this.shouldSuggestAutoChecking) { @@ -121,10 +122,82 @@ var gUpdateWizard = { .createInstance(Components.interfaces.nsISupportsString); url.data = updates.appUpdateURL; ary.AppendElement(url); - ww.openWindow(null, "chrome://browser/content/browser.xul", - "_blank", "chrome,all,dialog=no", ary); + + function obs(aWindow) + { + this._win = aWindow; + } + obs.prototype = { + _win: null, + notify: function (aTimer) + { + this._win.focus(); + } + }; + + var win = ww.openWindow(null, "chrome://browser/content/browser.xul", + "_blank", "chrome,all,dialog=no", ary); + var timer = Components.classes["@mozilla.org/timer;1"] + .createInstance(Components.interfaces.nsITimer); + timer.initWithCallback(new obs(win), 100, + Components.interfaces.nsITimer.TYPE_ONE_SHOT); #endif + + // Clear the "app update available" pref as an interim amnesty assuming + // the user actually does install the new version. If they don't, a subsequent + // update check will poke them again. + this.clearAppUpdatePrefs(); } + else { + // Downloading and Installed Extension + this.clearExtensionUpdatePrefs(); + } + + // Send an event to refresh any FE notification components. + var os = Components.classes["@mozilla.org/observer-service;1"] + .getService(Components.interfaces.nsIObserverService); + var userEvt = Components.interfaces.nsIUpdateService.SOURCE_EVENT_USER; + os.notifyObservers(null, "Update:Ended", userEvt.toString()); + }, + + clearAppUpdatePrefs: function () + { + var pref = Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefBranch); + + // Set the "applied app updates this session" pref so that the update service + // does not display the update notifier for application updates anymore this + // session, to give the user a one-cycle amnesty to install the newer version. + var updates = Components.classes["@mozilla.org/updates/update-service;1"] + .getService(Components.interfaces.nsIUpdateService); + updates.appUpdatesAvailable = false; + + // Unset prefs used by the update service to signify application updates + if (pref.prefHasUserValue(PREF_UPDATE_APP_UPDATESAVAILABLE)) + pref.clearUserPref(PREF_UPDATE_APP_UPDATESAVAILABLE); + if (pref.prefHasUserValue(PREF_UPDATE_APP_UPDATEVERSION)) + pref.clearUserPref(PREF_UPDATE_APP_UPDATEVERSION); + if (pref.prefHasUserValue(PREF_UPDATE_APP_UPDATEURL)) + pref.clearUserPref(PREF_UPDATE_APP_UPDATEDESCRIPTION); + if (pref.prefHasUserValue(PREF_UPDATE_APP_UPDATEURL)) + pref.clearUserPref(PREF_UPDATE_APP_UPDATEURL); + }, + + clearExtensionUpdatePrefs: function () + { + var pref = Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefBranch); + + // Set the "applied extension updates this session" pref so that the + // update service does not display the update notifier for extension + // updates anymore this session (the updates will be applied at the next + // start). + var updates = Components.classes["@mozilla.org/updates/update-service;1"] + .getService(Components.interfaces.nsIUpdateService); + updates.extensionUpdatesAvailable = this.remainingExtensionUpdateCount; + + if (pref.prefHasUserValue(PREF_UPDATE_EXTENSIONS_COUNT)) + pref.clearUserPref(PREF_UPDATE_EXTENSIONS_COUNT); }, _setUpButton: function (aButtonID, aButtonKey, aDisabled) @@ -208,7 +281,6 @@ var gMismatchPage = { var gUpdatePage = { _completeCount: 0, - _updateState: 0, _messages: ["Update:Extension:Started", "Update:Extension:Ended", "Update:Extension:Item-Started", @@ -235,8 +307,6 @@ var gUpdatePage = { .getService(Components.interfaces.nsIUpdateService); updates.checkForUpdatesInternal(gUpdateWizard.items, gUpdateWizard.items.length, gUpdateTypes, gSourceEvent); - - this._updateState = nsIUpdateService.UPDATED_NONE; }, uninit: function () @@ -319,6 +389,7 @@ var gUpdatePage = { } if (canFinish) { + gUpdatePage.uninit(); if (gUpdateWizard.itemsToUpdate.length > 0 || gUpdateWizard.appUpdatesAvailable) document.getElementById("checking").setAttribute("next", "found"); document.documentElement.advance(); @@ -350,7 +421,7 @@ var gFoundPage = { // If we have an App entry in the list, check it and uncheck // the others since the two are mutually exclusive installs. updateitem.type = item.type; - if (item.type == nsIUpdateItem.TYPE_APP) { + if (item.type & nsIUpdateItem.TYPE_APP) { updateitem.checked = true; this._appUpdateExists = true; this._appSelected = true; @@ -373,7 +444,7 @@ var gFoundPage = { { var i; if (this._appUpdateExists) { - if (aEvent.target.type == nsIUpdateItem.TYPE_APP) { + if (aEvent.target.type & nsIUpdateItem.TYPE_APP) { for (i = 0; i < this._nonAppItems.length; ++i) { var nonAppItem = this._nonAppItems[i]; nonAppItem.checked = !aEvent.target.checked; @@ -421,11 +492,13 @@ var gInstallingPage = { // Get XPInstallManager and kick off download/install // process, registering us as an observer. var items = []; + + gUpdateWizard.remainingExtensionUpdateCount = gUpdateWizard.itemsToUpdate.length; var foundList = document.getElementById("foundList"); for (var i = 0; i < foundList.childNodes.length; ++i) { var item = foundList.childNodes[i]; - if (item.type != nsIUpdateItem.TYPE_APP) { + if (!(item.type & nsIUpdateItem.TYPE_APP) && item.checked) { items.push(item.url); this._objs.push({ name: item.name }); } @@ -460,6 +533,8 @@ var gInstallingPage = { this._objs[aIndex].error = aValue; this._errors = true; } + else + --gUpdateWizard.remainingExtensionUpdateCount; break; case nsIXPIProgressDialog.DIALOG_CLOSE: document.getElementById("installing").setAttribute("next", this._errors ? "errors" : "finished"); diff --git a/mozilla/toolkit/mozapps/update/content/update.xul b/mozilla/toolkit/mozapps/update/content/update.xul index db6609cbab9..3cdd621333d 100644 --- a/mozilla/toolkit/mozapps/update/content/update.xul +++ b/mozilla/toolkit/mozapps/update/content/update.xul @@ -51,7 +51,6 @@ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" title="&updateWizard.title;" onload="gUpdateWizard.init()" - onunload="gUpdateWizard.uninit()" onwizardfinish="gUpdateWizard.onWizardFinish();" style="width: 40em;" buttons="accept,cancel"> diff --git a/mozilla/toolkit/mozapps/update/content/updates.xml b/mozilla/toolkit/mozapps/update/content/updates.xml index 9ab6dd94305..39d6fda97ad 100644 --- a/mozilla/toolkit/mozapps/update/content/updates.xml +++ b/mozilla/toolkit/mozapps/update/content/updates.xml @@ -18,15 +18,40 @@ - - + + - + var os = Components.classes["@mozilla.org/observer-service;1"] + .getService(Components.interfaces.nsIObserverService); + os.removeObserver(this, "Update:Ended"); + ]]> + + + + + + + + + + + + + + + + - 0) + key = "updatesAvailableTooltip-" + this.severity; + else + key = "updatesCheckForUpdatesTooltip"; var tooltip = updateStrings.GetStringFromName(key); this.setAttribute("tooltiptext", tooltip); ]]> diff --git a/mozilla/toolkit/mozapps/update/locale/update.properties b/mozilla/toolkit/mozapps/update/locale/update.properties index 4aba8871881..396c8e86502 100644 --- a/mozilla/toolkit/mozapps/update/locale/update.properties +++ b/mozilla/toolkit/mozapps/update/locale/update.properties @@ -22,3 +22,4 @@ updatesAvailableTooltip-0=Update(s) Available updatesAvailableTooltip-1=Update(s) Available updatesAvailableTooltip-2=Critical Update(s) Available +updatesCheckForUpdatesTooltip=Double-click here to check for Updates \ No newline at end of file diff --git a/mozilla/toolkit/mozapps/update/public/nsIUpdateService.idl b/mozilla/toolkit/mozapps/update/public/nsIUpdateService.idl index 305db2702e5..48045f382bf 100644 --- a/mozilla/toolkit/mozapps/update/public/nsIUpdateService.idl +++ b/mozilla/toolkit/mozapps/update/public/nsIUpdateService.idl @@ -35,6 +35,7 @@ * * ***** END LICENSE BLOCK ***** */ +// XXXben - document these files #include "nsISupports.idl" @@ -93,6 +94,10 @@ interface nsIUpdateService : nsISupports readonly attribute string appUpdateVersion; readonly attribute wstring appUpdateDescription; readonly attribute wstring appUpdateURL; + + // these are per-session settings + attribute boolean appUpdatesAvailable; + attribute long extensionUpdatesAvailable; }; [scriptable, uuid(22d35700-5765-42e1-914b-a0da7c911a8c)] diff --git a/mozilla/toolkit/mozapps/update/src/nsBackgroundUpdateService.js b/mozilla/toolkit/mozapps/update/src/nsBackgroundUpdateService.js index 652cca7e3d1..b46280ec41d 100644 --- a/mozilla/toolkit/mozapps/update/src/nsBackgroundUpdateService.js +++ b/mozilla/toolkit/mozapps/update/src/nsBackgroundUpdateService.js @@ -35,22 +35,23 @@ * * ***** END LICENSE BLOCK ***** */ -const PREF_APP_ID = "app.id"; -const PREF_APP_VERSION = "app.version"; -const PREF_UPDATE_APP_ENABLED = "update.app.enabled"; -const PREF_UPDATE_APP_URI = "update.app.url"; -const PREF_UPDATE_APP_UPDATESAVAILABLE = "update.app.updatesAvailable"; -const PREF_UPDATE_APP_UPDATEVERSION = "update.app.updateVersion"; -const PREF_UPDATE_APP_UPDATEDESCRIPTION = "update.app.updateDescription"; -const PREF_UPDATE_APP_UPDATEURL = "update.app.updateURL"; +const PREF_APP_ID = "app.id"; +const PREF_APP_VERSION = "app.version"; +const PREF_UPDATE_APP_ENABLED = "update.app.enabled"; +const PREF_UPDATE_APP_URI = "update.app.url"; +const PREF_UPDATE_APP_UPDATESAVAILABLE = "update.app.updatesAvailable"; +const PREF_UPDATE_APP_UPDATEVERSION = "update.app.updateVersion"; +const PREF_UPDATE_APP_UPDATEDESCRIPTION = "update.app.updateDescription"; +const PREF_UPDATE_APP_UPDATEURL = "update.app.updateURL"; +const PREF_UPDATE_SHOW_SLIDING_NOTIFICATION = "update.showSlidingNotification"; -const PREF_UPDATE_EXTENSIONS_ENABLED = "update.extensions.enabled"; -const PREF_UPDATE_EXTENSIONS_AUTOUPDATE = "update.extensions.autoUpdate"; -const PREF_UPDATE_EXTENSIONS_COUNT = "update.extensions.count"; +const PREF_UPDATE_EXTENSIONS_ENABLED = "update.extensions.enabled"; +const PREF_UPDATE_EXTENSIONS_AUTOUPDATE = "update.extensions.autoUpdate"; +const PREF_UPDATE_EXTENSIONS_COUNT = "update.extensions.count"; -const PREF_UPDATE_INTERVAL = "update.interval"; -const PREF_UPDATE_LASTUPDATEDATE = "update.lastUpdateDate"; -const PREF_UPDATE_SEVERITY = "update.severity"; +const PREF_UPDATE_INTERVAL = "update.interval"; +const PREF_UPDATE_LASTUPDATEDATE = "update.lastUpdateDate"; +const PREF_UPDATE_SEVERITY = "update.severity"; const nsIUpdateService = Components.interfaces.nsIUpdateService; const nsIUpdateItem = Components.interfaces.nsIUpdateItem; @@ -63,23 +64,26 @@ function nsUpdateService() this._pref = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefBranch); this.watchForUpdates(); + + var pbi = this._pref.QueryInterface(Components.interfaces.nsIPrefBranchInternal); + pbi.addObserver(PREF_UPDATE_APP_ENABLED, this, false); + pbi.addObserver(PREF_UPDATE_EXTENSIONS_ENABLED, this, false); + pbi.addObserver(PREF_UPDATE_INTERVAL, this, false); + + // Observe xpcom-shutdown to unhook pref branch observers above to avoid + // shutdown leaks. + var os = Components.classes["@mozilla.org/observer-service;1"] + .getService(Components.interfaces.nsIObserverService); + os.addObserver(this, "xpcom-shutdown", false); } nsUpdateService.prototype = { _timer: null, _pref: null, _updateObserver: null, - - // whether or not we're currently updating. prevents multiple simultaneous - // update operations. - updating: false, - - updateEnded: function () - { - // this.updating = false; - delete this._updateObserver; - }, - + _appUpdatesEnabled: true, + _extUpdatesEnabled: true, + ///////////////////////////////////////////////////////////////////////////// // nsIUpdateService watchForUpdates: function () @@ -87,18 +91,17 @@ nsUpdateService.prototype = { // This is called when the app starts, so check to see if the time interval // expired between now and the last time an automated update was performed. // now is the same one that was started last time. - var appUpdatesEnabled = this._pref.getBoolPref(PREF_UPDATE_APP_ENABLED); - var extUpdatesEnabled = this._pref.getBoolPref(PREF_UPDATE_EXTENSIONS_ENABLED); - if (!appUpdatesEnabled && !extUpdatesEnabled) + this._appUpdatesEnabled = this._pref.getBoolPref(PREF_UPDATE_APP_ENABLED); + this._extUpdatesEnabled = this._pref.getBoolPref(PREF_UPDATE_EXTENSIONS_ENABLED); + if (!this._appUpdatesEnabled && !this._extUpdatesEnabled) return; var interval = this._pref.getIntPref(PREF_UPDATE_INTERVAL); var lastUpdateTime = this._pref.getIntPref(PREF_UPDATE_LASTUPDATEDATE); - var timeSinceLastCheck = lastUpdateTime ? Math.abs(Date.UTC() - lastUpdateTime) : 0; + var timeSinceLastCheck = lastUpdateTime ? this._nowInMilliseconds - lastUpdateTime : 0; if (timeSinceLastCheck > interval) { - // if (!this.updating) - this.checkForUpdatesInternal([], 0, nsIUpdateItem.TYPE_ANY, - nsIUpdateService.SOURCE_EVENT_BACKGROUND); + this.checkForUpdatesInternal([], 0, nsIUpdateItem.TYPE_ANY, + nsIUpdateService.SOURCE_EVENT_BACKGROUND); } else this._makeTimer(interval - timeSinceLastCheck); @@ -106,8 +109,6 @@ nsUpdateService.prototype = { checkForUpdates: function (aItems, aItemCount, aUpdateTypes, aSourceEvent, aParentWindow) { - // if (this.updating) return; - switch (aSourceEvent) { case nsIUpdateService.SOURCE_EVENT_MISMATCH: case nsIUpdateService.SOURCE_EVENT_USER: @@ -125,6 +126,9 @@ nsUpdateService.prototype = { ary.AppendElement(sourceEvent); for (var i = 0; i < aItems.length; ++i) ary.AppendElement(aItems[i]); + + // This *must* be modal so as not to break startup! This code is invoked before + // the main event loop is initiated (via checkForMismatches). ww.openWindow(aParentWindow, "chrome://mozapps/content/update/update.xul", "", "chrome,modal,centerscreen", ary); break; @@ -134,48 +138,57 @@ nsUpdateService.prototype = { // updates that this function broadcasts. this.checkForUpdatesInternal([], 0, aUpdateTypes, aSourceEvent); - // If this was a background update, reset timer. - this._makeTimer(this._pref.getIntPref(PREF_UPDATE_INTERVAL)); - break; } }, checkForUpdatesInternal: function (aItems, aItemCount, aUpdateTypes, aSourceEvent) { - // this.updating = true; - // Listen for notifications sent out by the app updater (implemented here) and the // extension updater (implemented in nsExtensionItemUpdater) + if (this._updateObserver) { + this._updateObserver.destroy(); + this._updateObserver = null; + } this._updateObserver = new nsUpdateObserver(aUpdateTypes, aSourceEvent, this); var os = Components.classes["@mozilla.org/observer-service;1"] .getService(Components.interfaces.nsIObserverService); - os.addObserver(this._updateObserver, "Update:Extension:Item-Ended", false); - os.addObserver(this._updateObserver, "Update:Extension:Ended", false); - os.addObserver(this._updateObserver, "Update:App:Ended", false); - - if ((aUpdateTypes == nsIUpdateItem.TYPE_ANY) || (aUpdateTypes == nsIUpdateItem.TYPE_APP)) { - var dsURI = this._pref.getComplexValue(PREF_UPDATE_APP_URI, - Components.interfaces.nsIPrefLocalizedString).data; - var rdf = Components.classes["@mozilla.org/rdf/rdf-service;1"] - .getService(Components.interfaces.nsIRDFService); - var ds = rdf.GetDataSource(dsURI); - var rds = ds.QueryInterface(Components.interfaces.nsIRDFRemoteDataSource) - if (rds.loaded) - this.datasourceLoaded(ds); - else { - var sink = ds.QueryInterface(Components.interfaces.nsIRDFXMLSink); - sink.addXMLSinkObserver(new nsAppUpdateXMLRDFDSObserver(this)); + if ((aUpdateTypes & nsIUpdateItem.TYPE_ANY) || (aUpdateTypes & nsIUpdateItem.TYPE_APP)) { + if (this._appUpdatesEnabled) { + os.addObserver(this._updateObserver, "Update:App:Ended", false); + + var dsURI = this._pref.getComplexValue(PREF_UPDATE_APP_URI, + Components.interfaces.nsIPrefLocalizedString).data; + var rdf = Components.classes["@mozilla.org/rdf/rdf-service;1"] + .getService(Components.interfaces.nsIRDFService); + var ds = rdf.GetDataSource(dsURI); + var rds = ds.QueryInterface(Components.interfaces.nsIRDFRemoteDataSource) + if (rds.loaded) + this.datasourceLoaded(ds); + else { + var sink = ds.QueryInterface(Components.interfaces.nsIRDFXMLSink); + sink.addXMLSinkObserver(new nsAppUpdateXMLRDFDSObserver(this)); + } } } - if (aUpdateTypes != nsIUpdateItem.TYPE_APP) { - var em = Components.classes["@mozilla.org/extensions/manager;1"] - .getService(Components.interfaces.nsIExtensionManager); - em.update(aItems, aItems.length); + if (!(aUpdateTypes & nsIUpdateItem.TYPE_APP)) { // TYPE_EXTENSION, TYPE_ANY, etc. + if (this._extUpdatesEnabled) { + os.addObserver(this._updateObserver, "Update:Extension:Item-Ended", false); + os.addObserver(this._updateObserver, "Update:Extension:Ended", false); + + var em = Components.classes["@mozilla.org/extensions/manager;1"] + .getService(Components.interfaces.nsIExtensionManager); + em.update(aItems, aItems.length); + } } - if (aSourceEvent == nsIUpdateService.SOURCE_EVENT_BACKGROUND) - this._pref.setIntPref(PREF_UPDATE_LASTUPDATEDATE, Math.abs(Date.UTC())); + if (aSourceEvent == nsIUpdateService.SOURCE_EVENT_BACKGROUND && + (this._appUpdatesEnabled || this._extUpdatesEnabled)) { + this._pref.setIntPref(PREF_UPDATE_LASTUPDATEDATE, this._nowInMilliseconds); + + // If this was a background update, reset timer. + this._makeTimer(this._pref.getIntPref(PREF_UPDATE_INTERVAL)); + } }, _rdf: null, @@ -239,6 +252,8 @@ nsUpdateService.prototype = { Components.interfaces.nsISupportsString, descStr); } + else + this._clearAppUpdatePrefs(); // The Update Wizard uses this notification to determine that the application // update process is now complete. @@ -259,9 +274,8 @@ nsUpdateService.prototype = { { // The number of available updates is the number of extension/theme/other // updates + 1 for an application update, if one is available. - var updateCount = this._pref.getIntPref(PREF_UPDATE_EXTENSIONS_COUNT); - if (this._pref.prefHasUserValue(PREF_UPDATE_APP_UPDATESAVAILABLE) && - this._pref.getBoolPref(PREF_UPDATE_APP_UPDATESAVAILABLE)) + var updateCount = this.extensionUpdatesAvailable; + if (this.appUpdatesAvailable) ++updateCount; return updateCount; }, @@ -289,14 +303,92 @@ nsUpdateService.prototype = { Components.interfaces.nsIPrefLocalizedString).data; }, + _appUpdatesAvailable: undefined, + get appUpdatesAvailable() + { + if (this._appUpdatesAvailable === undefined) { + return (this._pref.prefHasUserValue(PREF_UPDATE_APP_UPDATESAVAILABLE) && + this._pref.getBoolPref(PREF_UPDATE_APP_UPDATESAVAILABLE)); + } + return this._appUpdatesAvailable; + }, + set appUpdatesAvailable(aValue) + { + this._appUpdatesAvailable = aValue; + return aValue; + }, + + _extensionUpdatesAvailable: undefined, + get extensionUpdatesAvailable() + { + if (this._extensionUpdatesAvailable === undefined) + return this._pref.getIntPref(PREF_UPDATE_EXTENSIONS_COUNT); + return this._extensionUpdatesAvailable; + }, + set extensionUpdatesAvailable(aValue) + { + this._extensionUpdatesAvailable = aValue; + return aValue; + }, + ///////////////////////////////////////////////////////////////////////////// // nsITimerCallback notify: function (aTimer) { - // if (this.updating) return; this.checkForUpdatesInternal([], 0, nsIUpdateItem.TYPE_ANY, nsIUpdateService.SOURCE_EVENT_BACKGROUND); - this._makeTimer(this._pref.getIntPref(PREF_UPDATE_INTERVAL)); + }, + + ///////////////////////////////////////////////////////////////////////////// + // nsIObserver + observe: function (aSubject, aTopic, aData) + { + switch (aTopic) { + case "nsPref:changed": + // User changed update prefs in Tools->Options + var noUpdatingGoingOn = !this._appUpdatesEnabled && !this._extUpdatesEnabled; + this._appUpdatesEnabled = this._pref.getBoolPref(PREF_UPDATE_APP_ENABLED); + this._extUpdatesEnabled = this._pref.getBoolPref(PREF_UPDATE_EXTENSIONS_ENABLED); + + var needsNotification = false; + if (!this._appUpdatesEnabled) { + this._clearAppUpdatePrefs(); + needsNotification = true; + } + if (!this._extUpdatesEnabled) { + // Unset prefs used by the update service to signify extension updates + if (this._pref.prefHasUserValue(PREF_UPDATE_EXTENSIONS_COUNT)) + this._pref.clearUserPref(PREF_UPDATE_EXTENSIONS_COUNT); + needsNotification = true; + } + + if (needsNotification) { + var os = Components.classes["@mozilla.org/observer-service;1"] + .getService(Components.interfaces.nsIObserverService); + var backgroundEvt = Components.interfaces.nsIUpdateService.SOURCE_EVENT_BACKGROUND; + os.notifyObservers(null, "Update:Ended", backgroundEvt.toString()); + } + + var needsToStartTimer = (noUpdatingGoingOn && this._appUpdatesEnabled) || + (noUpdatingGoingOn && this._extUpdatesEnabled); + // If the update interval changed or we went from a state in which there was + // no updating going on into one in which there is, we need to start the + // update timer + if (aData == PREF_UPDATE_INTERVAL || needsToStartTimer) + this._makeTimer(this._pref.getIntPref(PREF_UPDATE_INTERVAL)); + break; + case "xpcom-shutdown": + // Clean up held observers etc to avoid leaks. + var pbi = this._pref.QueryInterface(Components.interfaces.nsIPrefBranchInternal); + pbi.removeObserver(PREF_UPDATE_APP_ENABLED, this); + pbi.removeObserver(PREF_UPDATE_EXTENSIONS_ENABLED, this); + pbi.removeObserver(PREF_UPDATE_INTERVAL, this); + + var os = Components.classes["@mozilla.org/observer-service;1"] + .getService(Components.interfaces.nsIObserverService); + os.removeObserver(this, "xpcom-shutdown"); + break; + } }, ///////////////////////////////////////////////////////////////////////////// @@ -311,12 +403,38 @@ nsUpdateService.prototype = { this._timer.initWithCallback(this, aDelay, Components.interfaces.nsITimer.TYPE_ONE_SHOT); }, + + get _nowInMilliseconds () + { + var d = new Date(); + return Date.UTC(d.getUTCFullYear(), + d.getUTCMonth(), + d.getUTCDay(), + d.getUTCHours(), + d.getUTCMinutes(), + d.getUTCSeconds(), + d.getUTCMilliseconds()); + }, + + _clearAppUpdatePrefs: function () + { + // Unset prefs used by the update service to signify application updates + if (this._pref.prefHasUserValue(PREF_UPDATE_APP_UPDATESAVAILABLE)) + this._pref.clearUserPref(PREF_UPDATE_APP_UPDATESAVAILABLE); + if (this._pref.prefHasUserValue(PREF_UPDATE_APP_UPDATEVERSION)) + this._pref.clearUserPref(PREF_UPDATE_APP_UPDATEVERSION); + if (this._pref.prefHasUserValue(PREF_UPDATE_APP_UPDATEURL)) + this._pref.clearUserPref(PREF_UPDATE_APP_UPDATEDESCRIPTION); + if (this._pref.prefHasUserValue(PREF_UPDATE_APP_UPDATEURL)) + this._pref.clearUserPref(PREF_UPDATE_APP_UPDATEURL); + }, ///////////////////////////////////////////////////////////////////////////// // nsISupports QueryInterface: function (aIID) { if (!aIID.equals(Components.interfaces.nsIUpdateService) && + !aIID.equals(Components.interfaces.nsIObserver) && !aIID.equals(Components.interfaces.nsISupports)) throw Components.results.NS_ERROR_NO_INTERFACE; return this; @@ -336,13 +454,19 @@ nsUpdateObserver.prototype = { _updateTypes: 0, _sourceEvent: 0, _updateState: 0, + _endedTimer : null, get _doneUpdating() { + var appUpdatesEnabled = this._service._appUpdatesEnabled; + var extUpdatesEnabled = this._service._extUpdatesEnabled; + var test = 0; - var updatingApp = this._updateTypes == nsIUpdateItem.TYPE_ANY || - this._updateTypes == nsIUpdateItem.TYPE_APP; - var updatingExt = this._updateTypes != nsIUpdateItem.TYPE_APP; + var updatingApp = (this._updateTypes & nsIUpdateItem.TYPE_ANY || + this._updateTypes & nsIUpdateItem.TYPE_APP) && + appUpdatesEnabled; + var updatingExt = !(this._updateTypes & nsIUpdateItem.TYPE_APP) && + extUpdatesEnabled; if (updatingApp) test |= UPDATED_APP; @@ -374,50 +498,69 @@ nsUpdateObserver.prototype = { } if (this._doneUpdating) { - // The Inline Browser Update UI uses this notification to refresh its update - // UI if necessary. - var os = Components.classes["@mozilla.org/observer-service;1"] - .getService(Components.interfaces.nsIObserverService); - os.notifyObservers(null, "Update:Ended", this._sourceEvent.toString()); - - // Show update notification UI if: - // We were updating any types and any item was found - // We were updating extensions and an extension update was found. - // We were updating app and an app update was found. - var updatesAvailable = (((this._updateTypes == nsIUpdateItem.TYPE_EXTENSION) || - (this._updateTypes == nsIUpdateItem.TYPE_ANY)) && - this._pref.getIntPref(PREF_UPDATE_EXTENSIONS_COUNT) != 0); - if (!updatesAvailable) { - updatesAvailable = ((this._updateTypes == nsIUpdateItem.TYPE_APP) || - (this._updateTypes == nsIUpdateItem.TYPE_ANY)) && - this._pref.getBoolPref(PREF_UPDATE_APP_UPDATESAVAILABLE); - } - - if (updatesAvailable && this._sourceEvent == nsIUpdateService.SOURCE_EVENT_BACKGROUND) { - var sbs = Components.classes["@mozilla.org/intl/stringbundle;1"] - .getService(Components.interfaces.nsIStringBundleService); - var bundle = sbs.createBundle("chrome://mozapps/locale/update/update.properties"); - - var alertTitle = bundle.GetStringFromName("updatesAvailableTitle"); - var alertText = bundle.GetStringFromName("updatesAvailableText"); - - var alerts = Components.classes["@mozilla.org/alerts-service;1"] - .getService(Components.interfaces.nsIAlertsService); - alerts.showAlertNotification("chrome://mozapps/skin/xpinstall/xpinstallItemGeneric.png", - alertTitle, alertText, true, "", this); - } - - os.removeObserver(this, "Update:Extension:Item-Ended"); - os.removeObserver(this, "Update:Extension:Ended"); - os.removeObserver(this, "Update:App:Ended"); - - this._service.updating = false; + // Do the finalize stuff on a timer to let other observers have a chance to + // handle + if (this._endedTimer) + this._endedTimer.cancel(); + this._endedTimer = Components.classes["@mozilla.org/timer;1"] + .createInstance(Components.interfaces.nsITimer); + this._endedTimer.initWithCallback(this, 0, + Components.interfaces.nsITimer.TYPE_ONE_SHOT); } }, + notify: function (aTimer) + { + // The Inline Browser Update UI uses this notification to refresh its update + // UI if necessary. + var os = Components.classes["@mozilla.org/observer-service;1"] + .getService(Components.interfaces.nsIObserverService); + os.notifyObservers(null, "Update:Ended", this._sourceEvent.toString()); + + // Show update notification UI if: + // We were updating any types and any item was found + // We were updating extensions and an extension update was found. + // We were updating app and an app update was found. + var updatesAvailable = (((this._updateTypes & nsIUpdateItem.TYPE_EXTENSION) || + (this._updateTypes & nsIUpdateItem.TYPE_ANY)) && + this._pref.getIntPref(PREF_UPDATE_EXTENSIONS_COUNT) != 0); + if (!updatesAvailable) { + updatesAvailable = ((this._updateTypes & nsIUpdateItem.TYPE_APP) || + (this._updateTypes & nsIUpdateItem.TYPE_ANY)) && + this._pref.getBoolPref(PREF_UPDATE_APP_UPDATESAVAILABLE); + } + + var showNotification = this._pref.getBoolPref(PREF_UPDATE_SHOW_SLIDING_NOTIFICATION); + if (showNotification && updatesAvailable && + this._sourceEvent == nsIUpdateService.SOURCE_EVENT_BACKGROUND) { + var sbs = Components.classes["@mozilla.org/intl/stringbundle;1"] + .getService(Components.interfaces.nsIStringBundleService); + var bundle = sbs.createBundle("chrome://mozapps/locale/update/update.properties"); + + var alertTitle = bundle.GetStringFromName("updatesAvailableTitle"); + var alertText = bundle.GetStringFromName("updatesAvailableText"); + + var alerts = Components.classes["@mozilla.org/alerts-service;1"] + .getService(Components.interfaces.nsIAlertsService); + alerts.showAlertNotification("chrome://mozapps/skin/xpinstall/xpinstallItemGeneric.png", + alertTitle, alertText, true, "", this); + } + + this.destroy(); + }, + + destroy: function () + { + var os = Components.classes["@mozilla.org/observer-service;1"] + .getService(Components.interfaces.nsIObserverService); + + try { os.removeObserver(this, "Update:Extension:Item-Ended"); } catch (e) { } + try { os.removeObserver(this, "Update:Extension:Ended"); } catch (e) { } + try { os.removeObserver(this, "Update:App:Ended"); } catch (e) { } + }, + //////////////////////////////////////////////////////////////////////////// - // nsIObserver - + // nsIAlertListener onAlertFinished: function () { },