Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/apps/system/js/app_install_manager.js
diff options
context:
space:
mode:
Diffstat (limited to 'apps/system/js/app_install_manager.js')
-rw-r--r--apps/system/js/app_install_manager.js443
1 files changed, 443 insertions, 0 deletions
diff --git a/apps/system/js/app_install_manager.js b/apps/system/js/app_install_manager.js
new file mode 100644
index 0000000..146a01b
--- /dev/null
+++ b/apps/system/js/app_install_manager.js
@@ -0,0 +1,443 @@
+'use strict';
+
+var 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'
+ },
+
+ init: function ai_init() {
+ 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.installCancelDialog =
+ document.getElementById('app-install-cancel-dialog');
+ this.downloadCancelDialog =
+ document.getElementById('app-download-cancel-dialog');
+ this.confirmCancelButton =
+ document.getElementById('app-install-confirm-cancel-button');
+ this.resumeButton = document.getElementById('app-install-resume-button');
+
+ this.notifContainer =
+ document.getElementById('install-manager-notification-container');
+ this.appInfos = {};
+
+ window.addEventListener('mozChromeEvent',
+ (function ai_handleChromeEvent(e) {
+ if (e.detail.type == 'webapps-ask-install') {
+ this.handleAppInstallPrompt(e.detail);
+ }
+ }).bind(this));
+
+ window.addEventListener('applicationinstall',
+ this.handleApplicationInstall.bind(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);
+
+ // 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);
+
+ window.addEventListener('applicationready',
+ this.handleApplicationReady);
+ },
+
+ 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.showInstallSuccess(app);
+ return;
+ }
+
+ this.prepareForDownload(app);
+ },
+
+ handleAppInstallPrompt: function ai_handleInstallPrompt(detail) {
+ var _ = navigator.mozL10n.get;
+ 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');
+
+ var id = detail.id;
+
+ if (manifest.size) {
+ this.size.textContent = this.humanizeSize(manifest.size);
+ } else {
+ this.size.textContent = _('unknown');
+ }
+
+ // Wrap manifest to get localized properties
+ manifest = new ManifestHelper(manifest);
+ var msg = _('install-app', {'name': manifest.name});
+ this.msg.textContent = msg;
+
+ if (manifest.developer) {
+ this.authorName.textContent = manifest.developer.name || _('unknown');
+ this.authorUrl.textContent = manifest.developer.url || '';
+ } else {
+ this.authorName.textContent = _('unknown');
+ this.authorUrl.textContent = '';
+ }
+
+ 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);
+
+ },
+
+ handleInstall: function ai_handleInstall(evt) {
+ if (evt)
+ evt.preventDefault();
+ if (this.installCallback)
+ this.installCallback();
+ this.installCallback = null;
+ this.dialog.classList.remove('visible');
+ },
+
+ 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;
+ },
+
+ showInstallSuccess: function ai_showInstallSuccess(app) {
+ var manifest = app.manifest || app.updateManifest;
+ var name = new ManifestHelper(manifest).name;
+ var _ = navigator.mozL10n.get;
+ var msg = _('app-install-success', { appName: name });
+ SystemBanner.show(msg);
+ },
+
+ handleDownloadSuccess: function ai_handleDownloadSuccess(evt) {
+ var app = evt.application;
+ this.showInstallSuccess(app);
+ this.onDownloadStop(app);
+ this.onDownloadFinish(app);
+ },
+
+ handleDownloadError: function ai_handleDownloadError(evt) {
+ var app = evt.application;
+ var _ = navigator.mozL10n.get;
+ var manifest = app.manifest || app.updateManifest;
+ var name = new ManifestHelper(manifest).name;
+
+ 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';
+ var msg = _('app-install-' + key, { appName: name });
+ SystemBanner.show(msg);
+ }
+
+ this.onDownloadStop(app);
+ },
+
+ onDownloadStart: function ai_onDownloadStart(app) {
+ if (! this.hasNotification(app)) {
+ StatusBar.incSystemDownloads();
+ this.addNotification(app);
+ this.requestWifiLock(app);
+ }
+ },
+
+ onDownloadStop: function ai_onDownloadStop(app) {
+ if (this.hasNotification(app)) {
+ StatusBar.decSystemDownloads();
+ 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,
+ manifest = app.manifest || app.updateManifest,
+ appInfo = this.appInfos[manifestURL];
+
+ if (appInfo.installNotification) {
+ return;
+ }
+
+ var newNotif =
+ '<div class="fake-notification">' +
+ '<div class="message"></div>' +
+ '<progress></progress>' +
+ '</div>';
+
+ this.notifContainer.insertAdjacentHTML('afterbegin', newNotif);
+
+ var newNode = this.notifContainer.firstElementChild;
+ newNode.dataset.manifest = manifestURL;
+
+ var _ = navigator.mozL10n.get;
+
+ var message = _('downloadingAppMessage', {
+ appName: new ManifestHelper(manifest).name
+ });
+
+ newNode.querySelector('.message').textContent = message;
+
+ var size = manifest.size,
+ progressNode = newNode.querySelector('progress');
+ if (size) {
+ progressNode.max = size;
+ appInfo.hasMax = true;
+ }
+
+ appInfo.installNotification = newNode;
+ NotificationScreen.incExternalNotifications();
+ },
+
+ 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;
+ var _ = navigator.mozL10n.get;
+
+ 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 = _('downloadingAppProgressIndeterminate');
+ progressNode.value = undefined; // switch to indeterminate state
+ } else if (appInfo.hasMax) {
+ message = _('downloadingAppProgress',
+ {
+ progress: this.humanizeSize(app.progress),
+ max: this.humanizeSize(progressNode.max)
+ });
+ progressNode.value = app.progress;
+ } else {
+ message = _('downloadingAppProgressNoMax',
+ { progress: this.humanizeSize(app.progress) });
+ }
+ progressNode.textContent = message;
+ },
+
+ 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.decExternalNotifications();
+ },
+
+ 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');
+
+ title.textContent = navigator.mozL10n.get('stopDownloading', {
+ app: new ManifestHelper(manifest).name
+ });
+
+ dialog.classList.add('visible');
+ dialog.dataset.manifest = manifestURL;
+ UtilityTray.hide();
+ },
+
+ handleInstallCancel: function ai_handleInstallCancel() {
+ if (this.installCancelCallback)
+ this.installCancelCallback();
+ this.installCancelCallback = null;
+ this.installCancelDialog.classList.remove('visible');
+ },
+
+ 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');
+ delete dialog.dataset.manifest;
+ }
+};
+
+AppInstallManager.init();