Bug 794937 - Make updates work from the Metro front end, add handling for 1 instance handling updates. r=rstrong

This commit is contained in:
Brian R. Bondy 2013-05-22 10:16:53 -04:00
parent f906b641d5
commit 2b27a1ede9
12 changed files with 249 additions and 283 deletions

View File

@ -120,6 +120,7 @@ pref("app.update.cert.maxErrors", 5);
// |app.update.url.override| user preference has been set for testing updates or
// when the |app.update.cert.checkAttributes| preference is set to false. Also,
// the |app.update.url.override| preference should ONLY be used for testing.
// IMPORTANT! metro.js should also be updated for updates to certs.X.issuerName
pref("app.update.certs.1.issuerName", "OU=Equifax Secure Certificate Authority,O=Equifax,C=US");
pref("app.update.certs.1.commonName", "aus3.mozilla.org");

View File

@ -121,6 +121,11 @@ function appUpdater()
return;
}
if (this.aus.isOtherInstanceHandlingUpdates) {
this.selectPanel("otherInstanceHandlingUpdates");
return;
}
if (this.isDownloading) {
this.startDownload();
return;

View File

@ -76,6 +76,9 @@
<hbox id="noUpdatesFound" align="center">
<label>&update.noUpdatesFound;</label>
</hbox>
<hbox id="otherInstanceHandlingUpdates" align="center">
<label>&update.otherInstanceHandlingUpdates;</label>
</hbox>
<hbox id="manualUpdate" align="center">
<label>&update.manual.start;</label><label id="manualLink" class="text-link"/><label>&update.manual.end;</label>
</hbox>

View File

@ -47,6 +47,8 @@
<!ENTITY update.noUpdatesFound "&brandShortName; is up to date">
<!-- LOCALIZATION NOTE (update.adminDisabled): try to make the localized text short (see bug 596813 for screenshots). -->
<!ENTITY update.adminDisabled "Updates disabled by your system administrator">
<!-- LOCALIZATION NOTE (update.otherInstanceHandlingUpdates): try to make the localized text short -->
<!ENTITY update.otherInstanceHandlingUpdates "&brandShortName; is being updated by another instance">
<!-- LOCALIZATION NOTE (update.failed.start,update.failed.linkText,update.failed.end):
update.failed.start, update.failed.linkText, and update.failed.end all go into

View File

@ -154,12 +154,6 @@ var BrowserUI = {
Util.dumpLn("Exception in delay load module:", ex.message);
}
#ifdef MOZ_UPDATER
// Check for updates in progress
let updatePrompt = Cc["@mozilla.org/updates/update-prompt;1"].createInstance(Ci.nsIUpdatePrompt);
updatePrompt.checkForUpdates();
#endif
// check for left over crash reports and submit them if found.
if (BrowserUI.startupCrashCheck()) {
Browser.selectedTab = BrowserUI.newOrSelectTab("about:crash");

View File

@ -12,8 +12,6 @@ Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
// -----------------------------------------------------------------------
const NS_APP_CACHE_PARENT_DIR = "cachePDir";
const XRE_UPDATE_ROOT_DIR = "UpdRootD";
const ENVVAR_UPDATE_DIR = "UPDATES_DIRECTORY";
function DirectoryProvider() {}
@ -33,20 +31,8 @@ DirectoryProvider.prototype = {
default:
return profile;
}
} else if (prop == XRE_UPDATE_ROOT_DIR) {
let env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
if (env.exists(ENVVAR_UPDATE_DIR)) {
let path = env.get(ENVVAR_UPDATE_DIR);
if (path) {
let localFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
localFile.initWithPath(path);
return localFile;
}
}
let dm = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
return dm.defaultDownloadsDirectory;
}
// We are retuning null to show failure instead for throwing an error. The
// interface is called quite a bit and throwing an error is noisy. Returning
// null works with the way the interface is called [see bug 529077]

View File

@ -36,8 +36,4 @@ ifdef MOZ_SAFE_BROWSING
EXTRA_COMPONENTS += SafeBrowsing.js
endif
ifdef MOZ_UPDATER
EXTRA_COMPONENTS += UpdatePrompt.js
endif
include $(topsrcdir)/config/rules.mk

View File

@ -1,243 +0,0 @@
/* 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/. */
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const UPDATE_NOTIFICATION_NAME = "update-app";
const UPDATE_NOTIFICATION_ICON = "drawable://alert_download_progress";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyGetter(this, "gUpdateBundle", function aus_gUpdateBundle() {
return Services.strings.createBundle("chrome://mozapps/locale/update/updates.properties");
});
XPCOMUtils.defineLazyGetter(this, "gBrandBundle", function aus_gBrandBundle() {
return Services.strings.createBundle("chrome://branding/locale/brand.properties");
});
XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function aus_gBrowserBundle() {
return Services.strings.createBundle("chrome://browser/locale/browser.properties");
});
XPCOMUtils.defineLazyGetter(this, "LocaleRepository", function() {
Cu.import("resource://gre/modules/LocaleRepository.jsm");
return LocaleRepository;
});
function getPref(func, preference, defaultValue) {
try {
return Services.prefs[func](preference);
} catch (e) {}
return defaultValue;
}
// -----------------------------------------------------------------------
// Update Prompt
// -----------------------------------------------------------------------
function UpdatePrompt() { }
UpdatePrompt.prototype = {
classID: Components.ID("{88b3eb21-d072-4e3b-886d-f89d8c49fe59}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdatePrompt, Ci.nsIRequestObserver, Ci.nsIProgressEventSink]),
get _enabled() {
return !getPref("getBoolPref", "app.update.silent", false);
},
_showNotification: function UP__showNotif(aUpdate, aTitle, aText, aImageUrl, aMode) {
let observer = {
updatePrompt: this,
observe: function (aSubject, aTopic, aData) {
switch (aTopic) {
case "alertclickcallback":
this.updatePrompt._handleUpdate(aUpdate, aMode);
break;
}
}
};
let notifier = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService);
notifier.showAlertNotification(aImageUrl, aTitle, aText, true, "", observer, UPDATE_NOTIFICATION_NAME);
},
_handleUpdate: function UP__handleUpdate(aUpdate, aMode) {
if (aMode == "available") {
let window = Services.wm.getMostRecentWindow("navigator:browser");
let title = gUpdateBundle.GetStringFromName("updatesfound_" + aUpdate.type + ".title");
let brandName = gBrandBundle.GetStringFromName("brandShortName");
// Unconditionally use the "major" type here as for now it is always a new version
// without additional description required for a minor update message
let message = gUpdateBundle.formatStringFromName("intro_major", [brandName, aUpdate.displayVersion], 2);
let button0 = gUpdateBundle.GetStringFromName("okButton");
let button1 = gUpdateBundle.GetStringFromName("askLaterButton");
let prompt = Services.prompt;
let flags = prompt.BUTTON_POS_0 * prompt.BUTTON_TITLE_IS_STRING + prompt.BUTTON_POS_1 * prompt.BUTTON_TITLE_IS_STRING;
let download = (prompt.confirmEx(window, title, message, flags, button0, button1, null, null, {value: false}) == 0);
if (download) {
// Start downloading the update in the background
let aus = Cc["@mozilla.org/updates/update-service;1"].getService(Ci.nsIApplicationUpdateService);
if (aus.downloadUpdate(aUpdate, true) != "failed") {
let title = gBrowserBundle.formatStringFromName("alertDownloadsStart", [aUpdate.name], 1);
this._showNotification(aUpdate, title, "", UPDATE_NOTIFICATION_ICON, "download");
// Add this UI as a listener for active downloads
aus.addDownloadListener(this);
}
}
} else if(aMode == "downloaded") {
// Notify all windows that an application quit has been requested
let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
// If nothing aborted, restart the app
if (cancelQuit.data == false) {
let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup);
appStartup.quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit);
}
}
},
_updateDownloadProgress: function UP__updateDownloadProgress(aProgress, aTotal) {
let alertsService = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService);
let progressListener = alertsService.QueryInterface(Ci.nsIAlertsProgressListener);
if (progressListener)
progressListener.onProgress(UPDATE_NOTIFICATION_NAME, aProgress, aTotal);
},
// -------------------------
// nsIUpdatePrompt interface
// -------------------------
// Right now this is used only to check for updates in progress
checkForUpdates: function UP_checkForUpdates() {
if (!this._enabled)
return;
let aus = Cc["@mozilla.org/updates/update-service;1"].getService(Ci.nsIApplicationUpdateService);
if (!aus.isDownloading)
return;
let updateManager = Cc["@mozilla.org/updates/update-manager;1"].getService(Ci.nsIUpdateManager);
let updateName = updateManager.activeUpdate ? updateManager.activeUpdate.name : gBrandBundle.GetStringFromName("brandShortName");
let title = gBrowserBundle.formatStringFromName("alertDownloadsStart", [updateName], 1);
this._showNotification(updateManager.activeUpdate, title, "", UPDATE_NOTIFICATION_ICON, "downloading");
aus.removeDownloadListener(this); // just in case it's already added
aus.addDownloadListener(this);
},
showUpdateAvailable: function UP_showUpdateAvailable(aUpdate) {
if (!this._enabled)
return;
const PREF_APP_UPDATE_SKIPNOTIFICATION = "app.update.skipNotification";
if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_SKIPNOTIFICATION) &&
Services.prefs.getBoolPref(PREF_APP_UPDATE_SKIPNOTIFICATION)) {
Services.prefs.setBoolPref(PREF_APP_UPDATE_SKIPNOTIFICATION, false);
// Notification was already displayed and clicked, so jump to the next step:
// ask the user about downloading update
this._handleUpdate(aUpdate, "available");
return;
}
let stringsPrefix = "updateAvailable_" + aUpdate.type + ".";
let title = gUpdateBundle.formatStringFromName(stringsPrefix + "title", [aUpdate.name], 1);
let text = gUpdateBundle.GetStringFromName(stringsPrefix + "text");
let imageUrl = "";
this._showNotification(aUpdate, title, text, imageUrl, "available");
},
showUpdateDownloaded: function UP_showUpdateDownloaded(aUpdate, aBackground) {
if (!this._enabled)
return;
},
_showDownloadedNotification: function UP_showDlNotification(aUpdate) {
let stringsPrefix = "updateDownloaded_" + aUpdate.type + ".";
let title = gUpdateBundle.formatStringFromName(stringsPrefix + "title", [aUpdate.name], 1);
let text = gUpdateBundle.GetStringFromName(stringsPrefix + "text");
let imageUrl = "";
this._showNotification(aUpdate, title, text, imageUrl, "downloaded");
},
_uninstalling: [],
_installing: [],
_currentUpdate: null,
showUpdateInstalled: function UP_showUpdateInstalled() {
if (!this._enabled || !getPref("getBoolPref", "app.update.showInstalledUI", false))
return;
let title = gBrandBundle.GetStringFromName("brandShortName");
let text = gUpdateBundle.GetStringFromName("installSuccess");
let imageUrl = "";
this._showNotification(aUpdate, title, text, imageUrl, "installed");
},
showUpdateError: function UP_showUpdateError(aUpdate) {
if (!this._enabled)
return;
if (aUpdate.state == "failed") {
var title = gBrandBundle.GetStringFromName("brandShortName");
let text = gUpdateBundle.GetStringFromName("updaterIOErrorTitle");
let imageUrl = "";
this._showNotification(aUpdate, title, text, imageUrl, "error");
}
},
showUpdateHistory: function UP_showUpdateHistory(aParent) {
// NOT IMPL
},
// ----------------------------
// nsIRequestObserver interface
// ----------------------------
// When the data transfer begins
onStartRequest: function(request, context) {
// NOT IMPL
},
// When the data transfer ends
onStopRequest: function(request, context, status) {
let alertsService = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService);
let progressListener = alertsService.QueryInterface(Ci.nsIAlertsProgressListener);
if (progressListener)
progressListener.onCancel(UPDATE_NOTIFICATION_NAME);
let aus = Cc["@mozilla.org/updates/update-service;1"].getService(Ci.nsIApplicationUpdateService);
aus.removeDownloadListener(this);
},
// ------------------------------
// nsIProgressEventSink interface
// ------------------------------
// When new data has been downloaded
onProgress: function(request, context, progress, maxProgress) {
this._updateDownloadProgress(progress, maxProgress);
},
// When we have new status text
onStatus: function(request, context, status, statusText) {
// NOT IMPL
},
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([UpdatePrompt]);

