Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
601
gaia/apps/system/js/updatable.js
Executable file
601
gaia/apps/system/js/updatable.js
Executable file
|
|
@ -0,0 +1,601 @@
|
|||
'use strict';
|
||||
|
||||
/* global
|
||||
asyncStorage,
|
||||
ManifestHelper,
|
||||
NotificationHelper,
|
||||
Service,
|
||||
UpdateManager,
|
||||
IconsHelper,
|
||||
MozActivity
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
// Wrapper to read a settings value
|
||||
var setting = (function() {
|
||||
var values = {};
|
||||
return key => {
|
||||
|
||||
if (key in values) {
|
||||
return values[key];
|
||||
}
|
||||
|
||||
// Cache the promise straight away so multiple callers dont
|
||||
// create multiple listeners
|
||||
values[key] = new Promise(resolve => {
|
||||
// We store the value of the setting and the observer is
|
||||
// in charge of keeping it up to date
|
||||
function cacheAndResolve() {
|
||||
var result = (key in req.result) ? req.result[key] : undefined;
|
||||
values[key] = Promise.resolve(result);
|
||||
resolve(result);
|
||||
}
|
||||
|
||||
var lock = navigator.mozSettings.createLock();
|
||||
var req = lock.get(key);
|
||||
|
||||
req.onerror = cacheAndResolve;
|
||||
req.onsuccess = cacheAndResolve;
|
||||
|
||||
navigator.mozSettings.addObserver(key, e => {
|
||||
values[key] = Promise.resolve(e.settingValue);
|
||||
});
|
||||
});
|
||||
|
||||
return values[key];
|
||||
};
|
||||
})();
|
||||
|
||||
// Should we automatically update?
|
||||
AppUpdatable.AUTO_UPDATE = 'addons.auto_update';
|
||||
|
||||
// Notify the users after we have auto updated?
|
||||
AppUpdatable.NOTIFY_UPDATE = 'addons.update_notify';
|
||||
|
||||
// Notify the user about auto updates on first update
|
||||
AppUpdatable.AUTO_UPDATES_NOTIFIED = 'notified-autoupdate';
|
||||
|
||||
/* === 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.nameL10nArgs = null;
|
||||
|
||||
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') {
|
||||
return;
|
||||
}
|
||||
|
||||
// If its not an addon, add it to the update queue
|
||||
if (this.app.manifest.role !== 'addon') {
|
||||
this.queueUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
let settings = [
|
||||
setting(AppUpdatable.AUTO_UPDATE),
|
||||
setting(AppUpdatable.NOTIFY_UPDATE)
|
||||
];
|
||||
|
||||
Promise.all(settings).then(results => {
|
||||
|
||||
var autoAddonUpdates = results[0];
|
||||
var notifyAddonUpdates = results[1];
|
||||
|
||||
// If we have autoUpdates turned off for addons, they
|
||||
// go via the usual install flow
|
||||
if (!autoAddonUpdates) {
|
||||
this.queueUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
// We fail silently, the addon will be updated next time
|
||||
// we check for updates
|
||||
this.app.ondownloaderror = err => {
|
||||
console.error('failed to auto update', this.name,
|
||||
err.application.downloadError.name);
|
||||
};
|
||||
|
||||
this.app.ondownloadsuccess = this.applyUpdate.bind(this);
|
||||
this.app.onprogress = null;
|
||||
this.app.ondownloadapplied = null;
|
||||
|
||||
if (notifyAddonUpdates) {
|
||||
this.app.ondownloadapplied = this.notifyAutoUpdate.bind(this);
|
||||
}
|
||||
|
||||
this.app.download();
|
||||
this.maybeNotifyUpdate();
|
||||
});
|
||||
};
|
||||
|
||||
AppUpdatable.prototype.queueUpdate = function() {
|
||||
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.notifyAutoUpdate = function(iconUrl) {
|
||||
IconsHelper.getIcon(this.app.origin, 32, null, this.app).then(iconUrl => {
|
||||
var title = {id: 'addonUpdated', args: { name: this.name}};
|
||||
var opts = {
|
||||
tag: 'addonUpdated-' + this.name,
|
||||
bodyL10n: 'addonUpdatedDetails',
|
||||
mozbehavior: {showOnlyOnce: true },
|
||||
icon: iconUrl,
|
||||
closeOnClick: true
|
||||
};
|
||||
NotificationHelper.send(title, opts).then(n => {
|
||||
n.addEventListener('click', () => {
|
||||
/*jshint -W031 */
|
||||
new MozActivity({
|
||||
name: 'configure',
|
||||
data: {
|
||||
target: 'device',
|
||||
section: 'addon-details',
|
||||
options: {
|
||||
manifestURL: this.app.manifestURL
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// If this is the first auto update to addons, notify the
|
||||
// user that updates are being installed automatically
|
||||
AppUpdatable.prototype.maybeNotifyUpdate = function() {
|
||||
asyncStorage.getItem(AppUpdatable.AUTO_UPDATES_NOTIFIED, val => {
|
||||
if (val) {
|
||||
return;
|
||||
}
|
||||
// Ensure we dont show the notification again
|
||||
asyncStorage.setItem(AppUpdatable.AUTO_UPDATES_NOTIFIED, true);
|
||||
|
||||
var opts = {
|
||||
bodyL10n: 'addonUpdateDetails',
|
||||
tag: 'addonUpdate',
|
||||
mozbehavior: {showOnlyOnce: true },
|
||||
closeOnClick: true,
|
||||
icon: '/style/notifications/images/download.png'
|
||||
};
|
||||
|
||||
NotificationHelper.send('addonUpdate', opts).then(n => {
|
||||
n.addEventListener('click', function() {
|
||||
/*jshint -W031 */
|
||||
new MozActivity({
|
||||
name: 'configure',
|
||||
data: {target: 'device', section: 'about'}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
AppUpdatable.prototype.successCallBack = function() {
|
||||
var app = this.app;
|
||||
if (Service.query('AppWindowManager.getActiveWindow') &&
|
||||
Service.query('AppWindowManager.getActiveWindow').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() {
|
||||
Service.request('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 = 'systemUpdateWithVersion';
|
||||
this.nameL10nArgs = null;
|
||||
this.size = 0;
|
||||
this.buildID = null;
|
||||
this.detailsURL = null;
|
||||
this.downloading = false;
|
||||
this.paused = false;
|
||||
this.showingApplyPrompt = 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));
|
||||
|
||||
// We need to make sure that SystemUpdatable has an event listener ready
|
||||
// to catch mozChromeEvent to be sure we can get "update-error" events.
|
||||
// This was broken for the case of system updates error because of a race:
|
||||
// Gecko was sending the |update-error| event before the |mozChromeEvent|
|
||||
// could get installed.
|
||||
window.addEventListener('mozChromeEvent', this);
|
||||
this._dispatchEvent('update-prompt-ready');
|
||||
}
|
||||
|
||||
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(detail);
|
||||
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);
|
||||
UpdateManager.removeFromDownloadsQueue(this);
|
||||
this.showApplyPrompt(detail.isOSUpdate);
|
||||
break;
|
||||
case 'update-prompt-apply':
|
||||
this.showApplyPrompt(detail.isOSUpdate);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
SystemUpdatable.prototype.errorCallBack = function(aUpdate) {
|
||||
// Show notification for update installation failures
|
||||
console.debug('Handling systemUpdatable error: updateType',
|
||||
aUpdate.updateType, 'state', aUpdate.state);
|
||||
if (aUpdate.updateType === 'complete' && aUpdate.state === 'failed') {
|
||||
var errorOptions = {
|
||||
bodyL10n: 'systemUpdateErrorDetails',
|
||||
icon: '/style/notifications/images/system_update_error.svg',
|
||||
tag: 'systemUpdateError',
|
||||
mozbehavior: {
|
||||
showOnlyOnce: true
|
||||
},
|
||||
closeOnClick: false
|
||||
};
|
||||
|
||||
NotificationHelper.send('systemUpdateError', errorOptions).then(n => {
|
||||
n.addEventListener('click',
|
||||
this.showUpdateErrorDetails.bind(this, aUpdate));
|
||||
});
|
||||
} else {
|
||||
// Keep the old banner for the others for now
|
||||
UpdateManager.requestErrorBanner();
|
||||
}
|
||||
|
||||
UpdateManager.removeFromDownloadsQueue(this);
|
||||
this.downloading = false;
|
||||
};
|
||||
|
||||
SystemUpdatable.prototype.showUpdateErrorDetails = function(updateError) {
|
||||
var cancel = {
|
||||
title: 'later',
|
||||
callback: function() {
|
||||
Service.request('hideCustomDialog');
|
||||
}
|
||||
};
|
||||
|
||||
var confirm = {
|
||||
title: 'report',
|
||||
callback: function() {
|
||||
Service.request('hideCustomDialog');
|
||||
Notification.get({ tag: 'systemUpdateError' }).then(ns => {
|
||||
ns.forEach(n => {
|
||||
n && n.close();
|
||||
});
|
||||
});
|
||||
window.dispatchEvent(new CustomEvent('requestSystemLogs'));
|
||||
},
|
||||
recommend: true
|
||||
};
|
||||
|
||||
Service.request('UtilityTray:hide');
|
||||
Service.request('showCustomDialog',
|
||||
'systemUpdateError',
|
||||
{ id: 'wantToReportNow',
|
||||
args: {
|
||||
version: updateError.appVersion,
|
||||
buildID: updateError.buildID
|
||||
}
|
||||
},
|
||||
cancel,
|
||||
confirm
|
||||
);
|
||||
};
|
||||
|
||||
// isOsUpdate comes from Gecko's update object passed in the mozChromeEvent
|
||||
// and is expected to be true in case of an update package that gets applied
|
||||
// in recovery mode (FOTA). We want to show the battery warning only in this
|
||||
// case as described in bug 959195
|
||||
SystemUpdatable.prototype.showApplyPrompt = function(isOsUpdate) {
|
||||
var batteryLevel = window.navigator.battery.level * 100;
|
||||
this.getBatteryPercentageThreshold().then(function(threshold) {
|
||||
this.showingApplyPrompt = true;
|
||||
if (isOsUpdate && 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');
|
||||
|
||||
var settings = window.navigator.mozSettings;
|
||||
var getRequest = settings.createLock().get(batteryThresholdKey);
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
getRequest.onerror = function() {
|
||||
resolve(fallbackThreshold);
|
||||
};
|
||||
getRequest.onsuccess = function() {
|
||||
var threshold = getRequest.result[batteryThresholdKey];
|
||||
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)
|
||||
};
|
||||
|
||||
Service.request('UtilityTray:hide');
|
||||
Service.request('showCustomDialog',
|
||||
'systemUpdateReady',
|
||||
{ id: 'systemUpdateLowBatteryThreshold', args: { threshold: minBattery } },
|
||||
ok,
|
||||
null
|
||||
);
|
||||
};
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
Service.request('UtilityTray:hide');
|
||||
Service.request('showCustomDialog',
|
||||
'systemUpdateReady',
|
||||
'wantToInstallNow',
|
||||
cancel,
|
||||
confirm
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
this.showingApplyPrompt = false;
|
||||
Service.request('hideCustomDialog');
|
||||
this._dispatchEvent('update-prompt-apply-result', reason);
|
||||
};
|
||||
|
||||
SystemUpdatable.prototype.declineInstallBattery = function() {
|
||||
this.declineInstall('low-battery');
|
||||
};
|
||||
|
||||
SystemUpdatable.prototype.declineInstallWait = function() {
|
||||
this.declineInstall('wait');
|
||||
};
|
||||
|
||||
|
||||
SystemUpdatable.prototype.acceptInstall = function() {
|
||||
Service.request('hideCustomDialog');
|
||||
|
||||
// Display a splash-screen so the user knows an update is being applied
|
||||
var splash = document.createElement('form');
|
||||
splash.id = 'system-update-splash';
|
||||
['label', 'divider', 'icon'].forEach(function(name) {
|
||||
var child = document.createElement('div');
|
||||
child.id = name;
|
||||
splash.appendChild(child);
|
||||
});
|
||||
splash.firstChild.setAttribute('data-l10n-id', 'systemUpdate');
|
||||
|
||||
var screen = document.getElementById('screen');
|
||||
screen.appendChild(splash);
|
||||
|
||||
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);
|
||||
};
|
||||
Loading…
Reference in a new issue