Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
872 lines
28 KiB
JavaScript
872 lines
28 KiB
JavaScript
/* jshint moz:true */
|
||
/* global ConfirmDialogHelper */
|
||
/* global KeyboardHelper */
|
||
/* global LazyLoader */
|
||
/* global ManifestHelper */
|
||
/* global MatchPattern */
|
||
/* global ModalDialog */
|
||
/* global NotificationScreen */
|
||
/* global Service */
|
||
/* global SystemBanner */
|
||
/* global Sanitizer */
|
||
/* global UtilityTray */
|
||
/* global applications */
|
||
|
||
'use strict';
|
||
|
||
(function(exports) {
|
||
var AppInstallManager = {
|
||
DEBUG: false,
|
||
name: 'AppInstallManager',
|
||
mapDownloadErrorsToMessage: {
|
||
'NETWORK_ERROR': 'download-failed',
|
||
'DOWNLOAD_ERROR': 'download-failed',
|
||
'MISSING_MANIFEST': 'install-failed',
|
||
'INVALID_MANIFEST': 'install-failed',
|
||
'INSTALL_FROM_DENIED': 'install-failed',
|
||
'INVALID_SECURITY_LEVEL': 'install-failed',
|
||
'INVALID_PACKAGE': 'install-failed',
|
||
'APP_CACHE_DOWNLOAD_ERROR': 'download-failed',
|
||
'DOWNLOAD_CANCELED':'download-canceled'
|
||
},
|
||
|
||
start: function() {
|
||
var lazyLoadScripts = [
|
||
'js/system_banner.js',
|
||
'shared/js/addons/match_pattern.js'
|
||
];
|
||
LazyLoader.load(lazyLoadScripts).then(() => {
|
||
this.systemBanner = new SystemBanner();
|
||
}).catch((err) => {
|
||
console.error(err);
|
||
});
|
||
this.dialog = document.getElementById('app-install-dialog');
|
||
this.msg = document.getElementById('app-install-message');
|
||
this.size = document.getElementById('app-install-size');
|
||
this.authorName = document.getElementById('app-install-author-name');
|
||
this.authorUrl = document.getElementById('app-install-author-url');
|
||
this.installButton =
|
||
document.getElementById('app-install-install-button');
|
||
this.cancelButton = document.getElementById('app-install-cancel-button');
|
||
this.imeLayoutDialog = document.getElementById('ime-layout-dialog');
|
||
this.imeList = document.getElementById('ime-list');
|
||
this.imeCancelButton = document.getElementById('ime-cancel-button');
|
||
this.imeConfirmButton = document.getElementById('ime-confirm-button');
|
||
this.setupCancelButton =
|
||
document.getElementById('setup-cancel-button');
|
||
this.setupConfirmButton =
|
||
document.getElementById('setup-confirm-button');
|
||
|
||
this.installCancelDialog =
|
||
document.getElementById('app-install-cancel-dialog');
|
||
this.downloadCancelDialog =
|
||
document.getElementById('app-download-cancel-dialog');
|
||
this.setupInstalledAppDialog =
|
||
document.getElementById('setup-installed-app-dialog');
|
||
this.confirmCancelButton =
|
||
document.getElementById('app-install-confirm-cancel-button');
|
||
this.setupAppName = document.getElementById('setup-app-name');
|
||
this.setupAppDescription =
|
||
document.getElementById('setup-app-description');
|
||
|
||
this.resumeButton = document.getElementById('app-install-resume-button');
|
||
|
||
this.notifContainer =
|
||
document.getElementById('install-manager-notification-container');
|
||
this.appInfos = {};
|
||
this.setupQueue = [];
|
||
this.isSetupInProgress = false;
|
||
|
||
window.addEventListener('mozChromeEvent', this);
|
||
window.addEventListener('applicationinstall', this);
|
||
window.addEventListener('applicationuninstall', this);
|
||
|
||
this.installButton.onclick = this.handleInstall.bind(this);
|
||
this.cancelButton.onclick = this.showInstallCancelDialog.bind(this);
|
||
this.confirmCancelButton.onclick = this.handleInstallCancel.bind(this);
|
||
this.resumeButton.onclick = this.hideInstallCancelDialog.bind(this);
|
||
this.notifContainer.onclick = this.showDownloadCancelDialog.bind(this);
|
||
|
||
this.downloadCancelDialog.querySelector('.confirm').onclick =
|
||
this.handleConfirmDownloadCancel.bind(this);
|
||
this.downloadCancelDialog.querySelector('.cancel').onclick =
|
||
this.handleCancelDownloadCancel.bind(this);
|
||
|
||
this.setupCancelButton.onclick = this.handleSetupCancelAction.bind(this);
|
||
this.setupConfirmButton.onclick =
|
||
this.handleSetupConfirmAction.bind(this);
|
||
this.imeCancelButton.onclick = this.hideIMEList.bind(this);
|
||
this.imeConfirmButton.onclick = this.handleImeConfirmAction.bind(this);
|
||
LazyLoader.load(['shared/js/homescreens/confirm_dialog_helper.js']);
|
||
|
||
window.addEventListener('applicationready', this);
|
||
|
||
window.addEventListener('home', this);
|
||
window.addEventListener('holdhome', this);
|
||
|
||
// bind these handlers so that we can have only one instance and check
|
||
// them later on
|
||
['handleDownloadSuccess',
|
||
'handleDownloadError',
|
||
'handleProgress',
|
||
'handleApplicationReady'
|
||
].forEach(function(name) {
|
||
this[name] = this[name].bind(this);
|
||
}, this);
|
||
},
|
||
|
||
stop: function ai_stop() {
|
||
window.removeEventListener('mozChromeEvent', this);
|
||
window.removeEventListener('applicationinstall', this);
|
||
window.removeEventListener('applicationuninstall', this);
|
||
window.removeEventListener('applicationready', this);
|
||
|
||
window.removeEventListener('home', this);
|
||
window.removeEventListener('holdhome', this);
|
||
},
|
||
|
||
handleEvent: function ai_handleEvent(evt) {
|
||
switch (evt.type) {
|
||
case 'mozChromeEvent':
|
||
return this.handleMozChromeEvent(evt);
|
||
case 'applicationready':
|
||
return this.handleApplicationReady(evt);
|
||
case 'home':
|
||
case 'holdhome':
|
||
return this.cancelInstallation(evt);
|
||
case 'applicationinstall':
|
||
return this.handleApplicationInstall(evt);
|
||
case 'applicationuninstall':
|
||
return this.handleApplicationUninstall(evt);
|
||
}
|
||
},
|
||
|
||
imeListView: function({displayName, imeName}) {
|
||
return Sanitizer.createSafeHTML `
|
||
<li>
|
||
<gaia-checkbox class="ime inline" name="keyboards" value="${imeName}">
|
||
<label>${displayName}</label>
|
||
</gaia-checkbox>
|
||
</li>`;
|
||
},
|
||
|
||
cancelInstallation: function ai_cancelInstallation() {
|
||
this.dialog.classList.remove('visible');
|
||
this.dispatchPromptEvent('hidden');
|
||
this.handleInstallCancel();
|
||
|
||
// hide IME setup dialog if presented
|
||
if (this.setupInstalledAppDialog.classList.contains('visible') ) {
|
||
this.handleSetupCancelAction();
|
||
}
|
||
|
||
// hide IME layout list if presented
|
||
if (this.imeLayoutDialog.classList.contains('visible')) {
|
||
this.hideIMEList();
|
||
}
|
||
},
|
||
|
||
handleMozChromeEvent: function ai_handleMozChromeEvent(e) {
|
||
switch (e.detail.type) {
|
||
case 'webapps-ask-install':
|
||
var manifestURL = e.detail.app && e.detail.app.manifestURL;
|
||
var isCustomizing = Service.query('ftuCustomizationContains',
|
||
manifestURL);
|
||
this.debug('handling webapps-ask-install, ' +
|
||
'manifestURL: ' + manifestURL + ', ' +
|
||
'isCustomizing: ' + isCustomizing);
|
||
|
||
if (manifestURL && isCustomizing) {
|
||
var grantedEvent = new CustomEvent('mozContentEvent', {
|
||
bubbles: true,
|
||
cancelable: true,
|
||
detail: {
|
||
id: e.detail.id,
|
||
type: 'webapps-install-granted'
|
||
}
|
||
});
|
||
window.dispatchEvent(grantedEvent);
|
||
} else {
|
||
this.handleAppInstallPrompt(e.detail);
|
||
}
|
||
break;
|
||
case 'webapps-ask-uninstall':
|
||
this.handleAppUninstallPrompt(e.detail);
|
||
break;
|
||
}
|
||
},
|
||
|
||
handleApplicationReady: function ai_handleApplicationReady(e) {
|
||
window.removeEventListener('applicationready',
|
||
this.handleApplicationReady);
|
||
|
||
var apps = e.detail.applications;
|
||
|
||
Object.keys(apps)
|
||
.filter(function(key) { return apps[key].installState === 'pending'; })
|
||
.map(function(key) { return apps[key]; })
|
||
.forEach(this.prepareForDownload, this);
|
||
},
|
||
|
||
handleApplicationInstall: function ai_handleApplicationInstallEvent(e) {
|
||
var app = e.detail.application;
|
||
|
||
if (app.installState === 'installed') {
|
||
this.handleInstallSuccess(app);
|
||
return;
|
||
}
|
||
|
||
this.prepareForDownload(app);
|
||
},
|
||
|
||
handleApplicationUninstall: function ai_handleApplicationUninstall(e) {
|
||
var app = e.detail.application;
|
||
|
||
this.onDownloadStop(app);
|
||
this.onDownloadFinish(app);
|
||
},
|
||
|
||
handleAppInstallPrompt: function ai_handleInstallPrompt(detail) {
|
||
var app = detail.app;
|
||
// updateManifest is used by packaged apps until they are installed
|
||
var manifest = app.manifest ? app.manifest : app.updateManifest;
|
||
|
||
if (!manifest) {
|
||
return;
|
||
}
|
||
|
||
this.dialog.classList.add('visible');
|
||
this.dispatchPromptEvent('shown');
|
||
|
||
var id = detail.id;
|
||
|
||
if (manifest.size) {
|
||
this.size.removeAttribute('data-l10n-id');
|
||
this.size.textContent = this.humanizeSize(manifest.size);
|
||
} else {
|
||
this.size.setAttribute('data-l10n-id', 'size-unknown');
|
||
}
|
||
|
||
// Wrap manifest to get localized properties
|
||
manifest = new ManifestHelper(manifest);
|
||
navigator.mozL10n.setAttributes(this.msg, 'install-app', {
|
||
'name': manifest.displayName
|
||
});
|
||
|
||
if (manifest.developer) {
|
||
if (manifest.developer.name) {
|
||
this.authorName.removeAttribute('data-l10n-id');
|
||
this.authorName.textContent = manifest.developer.name;
|
||
} else {
|
||
this.authorName.setAttribute('data-l10n-id', 'author-unknown');
|
||
}
|
||
this.authorUrl.textContent = manifest.developer.url || '';
|
||
} else {
|
||
this.authorName.setAttribute('data-l10n-id', 'author-unknown');
|
||
this.authorUrl.textContent = '';
|
||
}
|
||
|
||
// Warn about system level addons?
|
||
var warningEl = document.getElementById('system-addon-warning');
|
||
warningEl.hidden = !this.requiresRestart(manifest.content_scripts);
|
||
|
||
this.installCallback = (function ai_installCallback() {
|
||
this.dispatchResponse(id, 'webapps-install-granted');
|
||
}).bind(this);
|
||
|
||
this.installCancelCallback = (function ai_cancelCallback() {
|
||
this.dispatchResponse(id, 'webapps-install-denied');
|
||
}).bind(this);
|
||
|
||
},
|
||
|
||
requiresRestart: function ai_requiresRestart(contentScripts) {
|
||
var restartRequired = false;
|
||
|
||
if (contentScripts) {
|
||
var patterns = contentScripts.map(contentScript => {
|
||
return {
|
||
matches: new MatchPattern(contentScript.matches),
|
||
excludeMatches: new MatchPattern(contentScript.exclude_matches)
|
||
};
|
||
});
|
||
|
||
appLoop:
|
||
for (var appKey in applications.installedApps) {
|
||
if (!appKey) {
|
||
continue;
|
||
}
|
||
|
||
var installedApp = applications.installedApps[appKey];
|
||
var launchPath = installedApp.manifest.launch_path || '';
|
||
|
||
if (!installedApp.manifest.origin) {
|
||
continue;
|
||
}
|
||
|
||
if (installedApp.manifest.role !== 'system' &&
|
||
installedApp.manifest.role !== 'homescreen') {
|
||
continue;
|
||
}
|
||
|
||
var launchURL = new URL(launchPath, installedApp.manifest.origin);
|
||
|
||
for(var i = 0; i < patterns.length; i++) {
|
||
var pattern = patterns[i];
|
||
if (!pattern.excludeMatches.matches(launchURL) &&
|
||
pattern.matches.matches(launchURL)) {
|
||
|
||
restartRequired = true;
|
||
break appLoop;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return restartRequired;
|
||
},
|
||
|
||
handleInstall: function ai_handleInstall(evt) {
|
||
if (evt) {
|
||
evt.preventDefault();
|
||
}
|
||
if (this.installCallback) {
|
||
this.installCallback();
|
||
}
|
||
this.installCallback = null;
|
||
this.installCancelCallback = null;
|
||
this.dialog.classList.remove('visible');
|
||
this.dispatchPromptEvent('hidden');
|
||
},
|
||
|
||
handleAppUninstallPrompt: function ai_handleUninstallPrompt(detail) {
|
||
var app = detail.app;
|
||
var id = detail.id;
|
||
|
||
// updateManifest is used by packaged apps until they are installed
|
||
var manifest = app.manifest ? app.manifest : app.updateManifest;
|
||
if (!manifest) {
|
||
return;
|
||
}
|
||
|
||
// Wrap manifest to get localized properties
|
||
manifest = new ManifestHelper(manifest);
|
||
|
||
if (manifest.role === 'theme') {
|
||
this.dispatchResponse(id, 'webapps-uninstall-granted');
|
||
return;
|
||
}
|
||
|
||
var unrecoverable = app.installState === 'pending' &&
|
||
!app.downloadAvailable &&
|
||
!app.readyToApplyDownload;
|
||
|
||
var dialogConfig;
|
||
|
||
if (unrecoverable) {
|
||
dialogConfig = {
|
||
type: 'unrecoverable',
|
||
title: 'unrecoverable-error-title',
|
||
body: 'unrecoverable-error-body',
|
||
confirm: {
|
||
title: 'unrecoverable-error-action',
|
||
cb: () => {
|
||
this.dispatchResponse(id, 'webapps-uninstall-granted');
|
||
}
|
||
}
|
||
};
|
||
} else {
|
||
var nameObj = { name: manifest.displayName };
|
||
dialogConfig = {
|
||
type: 'remove',
|
||
title: {id: 'delete-title', args: nameObj},
|
||
body: {id: 'delete-body', args: nameObj},
|
||
cancel: {
|
||
title: 'cancel',
|
||
cb: () => { this.dispatchResponse(id, 'webapps-uninstall-denied'); }
|
||
},
|
||
confirm: {
|
||
title: 'delete',
|
||
type: 'danger',
|
||
cb: () => {
|
||
this.dispatchResponse(id, 'webapps-uninstall-granted');
|
||
}
|
||
}
|
||
};
|
||
}
|
||
|
||
var dialog = new ConfirmDialogHelper(dialogConfig);
|
||
dialog.show(document.getElementById('app-uninstall-dialog'));
|
||
},
|
||
|
||
prepareForDownload: function ai_prepareForDownload(app) {
|
||
var manifestURL = app.manifestURL;
|
||
this.appInfos[manifestURL] = {};
|
||
|
||
// these methods are already bound to |this|
|
||
app.ondownloadsuccess = this.handleDownloadSuccess;
|
||
app.ondownloaderror = this.handleDownloadError;
|
||
app.onprogress = this.handleProgress;
|
||
},
|
||
|
||
configurations: {
|
||
'input': {
|
||
fnName: 'showIMEList'
|
||
}
|
||
},
|
||
|
||
handleInstallSuccess: function ai_handleInstallSuccess(app) {
|
||
var manifest = app.manifest || app.updateManifest;
|
||
var role = manifest.role;
|
||
|
||
// We must stop 3rd-party keyboard app from being installed
|
||
// if the feature is not enabled.
|
||
if (role === 'input' &&
|
||
!Service.query('isOutOfProcessEnabled')) {
|
||
navigator.mozApps.mgmt.uninstall(app);
|
||
|
||
return;
|
||
}
|
||
|
||
if (this.configurations[role]) {
|
||
this.setupQueue.push(app);
|
||
this.checkSetupQueue();
|
||
} else {
|
||
this.showInstallSuccess(app);
|
||
}
|
||
// send event
|
||
var evt = new CustomEvent('applicationinstallsuccess',
|
||
{ detail: { application: app } });
|
||
window.dispatchEvent(evt);
|
||
},
|
||
|
||
showInstallSuccess: function ai_showInstallSuccess(app) {
|
||
if (Service.query('isFtuRunning')) {
|
||
return;
|
||
}
|
||
var manifest = app.manifest || app.updateManifest;
|
||
var appManifest = new ManifestHelper(manifest);
|
||
var name = appManifest.displayName;
|
||
var l10nId = appManifest.role === 'langpack' ?
|
||
'langpack-install-success2' : 'app-install-success';
|
||
this.systemBanner.show({id: l10nId, args: { appName: name }});
|
||
},
|
||
|
||
checkSetupQueue: function ai_checkSetupQueue() {
|
||
if (this.setupQueue.length && !(this.isSetupInProgress)) {
|
||
this.isSetupInProgress = true;
|
||
this.showSetupDialog();
|
||
}
|
||
},
|
||
|
||
completedSetupTask: function ai_completedSetupTask() {
|
||
// clean completed app
|
||
this.setupQueue.shift();
|
||
this.isSetupInProgress = false;
|
||
this.checkSetupQueue();
|
||
},
|
||
|
||
hideSetupDialog: function ai_hideSetupDialog() {
|
||
this.setupAppName.textContent = '';
|
||
this.setupAppDescription.textContent = '';
|
||
this.setupInstalledAppDialog.classList.remove('visible');
|
||
this.dispatchPromptEvent('hidden');
|
||
},
|
||
|
||
showSetupDialog: function ai_showSetupDialog() {
|
||
var app = this.setupQueue[0];
|
||
var manifest = app.manifest;
|
||
var appManifest = new ManifestHelper(manifest);
|
||
var appName = appManifest.displayName;
|
||
var appDescription = appManifest.description;
|
||
this.setupAppDescription.textContent = appDescription;
|
||
navigator.mozL10n.setAttributes(this.setupAppName,
|
||
'app-install-success',
|
||
{ appName: appName });
|
||
this.setupInstalledAppDialog.classList.add('visible');
|
||
this.dispatchPromptEvent('shown');
|
||
window.dispatchEvent(new CustomEvent('applicationsetupdialogshow'));
|
||
},
|
||
|
||
handleSetupCancelAction: function ai_handleSetupCancelAction() {
|
||
this.hideSetupDialog();
|
||
this.completedSetupTask();
|
||
},
|
||
|
||
handleSetupConfirmAction: function ai_handleSetupConfirmAction() {
|
||
var fnName = this.configurations[this.setupQueue[0].manifest.role].fnName;
|
||
this[fnName].call(this);
|
||
this.hideSetupDialog();
|
||
},
|
||
|
||
showIMEList: function ai_showIMEList() {
|
||
var app = this.setupQueue[0];
|
||
var inputs = app.manifest.inputs;
|
||
if (typeof inputs !== 'object') {
|
||
console.error('inputs must be an object for ' +
|
||
'third-party keyboard layouts');
|
||
this.completedSetupTask();
|
||
return;
|
||
}
|
||
|
||
// Check permission level is correct
|
||
var hasInputPermission = (app.manifest.type === 'certified' ||
|
||
app.manifest.type === 'privileged') &&
|
||
(app.manifest.permissions &&
|
||
'input' in app.manifest.permissions);
|
||
if (!hasInputPermission) {
|
||
console.error('third-party IME does not have correct input permission');
|
||
this.completedSetupTask();
|
||
return;
|
||
}
|
||
|
||
// build the list of keyboard layouts
|
||
var listHtml = [];
|
||
for (var name in inputs) {
|
||
var displayIMEName = new ManifestHelper(inputs[name]).displayName;
|
||
listHtml.push(this.imeListView({
|
||
imeName: name,
|
||
displayName: displayIMEName
|
||
}));
|
||
}
|
||
// keeping li template
|
||
this.imeList.innerHTML = Sanitizer.unwrapSafeHTML(...listHtml);
|
||
this.imeLayoutDialog.classList.add('visible');
|
||
this.dispatchPromptEvent('shown');
|
||
},
|
||
|
||
hideIMEList: function ai_hideIMEList() {
|
||
this.imeLayoutDialog.classList.remove('visible');
|
||
this.dispatchPromptEvent('hidden');
|
||
this.imeList.innerHTML = '';
|
||
this.completedSetupTask();
|
||
},
|
||
|
||
handleImeConfirmAction: function ai_handleImeConfirmAction() {
|
||
var manifestURL = this.setupQueue[0].manifestURL;
|
||
var keyboards = this.imeList.getElementsByTagName('gaia-checkbox');
|
||
for (var i = 0, l = keyboards.length; i < l; i++) {
|
||
var keyboardIME = keyboards[i];
|
||
if (keyboardIME.checked) {
|
||
KeyboardHelper.setLayoutEnabled(manifestURL,
|
||
keyboardIME.getAttribute('value'), true);
|
||
KeyboardHelper.saveToSettings();
|
||
}
|
||
}
|
||
this.hideIMEList();
|
||
},
|
||
|
||
handleDownloadSuccess: function ai_handleDownloadSuccess(evt) {
|
||
var app = evt.application;
|
||
this.handleInstallSuccess(app);
|
||
this.onDownloadStop(app);
|
||
this.onDownloadFinish(app);
|
||
},
|
||
|
||
handleDownloadError: function ai_handleDownloadError(evt) {
|
||
var app = evt.application;
|
||
var manifest = app.manifest || app.updateManifest;
|
||
var name = new ManifestHelper(manifest).displayName;
|
||
|
||
var errorName = app.downloadError.name;
|
||
|
||
switch (errorName) {
|
||
case 'INSUFFICIENT_STORAGE':
|
||
var title = 'not-enough-space',
|
||
buttonText = 'ok',
|
||
message = 'not-enough-space-message';
|
||
|
||
ModalDialog.alert(title, message, {title: buttonText});
|
||
break;
|
||
default:
|
||
// showing the real error to a potential developer
|
||
console.info('downloadError event, error code is', errorName);
|
||
|
||
var key =
|
||
this.mapDownloadErrorsToMessage[errorName] || 'generic-error';
|
||
this.systemBanner.show({
|
||
id: 'app-install-' + key,
|
||
args: { appName: name }
|
||
});
|
||
}
|
||
|
||
this.onDownloadStop(app);
|
||
},
|
||
|
||
onDownloadStart: function ai_onDownloadStart(app) {
|
||
if (! this.hasNotification(app)) {
|
||
Service.request('incDownloads');
|
||
this.addNotification(app);
|
||
this.requestWifiLock(app);
|
||
}
|
||
},
|
||
|
||
onDownloadStop: function ai_onDownloadStop(app) {
|
||
if (this.hasNotification(app)) {
|
||
Service.request('decDownloads');
|
||
this.removeNotification(app);
|
||
this.releaseWifiLock(app);
|
||
}
|
||
},
|
||
|
||
hasNotification: function ai_hasNotification(app) {
|
||
var appInfo = this.appInfos[app.manifestURL];
|
||
return appInfo && !!appInfo.installNotification;
|
||
},
|
||
|
||
onDownloadFinish: function ai_onDownloadFinish(app) {
|
||
delete this.appInfos[app.manifestURL];
|
||
|
||
// check if these are our handlers before removing them
|
||
if (app.ondownloadsuccess === this.handleDownloadSuccess) {
|
||
app.ondownloadsuccess = null;
|
||
}
|
||
|
||
if (app.ondownloaderror === this.handleDownloadError) {
|
||
app.ondownloaderror = null;
|
||
}
|
||
|
||
if (app.onprogress === this.handleProgress) {
|
||
app.onprogress = null;
|
||
}
|
||
},
|
||
|
||
addNotification: function ai_addNotification(app) {
|
||
// should be unique (this is used already in applications.js)
|
||
var manifestURL = app.manifestURL,
|
||
appInfo = this.appInfos[manifestURL];
|
||
|
||
if (appInfo.installNotification) {
|
||
return;
|
||
}
|
||
|
||
var newNotif = Sanitizer.createSafeHTML `
|
||
<div class="fake-notification" role="link">
|
||
<div data-icon="rocket" class="alert" aria-hidden="true"></div>
|
||
<div class="title-container"></div>
|
||
<progress></progress>
|
||
</div>`;
|
||
|
||
this.notifContainer.insertAdjacentHTML('afterbegin',
|
||
Sanitizer.unwrapSafeHTML(newNotif));
|
||
|
||
var newNode = this.notifContainer.firstElementChild;
|
||
newNode.dataset.manifest = manifestURL;
|
||
|
||
var manifest = app.manifest || app.updateManifest;
|
||
|
||
navigator.mozL10n.setAttributes(
|
||
newNode.querySelector('.title-container'),
|
||
'downloadingAppMessage',
|
||
{ appName: new ManifestHelper(manifest).displayName }
|
||
);
|
||
|
||
var progressNode = newNode.querySelector('progress');
|
||
if (app.updateManifest && app.updateManifest.size) {
|
||
progressNode.max = app.updateManifest.size;
|
||
appInfo.hasMax = true;
|
||
}
|
||
|
||
appInfo.installNotification = newNode;
|
||
NotificationScreen.addUnreadNotification(manifestURL);
|
||
},
|
||
|
||
getNotificationProgressNode: function ai_getNotificationProgressNode(app) {
|
||
var appInfo = this.appInfos[app.manifestURL];
|
||
var progressNode = appInfo &&
|
||
appInfo.installNotification &&
|
||
appInfo.installNotification.querySelector('progress');
|
||
return progressNode || null;
|
||
},
|
||
|
||
handleProgress: function ai_handleProgress(evt) {
|
||
var app = evt.application,
|
||
appInfo = this.appInfos[app.manifestURL];
|
||
|
||
this.onDownloadStart(app);
|
||
|
||
|
||
var progressNode = this.getNotificationProgressNode(app);
|
||
var message;
|
||
|
||
if (isNaN(app.progress) || app.progress == null) {
|
||
// now we get NaN if there is no progress information but let's
|
||
// handle the null and undefined cases as well
|
||
message = {
|
||
id: 'downloadingAppProgressIndeterminate',
|
||
args: null
|
||
};
|
||
progressNode.removeAttribute('value'); // switch to indeterminate state
|
||
} else if (appInfo.hasMax) {
|
||
message = {
|
||
id: 'downloadingAppProgress',
|
||
args: {
|
||
progress: this.humanizeSize(app.progress),
|
||
max: this.humanizeSize(progressNode.max)
|
||
}
|
||
};
|
||
progressNode.value = app.progress;
|
||
} else {
|
||
message = {
|
||
id: 'downloadingAppProgressNoMax',
|
||
args: { progress: this.humanizeSize(app.progress) }
|
||
};
|
||
}
|
||
navigator.mozL10n.setAttributes(
|
||
progressNode,
|
||
message.id,
|
||
message.args);
|
||
},
|
||
|
||
removeNotification: function ai_removeNotification(app) {
|
||
var manifestURL = app.manifestURL,
|
||
appInfo = this.appInfos[manifestURL],
|
||
node = appInfo.installNotification;
|
||
|
||
if (!node) {
|
||
return;
|
||
}
|
||
|
||
node.parentNode.removeChild(node);
|
||
delete appInfo.installNotification;
|
||
NotificationScreen.removeUnreadNotification(manifestURL);
|
||
},
|
||
|
||
requestWifiLock: function ai_requestWifiLock(app) {
|
||
var appInfo = this.appInfos[app.manifestURL];
|
||
if (! appInfo.wifiLock) {
|
||
// we don't want 2 locks for the same app
|
||
appInfo.wifiLock = navigator.requestWakeLock('wifi');
|
||
}
|
||
},
|
||
|
||
releaseWifiLock: function ai_releaseWifiLock(app) {
|
||
var appInfo = this.appInfos[app.manifestURL];
|
||
|
||
if (appInfo.wifiLock) {
|
||
try {
|
||
appInfo.wifiLock.unlock();
|
||
} catch (e) {
|
||
// this can happen if the lock is already unlocked
|
||
console.error('error during unlock', e);
|
||
}
|
||
|
||
delete appInfo.wifiLock;
|
||
}
|
||
},
|
||
|
||
dispatchResponse: function ai_dispatchResponse(id, type) {
|
||
var event = document.createEvent('CustomEvent');
|
||
|
||
event.initCustomEvent('mozContentEvent', true, true, {
|
||
id: id,
|
||
type: type
|
||
});
|
||
|
||
window.dispatchEvent(event);
|
||
},
|
||
|
||
humanizeSize: function ai_humanizeSize(bytes) {
|
||
var _ = navigator.mozL10n.get;
|
||
var units = ['bytes', 'kB', 'MB', 'GB', 'TB', 'PB'];
|
||
|
||
if (!bytes) {
|
||
return '0.00 ' + _(units[0]);
|
||
}
|
||
|
||
var e = Math.floor(Math.log(bytes) / Math.log(1024));
|
||
return (bytes / Math.pow(1024, Math.floor(e))).toFixed(2) + ' ' +
|
||
_(units[e]);
|
||
},
|
||
|
||
showInstallCancelDialog: function ai_showInstallCancelDialog(evt) {
|
||
if (evt) {
|
||
evt.preventDefault();
|
||
}
|
||
this.installCancelDialog.classList.add('visible');
|
||
this.dialog.classList.remove('visible');
|
||
},
|
||
|
||
hideInstallCancelDialog: function ai_hideInstallCancelDialog(evt) {
|
||
if (evt) {
|
||
evt.preventDefault();
|
||
}
|
||
this.dialog.classList.add('visible');
|
||
this.installCancelDialog.classList.remove('visible');
|
||
},
|
||
|
||
showDownloadCancelDialog: function ai_showDownloadCancelDialog(e) {
|
||
var currentNode = e.target;
|
||
|
||
if (!currentNode.classList.contains('fake-notification')) {
|
||
// tapped outside of a notification
|
||
return;
|
||
}
|
||
|
||
var manifestURL = currentNode.dataset.manifest,
|
||
app = applications.getByManifestURL(manifestURL),
|
||
manifest = app.manifest || app.updateManifest,
|
||
dialog = this.downloadCancelDialog;
|
||
|
||
var title = dialog.querySelector('h1');
|
||
|
||
navigator.mozL10n.setAttributes(title, 'stopDownloading', {
|
||
app: new ManifestHelper(manifest).displayName
|
||
});
|
||
|
||
dialog.classList.add('visible');
|
||
this.dispatchPromptEvent('shown');
|
||
dialog.dataset.manifest = manifestURL;
|
||
UtilityTray.hide();
|
||
},
|
||
|
||
handleInstallCancel: function ai_handleInstallCancel() {
|
||
if (this.installCancelCallback) {
|
||
this.installCancelCallback();
|
||
}
|
||
this.installCallback = null;
|
||
this.installCancelCallback = null;
|
||
this.installCancelDialog.classList.remove('visible');
|
||
this.dispatchPromptEvent('hidden');
|
||
},
|
||
|
||
handleConfirmDownloadCancel: function ai_handleConfirmDownloadCancel(e) {
|
||
e && e.preventDefault();
|
||
var dialog = this.downloadCancelDialog,
|
||
manifestURL = dialog.dataset.manifest;
|
||
if (manifestURL) {
|
||
var app = applications.getByManifestURL(manifestURL);
|
||
app && app.cancelDownload();
|
||
}
|
||
|
||
this.hideDownloadCancelDialog();
|
||
},
|
||
|
||
handleCancelDownloadCancel: function ai_handleCancelDownloadCancel(e) {
|
||
e && e.preventDefault();
|
||
this.hideDownloadCancelDialog();
|
||
},
|
||
|
||
hideDownloadCancelDialog: function() {
|
||
var dialog = this.downloadCancelDialog;
|
||
dialog.classList.remove('visible');
|
||
this.dispatchPromptEvent('hidden');
|
||
delete dialog.dataset.manifest;
|
||
},
|
||
|
||
dispatchPromptEvent: function(state) {
|
||
window.dispatchEvent(new CustomEvent('installprompt' + state));
|
||
},
|
||
debug: function() {
|
||
if (this.DEBUG) {
|
||
console.log('[' + this.name + ']' +
|
||
'[' + Service.currentTime() + '] ' +
|
||
Array.slice(arguments).concat());
|
||
if (this.TRACE) {
|
||
console.trace();
|
||
}
|
||
}
|
||
}
|
||
};
|
||
exports.AppInstallManager = AppInstallManager;
|
||
}(window));
|