View File

@ -84,8 +84,3 @@ contract @mozilla.org/safebrowsing/application;1 {aadaed90-6c03-42d0-924a-fc6119
category app-startup SafeBrowsing service,@mozilla.org/safebrowsing/application;1
#endif
#ifdef MOZ_UPDATER
# UpdatePrompt.js
component {88b3eb21-d072-4e3b-886d-f89d8c49fe59} UpdatePrompt.js
contract @mozilla.org/updates/update-prompt;1 {88b3eb21-d072-4e3b-886d-f89d8c49fe59}
#endif

View File

@ -395,21 +395,111 @@ pref("security.warn_viewing_mixed", false); // Warning is disabled. See Bug 616
// Override some named colors to avoid inverse OS themes
/* app update prefs */
pref("app.update.timer", 60000); // milliseconds (1 min)
#ifdef MOZ_UPDATER
// temp
pref("app.update.enabled", false);
pref("app.update.timerFirstInterval", 20000); // milliseconds
pref("app.update.auto", false);
pref("app.update.channel", "@MOZ_UPDATE_CHANNEL@");
pref("app.update.mode", 1);
pref("app.update.silent", false);
pref("app.update.url", "https://aus2.mozilla.org/update/4/%PRODUCT%/%VERSION%/%BUILD_ID%/%BUILD_TARGET%-xul/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/%PLATFORM_VERSION%/update.xml");
// Whether or not app updates are enabled
pref("app.update.enabled", true);
// This preference turns on app.update.mode and allows automatic download and
// install to take place. We use a separate boolean toggle for this to make
// the UI easier to construct.
pref("app.update.auto", true);
// Defines how the Application Update Service notifies the user about updates:
//
// AUM Set to: Minor Releases: Major Releases:
// 0 download no prompt download no prompt
// 1 download no prompt download no prompt if no incompatibilities
// 2 download no prompt prompt
//
// See chart in nsUpdateService.js source for more details
pref("app.update.mode", 0);
// If set to true, the Update Service will present no UI for any event.
pref("app.update.silent", true);
// If set to true, the Update Service will apply updates in the background
// when it finishes downloading them.
pref("app.update.staging.enabled", true);
// Update service URL:
pref("app.update.url", "https://aus3.mozilla.org/update/3/%PRODUCT%/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/update.xml");
// Show the Update Checking/Ready UI when the user was idle for x seconds
pref("app.update.idletime", 60);
// Whether or not we show a dialog box informing the user that the update was
// successfully applied. This is off in Firefox by default since we show a
// upgrade start page instead! Other apps may wish to show this UI, and supply
// a whatsNewURL field in their brand.properties that contains a link to a page
// which tells users what's new in this new update.
pref("app.update.showInstalledUI", false);
// 0 = suppress prompting for incompatibilities if there are updates available
// to newer versions of installed addons that resolve them.
// 1 = suppress prompting for incompatibilities only if there are VersionInfo
// updates available to installed addons that resolve them, not newer
// versions.
pref("app.update.incompatible.mode", 0);
// Whether or not to attempt using the service for updates.
#ifdef MOZ_MAINTENANCE_SERVICE
pref("app.update.service.enabled", true);
#endif
// The minimum delay in seconds for the timer to fire.
// default=2 minutes
pref("app.update.timerMinimumDelay", 120);
// Enables some extra Application Update Logging (can reduce performance)
pref("app.update.log", false);
// The number of general background check failures to allow before notifying the
// user of the failure. User initiated update checks always notify the user of
// the failure.
pref("app.update.backgroundMaxErrors", 10);
// When |app.update.cert.requireBuiltIn| is true or not specified the
// final certificate and all certificates the connection is redirected to before
// the final certificate for the url specified in the |app.update.url|
// preference must be built-in.
pref("app.update.cert.requireBuiltIn", true);
// When |app.update.cert.checkAttributes| is true or not specified the
// certificate attributes specified in the |app.update.certs.| preference branch
// are checked against the certificate for the url specified by the
// |app.update.url| preference.
pref("app.update.cert.checkAttributes", true);
// The number of certificate attribute check failures to allow for background
// update checks before notifying the user of the failure. User initiated update
// checks always notify the user of the certificate attribute check failure.
pref("app.update.cert.maxErrors", 5);
// The |app.update.certs.| preference branch contains branches that are
// sequentially numbered starting at 1 that contain attribute name / value
// pairs for the certificate used by the server that hosts the update xml file
// as specified in the |app.update.url| preference. When these preferences are
// present the following conditions apply for a successful update check:
// 1. the uri scheme must be https
// 2. the preference name must exist as an attribute name on the certificate and
// the value for the name must be the same as the value for the attribute name
// on the certificate.
// If these conditions aren't met it will be treated the same as when there is
// no update available. This validation will not be performed when the
// |app.update.url.override| user preference has been set for testing updates or
// when the |app.update.cert.checkAttributes| preference is set to false. Also,
// the |app.update.url.override| preference should ONLY be used for testing.
// IMPORTANT! firefox.js should also be updated for updates to certs.X.issuerName
pref("app.update.certs.1.issuerName", "OU=Equifax Secure Certificate Authority,O=Equifax,C=US");
pref("app.update.certs.1.commonName", "aus3.mozilla.org");
pref("app.update.certs.2.issuerName", "CN=Thawte SSL CA,O=\"Thawte, Inc.\",C=US");
pref("app.update.certs.2.commonName", "aus3.mozilla.org");
// User-settable override to app.update.url for testing purposes.
//pref("app.update.url.override", "");
// replace newlines with spaces on paste into single-line text boxes
pref("editor.singleLine.pasteNewlines", 2);

View File

@ -19,7 +19,7 @@ interface nsIFile;
* be downloaded and applied to a version of this application so that it
* can be updated.
*/
[scriptable, uuid(1134957d-9449-481b-a515-4d88b9998278)]
[scriptable, uuid(dc8fb8a9-3a53-4031-9469-2a5197ea30e7)]
interface nsIUpdatePatch : nsISupports
{
/**
@ -429,6 +429,15 @@ interface nsIApplicationUpdateService : nsISupports
*/
readonly attribute boolean canApplyUpdates;
/**
* Whether or not a different instance is handling updates of this
* installation. This currently only ever returns true on Windows
* when 2 instances of an application are open or when both the Metro
* and Desktop browsers are open. Only one of the instances will actually
* handle updates for the installation.
*/
readonly attribute boolean isOtherInstanceHandlingUpdates;
/**
* Whether the Update Service is able to stage updates.
*/

View File

@ -385,6 +385,108 @@ function testWriteAccess(updateTestFile, createDirectory) {
updateTestFile.remove(false);
}
#ifdef XP_WIN
/**
* Closes a Win32 handle
*
* @param handle The handle to close
*/
function closeHandle(handle) {
var lib = ctypes.open("kernel32.dll");
var CloseHandle = lib.declare("CloseHandle",
ctypes.winapi_abi,
ctypes.int32_t, /* success */
ctypes.void_t.ptr); /* handle */
CloseHandle(handle);
lib.close();
}
/**
* Creates a mutex.
*
* @param aAllowExisting false if the function should fail if the mutex exists
* @return The Win32 handle to the mutex
*/
function createMutex(name, aAllowExisting) {
if (aAllowExisting === undefined) {
aAllowExisting = true;
}
Components.utils.import("resource://gre/modules/ctypes.jsm");
const INITIAL_OWN = 1;
const ERROR_ALREADY_EXISTS = 0xB7;
var lib = ctypes.open("kernel32.dll");
var CreateMutexW = lib.declare("CreateMutexW",
ctypes.winapi_abi,
ctypes.void_t.ptr, /* return handle */
ctypes.void_t.ptr, /* security attributes */
ctypes.int32_t, /* initial owner */
ctypes.jschar.ptr); /* name */
var handle = CreateMutexW(null, INITIAL_OWN, name);
var alreadyExists = ctypes.winLastError == ERROR_ALREADY_EXISTS;
if (handle && !handle.isNull() && !aAllowExisting && alreadyExists) {
closeHandle(handle);
handle = null;
}
lib.close();
if (handle && handle.isNull())
handle = null;
return handle;
}
/**
* Determines a unique mutex name for the installation
*
* @param aGlobal true if the function should return a global mutex. A global
* mutex is valid across different sessions
* @return Global mutex path
*/
function getPerInstallationMutexName(aGlobal) {
if (aGlobal === undefined) {
aGobal = true;
}
let hasher = Components.classes["@mozilla.org/security/hash;1"].
createInstance(Components.interfaces.nsICryptoHash);
hasher.init(hasher.SHA1);
Components.utils.import("resource://gre/modules/Services.jsm");
var exeFile = Services.dirsvc.get("XREExeF", Components.interfaces.nsILocalFile);
let converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].
createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
var data = converter.convertToByteArray(exeFile.path.toLowerCase());
hasher.update(data, data.length);
return (aGlobal ? "Global\\" : "") + "MozillaUpdateMutex-" + hasher.finish(true);
}
#endif // XP_WIN
/**
* Whether or not the current instance has the update mutex. The update mutex
* gives protection against 2 browsers from the same installation updating:
* 1) Running multiple profiles from the same installation path
* 2) Running a Metro and Desktop browser at the same time from the same path
* 3) 2 browsers running in 2 different user sessions from the same path
*
* @return true if this instance holds the update mutex
*/
function hasUpdateMutex() {
#ifdef XP_WIN
if (!this._updateMutexHandle) {
this._updateMutexHandle = createMutex(getPerInstallationMutexName(true), false);
}
return !!this._updateMutexHandle;
#else
return true;
#endif // XP_WIN
}
XPCOMUtils.defineLazyGetter(this, "gCanApplyUpdates", function aus_gCanApplyUpdates() {
function submitHasPermissionsTelemetryPing(val) {
try {
@ -480,6 +582,12 @@ XPCOMUtils.defineLazyGetter(this, "gCanApplyUpdates", function aus_gCanApplyUpda
return false;
}
if (!hasUpdateMutex()) {
LOG("gCanApplyUpdates - unable to apply updates because another " +
"browser is already handling updates for this installation.");
return false;
}
LOG("gCanApplyUpdates - able to apply updates");
submitHasPermissionsTelemetryPing(true);
return true;
@ -524,6 +632,12 @@ XPCOMUtils.defineLazyGetter(this, "gCanStageUpdates", function aus_gCanStageUpda
return false;
}
if (!hasUpdateMutex()) {
LOG("gCanStageUpdates - unable to stage updates because another " +
"browser is already handling updates for this installation.");
return false;
}
LOG("gCanStageUpdates - able to stage updates");
return true;
});
@ -552,6 +666,12 @@ XPCOMUtils.defineLazyGetter(this, "gCanCheckForUpdates", function aus_gCanCheckF
return false;
}
if (!hasUpdateMutex()) {
LOG("gCanCheckForUpdates - unable to apply updates because another " +
"browser is already handling updates for this installation.");
return false;
}
LOG("gCanCheckForUpdates - able to check for updates");
return true;
});
@ -2524,6 +2644,14 @@ UpdateService.prototype = {
return gCanStageUpdates;
},
/**
* See nsIUpdateService.idl
*/
get isOtherInstanceHandlingUpdates() {
return !hasUpdateMutex();
},
/**
* See nsIUpdateService.idl
*/