diff --git a/browser/app/nsBrowserApp.cpp b/browser/app/nsBrowserApp.cpp index ee0886c17a2..6cf433c55cd 100644 --- a/browser/app/nsBrowserApp.cpp +++ b/browser/app/nsBrowserApp.cpp @@ -47,7 +47,7 @@ static const nsXREAppData kAppData = { sizeof(nsXREAppData), nsnull, "Mozilla", - "Firefox", + "Firefox Debug", NS_STRINGIFY(APP_VERSION), NS_STRINGIFY(BUILD_ID), "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 38bf3ac8413..c085f1d1234 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -42,6 +42,8 @@ # SYNTAX HINTS: dashes are delimiters. Use underscores instead. # The first character after a period must be alphabetic. +pref("app.update.logEnabled", true); + // pref("startup.homepage_override_url","chrome://browser-region/locale/region.properties"); pref("general.startup.browser", true); @@ -80,15 +82,27 @@ pref("app.update.autoInstallMode", 0); // XXX these prefs and others like them are distribution specific and should move // into chrome://browser +// Default service URL for testing. pref("app.update.url", "chrome://mozapps/locale/update/updates.properties"); +// URL user can browse to manually if for some reason all update installation +// attempts fail. pref("app.update.url.manual", "chrome://mozapps/locale/update/updates.properties"); +// User-settable update preference that overrides app.update.url for testing +// purposes. pref("app.update.url.override", "chrome://mozapps/locale/update/updates.properties"); -pref("app.update.updatesAvailable", false); -// Check for updates to Firefox every day -pref("app.update.interval", 86400000); + +// Interval: Time between checks for a new version (in seconds) +// default=1 day +pref("app.update.interval", 86400); +// Interval: Time before prompting the user to download a new version that +// is available (in seconds) default=1 day +pref("app.update.nagTimer.download", 86400); +// Interval: Time before prompting the user to restart to install the latest +// download (in seconds) default=30 minutes +pref("app.update.nagTimer.restart", 1800); +// Interval: When all registered timers should be checked (in milliseconds) +// default=5 seconds pref("app.update.timer", 5000); -// UTC offset when last App update was performed. -pref("app.update.lastUpdateDate", 0); // Symmetric (can be overridden by individual extensions) update preferences. // e.g. diff --git a/toolkit/locales/en-US/chrome/mozapps/update/updates.properties b/toolkit/locales/en-US/chrome/mozapps/update/updates.properties index 767f7a85c7b..15490622f81 100755 --- a/toolkit/locales/en-US/chrome/mozapps/update/updates.properties +++ b/toolkit/locales/en-US/chrome/mozapps/update/updates.properties @@ -6,6 +6,7 @@ introType_major=A new version of %S is available: verificationError=%S could not confirm the integrity of the update package. errorsPageHeader=Update Failed IAgreeLabel=I Agree +IDoNotAgreeLabel=I Disagree license404Error=The license file could not be found. Please contact the distributor. downloadingLicense=Downloading license text... statusSucceededFormat=Installed on: %S diff --git a/toolkit/mozapps/update/content/updates.js b/toolkit/mozapps/update/content/updates.js index 6e2265b4190..9675f67fa2f 100755 --- a/toolkit/mozapps/update/content/updates.js +++ b/toolkit/mozapps/update/content/updates.js @@ -40,7 +40,9 @@ const nsIIncrementalDownload = Components.interfaces.nsIIncrementalDownload; const XMLNS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; -const PREF_UPDATE_MANUAL_URL = "app.update.url.manual"; +const PREF_UPDATE_MANUAL_URL = "app.update.url.manual"; +const PREF_UPDATE_NAGTIMER_DL = "app.update.nagTimer.download"; +const PREF_UPDATE_NAGTIMER_RESTART = "app.update.nagTimer.restart"; const URI_UPDATES_PROPERTIES = "chrome://mozapps/locale/update/updates.properties"; @@ -53,6 +55,8 @@ const STATE_FAILED = "failed"; const SRCEVT_FOREGROUND = 1; const SRCEVT_BACKGROUND = 2; +var gPref = null; + /** * Logs a string to the error console. * @param string @@ -62,6 +66,28 @@ function LOG(string) { dump("*** " + string + "\n"); } +/** + * Gets a preference value, handling the case where there is no default. + * @param func + * The name of the preference function to call, on nsIPrefBranch + * @param preference + * The name of the preference + * @param defaultValue + * The default value to return in the event the preference has + * no setting + * @returns The value of the preference, or undefined if there was no + * user or default value. + */ +function getPref(func, preference, defaultValue) { + try { + return gPref[func](preference); + } + catch (e) { + LOG("FAIL = " + e); + } + return defaultValue; +} + var gUpdates = { update : null, strings : null, @@ -111,6 +137,9 @@ var gUpdates = { * Called when the wizard UI is loaded. */ onLoad: function() { + gPref = Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefBranch2); + this.strings = document.getElementById("updateStrings"); var brandStrings = document.getElementById("brandStrings"); this.brandName = brandStrings.getString("brandShortName"); @@ -121,9 +150,17 @@ var gUpdates = { if (page.localName == "wizardpage") this._pages[page.pageid] = eval(page.getAttribute("object")); } + + var de = document.documentElement; + + // Cache the standard button labels in case we need to restore them + this.buttonLabel_back = de.getButton("back").label; + this.buttonLabel_next = de.getButton("next").label; + this.buttonLabel_finish = de.getButton("finish").label; + this.buttonLabel_cancel = de.getButton("cancel").label; // Advance to the Start page. - document.documentElement.currentPage = this.startPage; + de.currentPage = this.startPage; }, /** @@ -145,7 +182,8 @@ var gUpdates = { // their permission to install, and it's ready for download. this.update = arg0; this.sourceEvent = SRCEVT_BACKGROUND; - if (this.update.selectedPatch.state == STATE_PENDING) + if (this.update.selectedPatch && + this.update.selectedPatch.state == STATE_PENDING) return document.getElementById("finishedBackground"); return document.getElementById("updatesfound"); } @@ -185,7 +223,69 @@ var gUpdates = { errorPage.setAttribute("label", pageTitle); document.documentElement.currentPage = document.getElementById("errors"); document.documentElement.setAttribute("label", pageTitle); - } + }, + + /** + * Registers a timer to nag the user about something relating to update + * @param timerID + * The ID of the timer to register, used for persistence + * @param timerInterval + * The interval of the timer + * @param methodName + * The method to call on the Update Prompter when the timer fires + */ + registerNagTimer: function(timerID, timerInterval, methodName) { + // Remind the user to restart their browser in a little bit. + var tm = + Components.classes["@mozilla.org/updates/timer-manager;1"]. + getService(Components.interfaces.nsIUpdateTimerManager); + + /** + * An object implementing nsITimerCallback that uses the Update Prompt + * component to notify the user about some event relating to app update + * that they should take action on. + * @param update + * The nsIUpdate object in question + * @param methodName + * The name of the method on the Update Prompter that should be + * called + * @constructor + */ + function Callback(update, methodName) { + this._update = update; + this._methodName = methodName; + this._prompter = + Components.classes["@mozilla.org/updates/update-prompt;1"]. + createInstance(Components.interfaces.nsIUpdatePrompt); + } + Callback.prototype = { + /** + * The Update we should nag about downloading + */ + _update: null, + + /** + * The Update prompter we can use to notify the user + */ + _prompter: null, + + /** + * The method on the update prompt that should be called + */ + _methodName: "", + + /** + * Called when the timer fires. Notifies the user about whichever event + * they need to be nagged about (e.g. update available, please restart, + * etc). + */ + notify: function(timerCallback) { + this._prompter[methodName](this._update); + } + } + tm.registerTimer(timerID, (new Callback(gUpdates.update, methodName)), + timerInterval); + }, } var gCheckingPage = { @@ -202,10 +302,10 @@ var gCheckingPage = { var wiz = document.documentElement; wiz.getButton("next").disabled = true; - var aus = - Components.classes["@mozilla.org/updates/update-service;1"]. - getService(Components.interfaces.nsIApplicationUpdateService); - this._checker = aus.checkForUpdates(this.updateListener); + this._checker = + Components.classes["@mozilla.org/updates/update-checker;1"]. + createInstance(Components.interfaces.nsIUpdateChecker); + this._checker.checkForUpdates(this.updateListener); }, /** @@ -213,8 +313,10 @@ var gCheckingPage = { * Manager control, so stop checking for updates. */ onWizardCancel: function() { - if (this._checker) - this._checker.stopChecking(); + if (this._checker) { + const nsIUpdateChecker = Components.interfaces.nsIUpdateChecker; + this._checker.stopChecking(nsIUpdateChecker.CURRENT_CHECK); + } }, updateListener: { @@ -271,8 +373,14 @@ var gNoUpdatesPage = { }; var gUpdatesAvailablePage = { + /** + * An array of installed addons incompatible with this update. + */ _incompatibleItems: null, + /** + * Initialize. + */ onPageShow: function() { var updateName = gUpdates.strings.getFormattedString("updateName", [gUpdates.brandName, gUpdates.update.version]); @@ -307,15 +415,38 @@ var gUpdatesAvailablePage = { downloadNow.focus(); }, + /** + * User said they wanted to install now, so advance to the License or + * Downloading page, whichever is appropriate. + */ onInstallNow: function() { var nextPageID = gUpdates.update.licenseURL ? "license" : "downloading"; document.documentElement.currentPage = document.getElementById(nextPageID); }, + /** + * User said that they would install later. Register a timer to remind them + * after a day or so. + */ onInstallLater: function() { + var interval = getPref("getIntPref", PREF_UPDATE_NAGTIMER_DL, 86400000); + gUpdates.registerNagTimer("download-nag-timer", interval, + "showUpdateAvailable"); + + // The user said "Later", so stop all update checks for this session + // so that we don't bother them again. + const nsIUpdateChecker = Components.interfaces.nsIUpdateChecker; + var aus = + Components.classes["@mozilla.org/updates/update-service;1"]. + getService(Components.interfaces.nsIApplicationUpdateService); + aus.backgroundChecker.stopChecking(nsIUpdateChecker.CURRENT_SESSION); + close(); }, + /** + * Show a list of extensions made incompatible by this update. + */ showIncompatibleItems: function() { openDialog("chrome://mozapps/content/update/incompatible.xul", "", "dialog,centerscreen,modal,resizable,titlebar", this._incompatibleItems); @@ -327,11 +458,15 @@ var gLicensePage = { onPageShow: function() { this._licenseContent = document.getElementById("licenseContent"); - var nextButton = document.documentElement.getButton("next"); + var de = document.documentElement; + var nextButton = de.getButton("next"); nextButton.disabled = true; nextButton.label = gUpdates.strings.getString("IAgreeLabel"); - document.documentElement.getButton("back").disabled = true; - document.documentElement.getButton("next").focus(); + de.getButton("back").disabled = true; + de.getButton("next").focus(); + + var cancelButton = de.getButton("cancel"); + cancelButton.label = gUpdates.strings.getString("IDoNotAgreeLabel"); this._licenseContent.addEventListener("load", this.onLicenseLoad, false); this._licenseContent.url = gUpdates.update.licenseURL; @@ -345,6 +480,14 @@ var gLicensePage = { onWizardCancel: function() { this._licenseContent.stopDownloading(); + + // The user said "Do Not Agree", so stop all update checks for this session + // so that we don't bother them again. + const nsIUpdateChecker = Components.interfaces.nsIUpdateChecker; + var aus = + Components.classes["@mozilla.org/updates/update-service;1"]. + getService(Components.interfaces.nsIApplicationUpdateService); + aus.backgroundChecker.stopChecking(nsIUpdateChecker.CURRENT_SESSION); } }; @@ -598,11 +741,14 @@ var gDownloadingPage = { updates.addDownloadListener(this); } - document.documentElement.getButton("back").disabled = true; - document.documentElement.getButton("next").disabled = true; - var cancelButton = document.documentElement.getButton("cancel"); + var de = document.documentElement; + de.getButton("back").disabled = true; + var cancelButton = de.getButton("cancel"); cancelButton.label = gUpdates.strings.getString("closeButtonLabel"); cancelButton.focus(); + var nextButton = de.getButton("next"); + nextButton.disabled = true; + nextButton.label = gUpdates.buttonLabel_next; }, /** @@ -626,13 +772,14 @@ var gDownloadingPage = { */ _togglePausedState: function(paused) { var u = gUpdates.update; + var p = u.selectedPatch.QueryInterface(Components.interfaces.nsIPropertyBag); if (paused) { this._oldStatus = this._downloadStatus.textContent; this._oldMode = this._downloadProgress.mode; this._oldProgress = parseInt(this._downloadProgress.progress); this._downloadName.value = gUpdates.strings.getFormattedString( "pausedName", [u.name]); - this._setStatus(u.selectedPatch.status); + this._setStatus(p.getProperty("status")); this._downloadProgress.mode = "normal"; this._pauseButton.label = gUpdates.strings.getString("pauseButtonResume"); @@ -640,8 +787,9 @@ var gDownloadingPage = { else { this._downloadName.value = gUpdates.strings.getFormattedString( "downloadingPrefix", [u.name]); - this._setStatus(this._oldStatus || u.selectedPatch.status); - this._downloadProgress.value = this._oldProgress || u.selectedPatch.progress; + this._setStatus(this._oldStatus || p.getProperty("status")); + this._downloadProgress.value = + this._oldProgress || parseInt(p.getProperty("progress")); this._downloadProgress.mode = this._oldMode || "normal"; this._pauseButton.label = gUpdates.strings.getString("pauseButtonPause"); } @@ -657,9 +805,11 @@ var gDownloadingPage = { if (this._paused) updates.downloadUpdate(gUpdates.update, false); else { - gUpdates.update.selectedPatch.status = + var patch = gUpdates.update.selectedPatch; + patch.QueryInterface(Components.interfaces.nsIWritablePropertyBag); + patch.setProperty("status", gUpdates.strings.getFormattedString("pausedStatus", - [this._statusFormatter.progress]); + [this._statusFormatter.progress])); updates.pauseDownload(); } this._paused = !this._paused; @@ -728,15 +878,18 @@ var gDownloadingPage = { request.QueryInterface(nsIIncrementalDownload); // LOG("gDownloadingPage.onProgress: " + request.URI.spec + ", " + progress + "/" + maxProgress); - gUpdates.update.selectedPatch.status = - this._statusFormatter.formatStatus(progress, maxProgress); + var p = gUpdates.update.selectedPatch; + p.QueryInterface(Components.interfaces.nsIWritablePropertyBag); + p.setProperty("progress", Math.round(100 * (progress/maxProgress))); + p.setProperty("status", + this._statusFormatter.formatStatus(progress, maxProgress)); this._downloadProgress.mode = "normal"; - this._downloadProgress.value = gUpdates.update.selectedPatch.progress; + this._downloadProgress.value = parseInt(p.getProperty("progress")); this._pauseButton.disabled = false; var name = gUpdates.strings.getFormattedString("downloadingPrefix", [gUpdates.update.name]); this._downloadName.value = name; - this._setStatus(gUpdates.update.selectedPatch.status); + this._setStatus(p.getProperty("status")); }, /** @@ -909,6 +1062,11 @@ var gFinishedPage = { [gUpdates.brandName]); ps.alert(window, gUpdates.strings.getString("restartLaterTitle"), message); + + var interval = getPref("getIntPref", PREF_UPDATE_NAGTIMER_RESTART, + 18000000); + gUpdates.registerNagTimer("restart-nag-timer", interval, + "showUpdateComplete"); }, }; diff --git a/toolkit/mozapps/update/public/nsIUpdateService.idl b/toolkit/mozapps/update/public/nsIUpdateService.idl index ad9bd2b74b7..69e4e6ef2e1 100644 --- a/toolkit/mozapps/update/public/nsIUpdateService.idl +++ b/toolkit/mozapps/update/public/nsIUpdateService.idl @@ -69,16 +69,6 @@ interface nsIUpdatePatch : nsISupports */ attribute unsigned long size; - /** - * - */ - attribute double progress; - - /** - * - */ - attribute AString status; - /** * The state of this update */ @@ -231,18 +221,56 @@ interface nsIUpdatePrompt : nsISupports interface nsIUpdateChecker : nsISupports { /** - * Ends any pending update check. + * Checks for available updates, notifying a listener of the results. + * @param listener + * An object implementing nsIUpdateCheckListener which is notified + * of the results of an update check. */ - void stopChecking(); + void checkForUpdates(in nsIUpdateCheckListener listener); + + /** + * Constants for the |stopChecking| function that tell the Checker how long + * to stop checking: + * + * CURRENT_CHECK: Stops the current (active) check only + * CURRENT_SESSION: Stops all checking for the current session + * ANY_CHECKS: Stops all checking, any session from now on + * (disables update checking preferences) + */ + const unsigned short CURRENT_CHECK = 1; + const unsigned short CURRENT_SESSION = 2; + const unsigned short ANY_CHECKS = 3; + + /** + * Ends any pending update check. + * @param duration + * A value representing the set of checks to stop doing. + */ + void stopChecking(in unsigned short duration); + + /** + * Whether or not this Checker can perform update checking. + */ + readonly attribute boolean enabled; }; [scriptable, uuid(9849c4bf-5197-4d22-baa8-e3b44a1703d2)] interface nsIApplicationUpdateService : nsISupports { /** - * + * The Update Checker used for background update checking. */ - nsIUpdateChecker checkForUpdates(in nsIUpdateCheckListener listener); + readonly attribute nsIUpdateChecker backgroundChecker; + + /** + * Selects the best update to install from a list of available updates. + * @param updates + * An array of updates that are available + * @param updateCount + * The length of the |updates| array + */ + nsIUpdate selectUpdate([array, size_is(updateCount)] in nsIUpdate updates, + in unsigned long updateCount); /** * Adds a listener that receives progress and state information about the @@ -263,16 +291,6 @@ interface nsIApplicationUpdateService : nsISupports */ void removeDownloadListener(in nsIRequestObserver listener); - /** - * Selects the best update to install from a list of available updates. - * @param updates - * An array of updates that are available - * @param updateCount - * The length of the |updates| array - */ - nsIUpdate selectUpdate([array, size_is(updateCount)] in nsIUpdate updates, - in unsigned long updateCount); - /** * */ @@ -322,22 +340,45 @@ interface nsIUpdateManager : nsISupports interface nsIUpdateTimerManager : nsISupports { /** - * + * Register an interval with the timer manager. The timer manager + * periodically checks to see if the interval has expired and if it has + * calls the specified callback. This is persistent across application + * restarts and can handle intervals of long durations. + * @param id + * An id that identifies the interval, used for persistence + * @param callback + * A nsITimerCallback object that is notified when the interval + * expires + * @param interval + * The length of time, in milliseconds, of the interval */ void registerTimer(in AString id, in nsITimerCallback callback, - in unsigned long interval, - in unsigned long type); + in unsigned long interval); }; [scriptable, uuid(22d35700-5765-42e1-914b-a0da7c911a8c)] interface nsIVersionChecker : nsISupports { - // -ve if B is newer - // equal if A == B - // +ve if A is newer - long compare(in AString aVersionA, in AString aVersionB); + /** + * Compare two FVF versions + * @param versionA + * The first version + * @param versionB + * The second version + * @returns < 0 if A < B + * = 0 if A == B + * > 0 if B > A + * XXXben - change the return value here to return a constant + */ + long compare(in AString versionA, in AString versionB); - boolean isValidVersion(in AString aVersion); + /** + * Determines if a string is a valid FVF version + * @param string + * The string to validate + * @returns true if the string is a valid FVF version + */ + boolean isValidVersion(in AString version); }; diff --git a/toolkit/mozapps/update/src/nsUpdateService.js.in b/toolkit/mozapps/update/src/nsUpdateService.js.in index 2d461854c35..aaa2f4eeca6 100644 --- a/toolkit/mozapps/update/src/nsUpdateService.js.in +++ b/toolkit/mozapps/update/src/nsUpdateService.js.in @@ -429,7 +429,6 @@ UpdateService.prototype = { var um = Components.classes["@mozilla.org/updates/update-manager;1"] .getService(Components.interfaces.nsIUpdateManager); var activeUpdate = um.activeUpdate; - LOG("ACTIVEUPDATE = " + activeUpdate); if (activeUpdate) this.downloadUpdate(activeUpdate, true); break; @@ -441,8 +440,7 @@ UpdateService.prototype = { Components.classes["@mozilla.org/updates/timer-manager;1"] .getService(Components.interfaces.nsIUpdateTimerManager); var interval = getPref("getIntPref", PREF_APP_UPDATE_INTERVAL, 86400000); - tm.registerTimer("background-update-timer", this, interval, - Components.interfaces.nsITimer.TYPE_REPEATING_SLACK); + tm.registerTimer("background-update-timer", this, interval); break; case "xpcom-shutdown": gOS.removeObserver(this, "xpcom-shutdown"); @@ -472,7 +470,7 @@ UpdateService.prototype = { notify: function(timer) { // If a download is in progress, then do nothing. - if (this.isDownloading) + if (this.isDownloading || this._downloader && this._downloader.patchIsStaged) return; var self = this; @@ -484,7 +482,7 @@ UpdateService.prototype = { onError: function() { }, } - this.checkForUpdates(listener); + this.backgroundChecker.checkForUpdates(listener); }, /** @@ -514,12 +512,18 @@ UpdateService.prototype = { // Minor 1 Yes Notify and Confirm // Minor 2 Yes or No Notify and Confirm // + // In addition, if there is a license associated with an update, regardless + // of type it must be agreed to. + // // If app.update.enabled is set to false, an update check is not performed // at all, and so none of the decision making above is entered into. // + if (update.licenseURL) + return true; + var updateEnabled = getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true); if (!updateEnabled) - return; + return false; var mode = getPref("getIntPref", PREF_APP_UPDATE_AUTOINSTALL_MODE, 0); var compatible = isCompatible(update); @@ -580,12 +584,17 @@ UpdateService.prototype = { }, /** - * + * The Checker used for background update checks. */ - checkForUpdates: function(listener) { - var checker = new Checker(); - checker.findUpdates(listener); - return checker; + _backgroundChecker: null, + + /** + * See nsIUpdateService.idl + */ + get backgroundChecker() { + if (!this._backgroundChecker) + this._backgroundChecker = new Checker(); + return this._backgroundChecker; }, /** @@ -871,15 +880,17 @@ function getPref(func, preference, defaultValue) { * @constructor */ function UpdatePatch(patch) { - this.type = patch.getAttribute("type"); - this.URL = patch.getAttribute("URL"); - this.hashFunction = patch.getAttribute("hashFunction"); - this.hashValue = patch.getAttribute("hashValue"); - this.size = parseInt(patch.getAttribute("size")); - this.percentage = parseFloat(patch.getAttribute("percentage")); - this.state = patch.getAttribute("state"); - this.status = patch.getAttribute("status"); - this.selected = patch.getAttribute("selected") == "true"; + for (var i = 0; i < patch.attributes.length; ++i) { + var attr = patch.attributes[i]; + switch (attr.name) { + case "selected": + this.selected = attr.value == "true"; + break; + default: + this[attr.name] = attr.value; + break; + }; + } } UpdatePatch.prototype = { /** @@ -892,18 +903,65 @@ UpdatePatch.prototype = { patch.setAttribute("hashFunction", this.hashFunction); patch.setAttribute("hashValue", this.hashValue); patch.setAttribute("size", this.size); - patch.setAttribute("percentage", this.percentage); - patch.setAttribute("state", this.state); - patch.setAttribute("status", this.status); patch.setAttribute("selected", this.selected); + + for (var p in this._properties) { + if (this._properties[p].present) + patch.setAttribute(p, this._properties[p].data); + } + return patch; }, + /** + * A hash of custom properties + */ + _properties: { }, + + /** + * See nsIWritablePropertyBag.idl + */ + setProperty: function(name, value) { + this._properties[name] = { data: value, present: true }; + }, + + /** + * See nsIWritablePropertyBag.idl + */ + deleteProperty: function(name) { + if ("name" in this._properties) + this._properties[name].present = false; + else + throw Components.results.NS_ERROR_FAILURE; + }, + + /** + * See nsIPropertyBag.idl + */ + get enumerator() { + var properties = []; + for (var p in this._properties) + properties.push(this._properties[p].data); + return new ArrayEnumerator(properties); + }, + + /** + * See nsIPropertyBag.idl + */ + getProperty: function(name) { + if ("name" in this._properties && + this._properties[name].present) + return this._properties[name].data; + throw Components.results.NS_ERROR_FAILURE; + }, + /** * See nsISupports.idl */ QueryInterface: function(iid) { if (!iid.equals(Components.interfaces.nsIUpdatePatch) && + !iid.equals(Components.interfaces.nsIPropertyBag) && + !iid.equals(Components.interfaces.nsIWritablePropertyBag) && !iid.equals(Components.interfaces.nsISupports)) throw Components.results.NS_ERROR_NO_INTERFACE; return this; @@ -1067,13 +1125,13 @@ Checker.prototype = { }, /** - * + * See nsIUpdateService.idl */ - findUpdates: function(callback) { - if (!callback) + checkForUpdates: function(listener) { + if (!listener) throw Components.results.NS_ERROR_NULL_POINTER; - if (!this._updateURL) + if (!this._updateURL || !this.enabled) return; this._request = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] @@ -1087,10 +1145,10 @@ Checker.prototype = { this._request.onload = function(event) { self.onLoad(event); }; this._request.onprogress = function(event) { self.onProgress(event); }; - LOG("Checker.findUpdates: sending request to " + this._updateURL); + LOG("Checker.checkForUpdates: sending request to " + this._updateURL); this._request.send(null); - this._callback = callback; + this._callback = listener; }, /** @@ -1157,11 +1215,36 @@ Checker.prototype = { this._callback.onError(event.target); }, + /** + * Whether or not we are allowed to do update checking. + */ + _enabled: true, + /** * See nsIUpdateService.idl */ - stopChecking: function() { + get enabled() { + return getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true) || + this._enabled; + }, + + /** + * See nsIUpdateService.idl + */ + stopChecking: function(duration) { + // Always stop the current check this._request.abort(); + + const nsIUpdateChecker = Components.interfaces.nsIUpdateChecker; + switch (duration) { + case nsIUpdateChecker.CURRENT_SESSION: + this._enabled = false; + break; + case nsIUpdateChecker.ANY_CHECKS: + this._enabled = false; + gPref.setBoolPref(PREF_APP_UPDATE_ENABLED, this._enabled); + break; + } }, /** @@ -1209,7 +1292,7 @@ Downloader.prototype = { isCompleteUpdate: null, /** - * + * Cancels the active download. */ cancel: function() { if (this._request && @@ -1220,9 +1303,9 @@ Downloader.prototype = { }, /** - * + * Retrieves the active Updates directory, as a nsIFile object. */ - _getUpdatesDir: function() { + get _updatesDir() { // Right now, we only support downloading one patch at a time, so we always // use the same target directory. var fileLocator = @@ -1261,6 +1344,13 @@ Downloader.prototype = { LOG(statusFile.path); return readStringFromFile(statusFile); }, + + /** + * Whether or not a patch has been downloaded and staged for installation. + */ + get patchIsStaged() { + return this._readStatusFile(this._updatesDir) == STATE_PENDING; + }, /** * Verify the downloaded file. We assume that the download is complete at @@ -1365,7 +1455,7 @@ Downloader.prototype = { updateDir.remove(true); } catch (e) {} // Restore the updateDir since we may have deleted it. - updateDir = this._getUpdatesDir(); + updateDir = this._updatesDir; selectedPatch = null; } @@ -1408,13 +1498,14 @@ Downloader.prototype = { if (!update) throw Components.results.NS_ERROR_NULL_POINTER; - var updateDir = this._getUpdatesDir(); + var updateDir = this._updatesDir; this._update = update; // This function may return null, which indicates that there are no patches // to download. this._patch = this._selectPatch(update, updateDir); + LOG("PATCH = " + this._patch); if (!this._patch) { LOG("no patch to download"); return this._readStatusFile(updateDir); @@ -1441,6 +1532,7 @@ Downloader.prototype = { this._request.start(this, null); this._writeStatusFile(updateDir, STATE_DOWNLOADING); + this._patch.QueryInterface(Components.interfaces.nsIWritablePropertyBag); this._patch.state = STATE_DOWNLOADING; var um = Components.classes["@mozilla.org/updates/update-manager;1"] .getService(Components.interfaces.nsIUpdateManager); @@ -1495,8 +1587,6 @@ Downloader.prototype = { request.QueryInterface(nsIIncrementalDownload); LOG("Downloader.onProgress: " + request.URI.spec + ", " + progress + "/" + maxProgress); - this._patch.progress = Math.round(100 * (progress/maxProgress)); - var listenerCount = this._listeners.length; for (var i = 0; i < listenerCount; ++i) { var listener = this._listeners[i]; @@ -1553,7 +1643,8 @@ Downloader.prototype = { // download initiates. } } - this._writeStatusFile(this._getUpdatesDir(), state); + this._writeStatusFile(this._updatesDir, state); + this._patch.QueryInterface(Components.interfaces.nsIWritablePropertyBag); this._patch.state = state; var um = Components.classes["@mozilla.org/updates/update-manager;1"] .getService(Components.interfaces.nsIUpdateManager); @@ -1598,82 +1689,61 @@ Downloader.prototype = { * @constructor */ function TimerManager() { + const nsITimer = Components.interfaces.nsITimer; + this._timer = Components.classes["@mozilla.org/timer;1"] + .createInstance(nsITimer); + var timerInterval = getPref("getIntPref", PREF_APP_UPDATE_TIMER, 5000); + this._timer.initWithCallback(this, timerInterval, + nsITimer.TYPE_REPEATING_SLACK); } TimerManager.prototype = { + /** + * The Checker Timer + */ + _timer: null, + /** * The set of registered timers. */ _timers: { }, /** - * + * Called when the checking timer fires. + * @param timer + * The checking timer that fired. */ - registerTimer: function(id, callback, interval, type) { - const nsITimer = Components.interfaces.nsITimer; - var timer = Components.classes["@mozilla.org/timer;1"] - .createInstance(nsITimer); - var timerInterval = getPref("getIntPref", PREF_APP_UPDATE_TIMER, 5000); + notify: function(timer) { + for (var timerID in this._timers) { + var timerData = this._timers[timerID]; + var lastUpdateTime = timerData.lastUpdateTime; + var now = Math.round(Date.now() / 1000); - var self = this; - - /** - * A callback object implementing nsITimerCallback that determines if the - * user-registered callback should be invoked yet. - * @param id - * The id of the timer that fired - * @param callback - * The nsITimerCallback object supplied by the user that should be - * notified if the user's interval has expired. - * @param interval - * The user's interval - * @constructor - */ - function TimerCallback(id, callback, interval) { - this.id = id; - this.callback = callback; - this.interval = interval; - } - TimerCallback.prototype = { - /** - * - */ - notify: function(timer) { - // LOG("self._timers = " + self._timers.toSource()); - var lastUpdateTime = self._timers[this.id].lastUpdateTime; - var now = Math.round(Date.now() / 1000); - // LOG("notify = " + (now - lastUpdateTime) + " > " + this.interval); - - // Fudge the lastUpdateTime by some random increment of the update - // check interval (e.g. some random slice of 10 minutes) so that when - // the time comes to check, we offset each client request by a random - // amount so they don't all hit at once. - lastUpdateTime += Math.round(Math.random() * this.interval); - - if ((now - lastUpdateTime) > this.interval && - this.callback instanceof Components.interfaces.nsITimerCallback) { - this.callback.notify(timer); - self._timers[this.id].lastUpdateTime = now; - var preference = PREF_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/, this.id); - gPref.setIntPref(preference, now); - } - }, + // Fudge the lastUpdateTime by some random increment of the update + // check interval (e.g. some random slice of 10 minutes) so that when + // the time comes to check, we offset each client request by a random + // amount so they don't all hit at once. + lastUpdateTime += Math.round(Math.random() * timerData.interval); - /** - * See nsISupports.idl - */ - QueryInterface: function(iid) { - if (!iid.equals(Components.interfaces.nsITimerCallback) && - !iid.equals(Components.interfaces.nsISupports)) - throw Components.results.NS_ERROR_NO_INTERFACE; - return this; + if ((now - lastUpdateTime) > timerData.interval && + timerData.callback instanceof Components.interfaces.nsITimerCallback) { + timerData.callback.notify(timer); + timerData.lastUpdateTime = now; + var preference = PREF_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/, timerID); + gPref.setIntPref(preference, now); } - }; - var tc = new TimerCallback(id, callback, interval); - timer.initWithCallback(tc, timerInterval, type); + } + }, + + /** + * See nsIUpdateService.idl + */ + registerTimer: function(id, callback, interval) { var preference = PREF_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/, id); var lastUpdateTime = getPref("getIntPref", preference, Math.round(Date.now() / 1000)); - this._timers[id] = { timer: timer, lastUpdateTime: lastUpdateTime }; + this._timers[id] = { callback : callback, + interval : interval, + lastUpdateTime : lastUpdateTime }; }, /** @@ -1681,6 +1751,7 @@ TimerManager.prototype = { */ QueryInterface: function(iid) { if (!iid.equals(Components.interfaces.nsIUpdateTimerManager) && + !iid.equals(Components.interfaces.nsITimerCallback) && !iid.equals(Components.interfaces.nsISupports)) throw Components.results.NS_ERROR_NO_INTERFACE; return this; @@ -1820,6 +1891,11 @@ var gModule = { className : "Update Service", factory : #1#(UpdateService) }, + checker: { CID : Components.ID("{898CDC9B-E43F-422F-9CC4-2F6291B415A3}"), + contractID : "@mozilla.org/updates/update-checker;1", + className : "Update Checker", + factory : #1#(Checker) + }, manager: { CID : Components.ID("{093C2356-4843-4C65-8709-D7DBCBBE7DFB}"), contractID : "@mozilla.org/updates/update-manager;1", className : "Update Manager", @@ -1860,12 +1936,14 @@ function NSGetModule(compMgr, fileSpec) { * the specified update, false otherwise. */ function isCompatible(update) { -#ifdef MOZ_XULAPP +#ifdef MOZ_XUL_APP var em = Components.classes["@mozilla.org/extensions/manager;1"] .getService(Components.interfaces.nsIExtensionManager); var items = em.getIncompatibleItemList("", update.extensionVersion, nsIUpdateItem.TYPE_ADDON, { }); return items.length == 0; +#else + return true; #endif }