Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
356 lines
9.4 KiB
JavaScript
Executable file
356 lines
9.4 KiB
JavaScript
Executable file
/* global ManifestHelper, UpdateManager, AppWindowManager, SettingsCache,
|
|
CustomDialog, asyncStorage */
|
|
'use strict';
|
|
|
|
/*
|
|
* An Updatable object represents an application *or* system update.
|
|
* It takes care of the interaction with the UpdateManager and observes
|
|
* the update itself to handle success/error cases.
|
|
*
|
|
* - name of the update
|
|
* - size of the update
|
|
* - download() to start the download
|
|
* - cancelDownload() to cancel it
|
|
*/
|
|
|
|
/* === App Updates === */
|
|
function AppUpdatable(app) {
|
|
this._mgmt = navigator.mozApps.mgmt;
|
|
this.app = app;
|
|
|
|
var manifest = app.manifest ? app.manifest : app.updateManifest;
|
|
this.name = new ManifestHelper(manifest).name;
|
|
this.nameL10nId = '';
|
|
|
|
this.size = app.downloadSize;
|
|
this.progress = null;
|
|
|
|
UpdateManager.addToUpdatableApps(this);
|
|
app.ondownloadavailable = this.availableCallBack.bind(this);
|
|
if (app.downloadAvailable) {
|
|
this.availableCallBack();
|
|
}
|
|
if (app.readyToApplyDownload) {
|
|
this.applyUpdate();
|
|
}
|
|
}
|
|
|
|
AppUpdatable.prototype.download = function() {
|
|
UpdateManager.addToDownloadsQueue(this);
|
|
this.progress = 0;
|
|
|
|
this.app.download();
|
|
};
|
|
|
|
AppUpdatable.prototype.cancelDownload = function() {
|
|
this.app.cancelDownload();
|
|
};
|
|
|
|
AppUpdatable.prototype.uninit = function() {
|
|
this.app.ondownloadavailable = null;
|
|
this.clean();
|
|
};
|
|
|
|
AppUpdatable.prototype.clean = function() {
|
|
this.app.ondownloaderror = null;
|
|
this.app.ondownloadsuccess = null;
|
|
this.app.ondownloadapplied = null;
|
|
this.app.onprogress = null;
|
|
|
|
this.progress = null;
|
|
};
|
|
|
|
AppUpdatable.prototype.availableCallBack = function() {
|
|
this.size = this.app.downloadSize;
|
|
|
|
if (this.app.installState === 'installed') {
|
|
UpdateManager.addToUpdatesQueue(this);
|
|
|
|
// we add these callbacks only now to prevent interfering
|
|
// with other modules (especially the AppInstallManager)
|
|
this.app.ondownloaderror = this.errorCallBack.bind(this);
|
|
this.app.ondownloadsuccess = this.successCallBack.bind(this);
|
|
this.app.ondownloadapplied = this.appliedCallBack.bind(this);
|
|
this.app.onprogress = this.progressCallBack.bind(this);
|
|
}
|
|
};
|
|
|
|
AppUpdatable.prototype.successCallBack = function() {
|
|
var app = this.app;
|
|
if (AppWindowManager.getActiveApp() &&
|
|
AppWindowManager.getActiveApp().origin !== app.origin) {
|
|
this.applyUpdate();
|
|
} else {
|
|
var self = this;
|
|
window.addEventListener('appwillclose', function waitClose() {
|
|
window.removeEventListener('appwillclose', waitClose);
|
|
self.applyUpdate();
|
|
});
|
|
}
|
|
|
|
UpdateManager.downloaded(this);
|
|
UpdateManager.removeFromDownloadsQueue(this);
|
|
UpdateManager.removeFromUpdatesQueue(this);
|
|
};
|
|
|
|
AppUpdatable.prototype.applyUpdate = function() {
|
|
AppWindowManager.kill(this.app.origin);
|
|
this._mgmt.applyDownload(this.app);
|
|
};
|
|
|
|
AppUpdatable.prototype.appliedCallBack = function() {
|
|
this.clean();
|
|
};
|
|
|
|
AppUpdatable.prototype.errorCallBack = function(e) {
|
|
var app = e.application;
|
|
var errorName = app.downloadError.name;
|
|
console.info('downloadError event, error code is', errorName);
|
|
UpdateManager.requestErrorBanner();
|
|
UpdateManager.removeFromDownloadsQueue(this);
|
|
if (!app.downloadAvailable) {
|
|
UpdateManager.removeFromUpdatesQueue(this);
|
|
}
|
|
this.progress = null;
|
|
};
|
|
|
|
AppUpdatable.prototype.progressCallBack = function() {
|
|
if (this.progress === null) {
|
|
// this is the first progress
|
|
UpdateManager.addToDownloadsQueue(this);
|
|
this.progress = 0;
|
|
}
|
|
|
|
var delta = this.app.progress - this.progress;
|
|
|
|
this.progress = this.app.progress;
|
|
UpdateManager.downloadProgressed(delta);
|
|
};
|
|
|
|
/*
|
|
* System Updates
|
|
* Will be instanciated only once by the UpdateManager
|
|
*
|
|
*/
|
|
function SystemUpdatable() {
|
|
this.nameL10nId = 'systemUpdate';
|
|
this.size = 0;
|
|
this.downloading = false;
|
|
this.paused = false;
|
|
|
|
// XXX: this state should be kept on the platform side
|
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=827090
|
|
this.checkKnownUpdate(UpdateManager.checkForUpdates.bind(UpdateManager));
|
|
|
|
window.addEventListener('mozChromeEvent', this);
|
|
}
|
|
|
|
SystemUpdatable.KNOWN_UPDATE_FLAG = 'known-sysupdate';
|
|
|
|
SystemUpdatable.prototype.download = function() {
|
|
if (this.downloading) {
|
|
return;
|
|
}
|
|
|
|
this.downloading = true;
|
|
this.paused = false;
|
|
UpdateManager.addToDownloadsQueue(this);
|
|
this.progress = 0;
|
|
this._dispatchEvent('update-available-result', 'download');
|
|
};
|
|
|
|
SystemUpdatable.prototype.cancelDownload = function() {
|
|
this._dispatchEvent('update-download-cancel');
|
|
this.downloading = false;
|
|
this.paused = false;
|
|
};
|
|
|
|
SystemUpdatable.prototype.uninit = function() {
|
|
window.removeEventListener('mozChromeEvent', this);
|
|
};
|
|
|
|
SystemUpdatable.prototype.handleEvent = function(evt) {
|
|
if (evt.type !== 'mozChromeEvent') {
|
|
return;
|
|
}
|
|
|
|
var detail = evt.detail;
|
|
if (!detail.type) {
|
|
return;
|
|
}
|
|
|
|
switch (detail.type) {
|
|
case 'update-error':
|
|
this.errorCallBack();
|
|
break;
|
|
case 'update-download-started':
|
|
// TODO UpdateManager glue
|
|
this.paused = false;
|
|
break;
|
|
case 'update-download-progress':
|
|
var delta = detail.progress - this.progress;
|
|
this.progress = detail.progress;
|
|
|
|
UpdateManager.downloadProgressed(delta);
|
|
break;
|
|
case 'update-download-stopped':
|
|
// TODO UpdateManager glue
|
|
this.paused = detail.paused;
|
|
if (!this.paused) {
|
|
UpdateManager.startedUncompressing();
|
|
}
|
|
break;
|
|
case 'update-downloaded':
|
|
this.downloading = false;
|
|
UpdateManager.downloaded(this);
|
|
this.showApplyPrompt();
|
|
break;
|
|
case 'update-prompt-apply':
|
|
this.showApplyPrompt();
|
|
break;
|
|
}
|
|
};
|
|
|
|
SystemUpdatable.prototype.errorCallBack = function() {
|
|
UpdateManager.requestErrorBanner();
|
|
UpdateManager.removeFromDownloadsQueue(this);
|
|
this.downloading = false;
|
|
};
|
|
|
|
SystemUpdatable.prototype.showApplyPrompt = function() {
|
|
var batteryLevel = window.navigator.battery.level * 100;
|
|
this.getBatteryPercentageThreshold().then(function(threshold) {
|
|
if (batteryLevel < threshold) {
|
|
this.showApplyPromptBatteryNok(threshold);
|
|
} else {
|
|
this.showApplyPromptBatteryOk();
|
|
}
|
|
}.bind(this));
|
|
};
|
|
|
|
SystemUpdatable.prototype.BATTERY_FALLBACK_THRESHOLD = 25;
|
|
|
|
SystemUpdatable.prototype.getBatteryPercentageThreshold = function() {
|
|
var fallbackThreshold = this.BATTERY_FALLBACK_THRESHOLD;
|
|
|
|
var isCharging = window.navigator.battery.charging;
|
|
var batteryThresholdKey =
|
|
'app.update.battery-threshold.' + (isCharging ? 'plugged' : 'unplugged');
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
SettingsCache.get(batteryThresholdKey, function(value) {
|
|
var threshold = value;
|
|
if (typeof threshold !== 'number') {
|
|
threshold = fallbackThreshold;
|
|
}
|
|
if (threshold < 0 || threshold > 100) {
|
|
threshold = fallbackThreshold;
|
|
}
|
|
resolve(threshold);
|
|
});
|
|
});
|
|
};
|
|
|
|
SystemUpdatable.prototype.showApplyPromptBatteryNok = function(minBattery) {
|
|
var ok = {
|
|
title: 'ok',
|
|
callback: this.declineInstallBattery.bind(this)
|
|
};
|
|
|
|
var screen = document.getElementById('screen');
|
|
|
|
CustomDialog.show(
|
|
'systemUpdateReady',
|
|
{ id: 'systemUpdateLowBatteryThreshold', args: { threshold: minBattery } },
|
|
ok,
|
|
null,
|
|
screen
|
|
)
|
|
.setAttribute('data-z-index-level', 'system-dialog');
|
|
};
|
|
|
|
SystemUpdatable.prototype.showApplyPromptBatteryOk = function() {
|
|
|
|
// Update will be completed after restart
|
|
this.forgetKnownUpdate();
|
|
|
|
var cancel = {
|
|
title: 'later',
|
|
callback: this.declineInstallWait.bind(this)
|
|
};
|
|
|
|
var confirm = {
|
|
title: 'installNow',
|
|
callback: this.acceptInstall.bind(this),
|
|
recommend: true
|
|
};
|
|
|
|
var screen = document.getElementById('screen');
|
|
|
|
CustomDialog.show(
|
|
'systemUpdateReady',
|
|
'wantToInstallNow',
|
|
cancel,
|
|
confirm,
|
|
screen
|
|
)
|
|
.setAttribute('data-z-index-level', 'system-dialog');
|
|
};
|
|
|
|
/**
|
|
* Decline install of update, forwarding `reason` to UpdatePrompt.jsm.
|
|
* `reason` is either 'wait' or 'low-battery'. 'wait' corresponds to the user
|
|
* deciding to delay the update, in which case the prompt will reappear after a
|
|
* few minutes of idle time. 'low-battery' means the battery is currently too
|
|
* low for an update to take place and the update prompt will not reappear.
|
|
* @param {String} reason
|
|
*/
|
|
SystemUpdatable.prototype.declineInstall = function(reason) {
|
|
CustomDialog.hide();
|
|
this._dispatchEvent('update-prompt-apply-result', reason);
|
|
|
|
UpdateManager.removeFromDownloadsQueue(this);
|
|
};
|
|
|
|
SystemUpdatable.prototype.declineInstallBattery = function() {
|
|
this.declineInstall('low-battery');
|
|
};
|
|
|
|
SystemUpdatable.prototype.declineInstallWait = function() {
|
|
this.declineInstall('wait');
|
|
};
|
|
|
|
|
|
SystemUpdatable.prototype.acceptInstall = function() {
|
|
CustomDialog.hide();
|
|
this._dispatchEvent('update-prompt-apply-result', 'restart');
|
|
};
|
|
|
|
SystemUpdatable.prototype.rememberKnownUpdate = function() {
|
|
asyncStorage.setItem(SystemUpdatable.KNOWN_UPDATE_FLAG, true);
|
|
};
|
|
|
|
SystemUpdatable.prototype.checkKnownUpdate = function(callback) {
|
|
if (typeof callback !== 'function') {
|
|
return;
|
|
}
|
|
|
|
asyncStorage.getItem(SystemUpdatable.KNOWN_UPDATE_FLAG, function(value) {
|
|
callback(!!value);
|
|
});
|
|
};
|
|
|
|
SystemUpdatable.prototype.forgetKnownUpdate = function() {
|
|
asyncStorage.removeItem(SystemUpdatable.KNOWN_UPDATE_FLAG);
|
|
};
|
|
|
|
SystemUpdatable.prototype._dispatchEvent = function(type, result) {
|
|
var event = document.createEvent('CustomEvent');
|
|
var data = { type: type };
|
|
if (result) {
|
|
data.result = result;
|
|
}
|
|
|
|
event.initCustomEvent('mozContentEvent', true, true, data);
|
|
window.dispatchEvent(event);
|
|
};
|