Source: panels/app_permissions_list/app_permissions_list.js

/**
 * Handle app_permissions_list panel's functionality.
 *
 * @module PermissionsList
 */
define(function(require) {
  'use strict';

  var SettingsService = require('modules/settings_service');
  var ManifestHelper = require('shared/manifest_helper');
  var AppsCache = require('modules/apps_cache');
  var mozApps = require('modules/navigator/mozApps');
  var mozPerms = require('modules/navigator/mozPermissionSettings');

  var PermissionsList = function pl() {
    this._listRoot = null;
    this._permissionsTable = null;
    this._permissionTableHaveProcessed = false;
    this._apps = null;
    this._enabled = false;
  };

  PermissionsList.prototype = {
    /**
     * initialization
     *
     * @memberOf PermissionsList
     * @param {HTMLElement} listRoot
     * @access public
     */
    init: function pl_init(listRoot) {
      this._listRoot = listRoot;

      this._boundOnAppChoose = this._onAppChoose.bind(this);
      this._boundOnApplicationInstall = this._onApplicationInstall.bind(this);
      this._boundOnApplicationUninstall =
        this._onApplicationUninstall.bind(this);
    },

    set enabled(value) {
      if (value !== this._enabled) {
        this._enabled = value;
        if (this._enabled) {
          this._bindEvents();
        } else {
          this._unbindEvents();
        }
      }
    },

    _bindEvents: function pl__bindEvents() {
      this._listRoot.addEventListener('click', this._boundOnAppChoose);
      AppsCache.addEventListener('oninstall', this._boundOnApplicationInstall);
      AppsCache.addEventListener('onuninstall',
        this._boundOnApplicationUninstall);
    },

    _unbindEvents: function pl__unbindEvents() {
      this._listRoot.removeEventListener('click', this._boundOnAppChoose);
      AppsCache.removeEventListener('oninstall',
        this._boundOnApplicationInstall);
      AppsCache.removeEventListener('onuninstall',
        this._boundOnApplicationUninstall);
    },

    /**
     * Set /resources/permissions_table.json.
     *
     * @memberOf PermissionsList
     * @param {Object} permissionTable
     * @access public
     */
    setPermissionsTable: function pl_setPermissionsTable(permissionTable) {
      this._permissionTable = permissionTable;
    },

    /**
     * Refresh the app list when we enter into panel.
     *
     * @memberOf PermissionsList
     * @access public
     * @return {Promise}
     */
    refresh: function pl_refresh() {
      var self = this;
      this._apps = [];
      if (this._permissionTableHaveProcessed) {
        return this.loadApps();
      } else {
        return mozApps.getSelf().then(function(app) {
          return self._initExplicitPermissionsTable(app);
        }).then(function() {
          return self.loadApps();
        });
      }
    },

    /**
     * Go to app_permissions_detail panel when user select an app.
     *
     * @memberOf PermissionsList
     * @param {Event} evt
     * @access public
     */
    _onAppChoose: function pl__onAppChoose(evt) {
      if (evt.target.dataset && evt.target.dataset.appIndex) {
        SettingsService.navigate('appPermissions-details', {
          app: this._apps[evt.target.dataset.appIndex],
          permissionsTable: this._permissionTable
        });
      }
    },

    /**
     * When new application is installed, we push the app to list, sort them and
     * rerender the app list.
     *
     * @memberOf PermissionsList
     * @param {Event} evt
     * @access public
     */
    _onApplicationInstall: function pl__onApplicationInstall(evt) {
      var app = evt.application;
      this._apps.push(app);
      this._sortApps();
      this.renderList();
    },

    /**
     * When application is uninstalled, we remove it from list and rerender the
     * app list.
     *
     * @memberOf PermissionsList
     * @param {Event} evt
     * @access public
     */
    _onApplicationUninstall: function pl__onApplicationUninstall(evt) {
      var app;
      var appIndex;
      this._apps.some(function findApp(anApp, index) {
        if (anApp.origin === evt.application.origin) {
          app = anApp;
          appIndex = index;
          return true;
        }
        return false;
      });

      if (!app) {
        return;
      }
      SettingsService.navigate('appPermissions');
      this._apps.splice(appIndex, 1);
      this.renderList();
    },

    /**
     * Sort the applist by the name of its manifest.
     *
     * @memberOf PermissionsList
     * @access private
     */
    _sortApps: function pl__sortApps() {
      this._apps.sort(function alphabeticalSort(app, otherApp) {
        var manifest = new ManifestHelper(app.manifest ?
          app.manifest : app.updateManifest);
        var otherManifest = new ManifestHelper(otherApp.manifest ?
          otherApp.manifest : otherApp.updateManifest);
        return manifest.name > otherManifest.name;
      });
    },

    /**
     * Genrate UI template of app item.
     *
     * @memberOf PermissionsList
     * @access private
     * @param {Object} itemData
     * @return {HTMLDivElement}
     */
    _genAppItemTemplate: function pl__genAppItemTemplate(itemData) {
      var icon = document.createElement('img');
      var item = document.createElement('li');
      var link = document.createElement('a');
      var span = document.createElement('span');
      span.textContent = itemData.name;
      icon.src = itemData.iconSrc;
      link.dataset.appIndex = itemData.index;
      link.href = '#';
      link.classList.add('menu-item');
      link.appendChild(icon);
      link.appendChild(span);
      item.appendChild(link);
      return item;
    },

    /**
     * Genrate UI template of app item.
     *
     * @memberOf PermissionsList
     * @access public
     */
    renderList: function pl_renderList() {
      this._listRoot.innerHTML = '';
      var listFragment = document.createDocumentFragment();
      this._apps.forEach(function appIterator(app, index) {
        var manifest = new ManifestHelper(app.manifest ?
            app.manifest : app.updateManifest);
        var li = this._genAppItemTemplate({
          name: manifest.displayName,
          index: index,
          iconSrc: this._getBestIconURL(app, manifest.icons)
        });
        listFragment.appendChild(li);
      }.bind(this));
      this._listRoot.appendChild(listFragment);
    },

    /**
     * Genrate explicitCertifiedPermissions table from plainPermissions table
     * table and "composedPermissions + accessModes" table.
     *
     * @memberOf PermissionsList
     * @param {Event} evt
     * @access private
     */
    _initExplicitPermissionsTable:
      function pl__initExplicitPermissionsTable(app) {
        this._currentApp = app;

        var table = this._permissionTable;
        table.explicitCertifiedPermissions = [];
        table.plainPermissions.forEach(function plainPermIterator(perm) {
          if (this._isExplicitPerm(perm)) {
            table.explicitCertifiedPermissions.push({
              explicitPermission: perm,
              permission: perm
            });
          }
        }.bind(this));

        table.composedPermissions.forEach(function permIterator(perm) {
          table.accessModes.forEach(function modeIterator(mode) {
            var composedPerm = perm + '-' + mode;
            if (this._isExplicitPerm(composedPerm)) {
              table.explicitCertifiedPermissions.push({
                explicitPermission: composedPerm,
                permission: perm
              });
            }
          }.bind(this));
        }.bind(this));
        this._permissionTableHaveProcessed = true;
    },

    /**
     * Identify the permission whether is explict or not.
     *
     * @memberOf PermissionsList
     * @access private
     * @return {Bool}
     */
    _isExplicitPerm: function pl_isExplicitPerm(perm) {
      return mozPerms.isExplicit(perm, this._currentApp.manifestURL,
                                 this._currentApp.origin, false);
    },

    /**
     * Filter explicit apps from moz apps, sort them, and render to screen.
     *
     * @memberOf PermissionsList
     * @access public
     * @return {Promise}
     */
    loadApps: function pl_loadApps() {
      var self = this;
      return AppsCache.apps().then(function(apps) {
        self._loadApps(apps);
      });
    },

    /**
     * Iterate internal apps and render them on UI.
     *
     * @memberOf PermissionsList
     * @param {Object[]} apps
     * @access private
     */
    _loadApps: function pl__loadApps(apps) {
      var table = this._permissionTable;
      apps.forEach(function(app) {
        var manifest = app.manifest ? app.manifest : app.updateManifest;
        if (manifest.type != 'certified') {
          this._apps.push(app);
          return;
        }

        var display = table.explicitCertifiedPermissions
                           .some(function iterator(perm) {
          var permInfo = mozPerms.get(perm.explicitPermission,
            app.manifestURL, app.origin, false);
          return permInfo != 'unknown';
        }.bind(this));

        if (display) {
          this._apps.push(app);
        }
      }.bind(this));

      this._sortApps();
      this.renderList();
    },

    /**
     * Get icon URL.
     *
     * @memberOf PermissionsList
     * @param {Object} app
     * @param {Object} icons
     * @access private
     */
    _getBestIconURL: function pl__getBestIconURL(app, icons) {
      if (!icons || !Object.keys(icons).length) {
        return '../style/images/default.png';
      }

      // The preferred size is 30 by the default. If we use HDPI device, we may
      // use the image larger than 30 * 1.5 = 45 pixels.
      var preferredIconSize = 30 * (window.devicePixelRatio || 1);
      var preferredSize = Number.MAX_VALUE;
      var max = 0;

      for (var size in icons) {
        size = parseInt(size, 10);
        if (size > max) {
          max = size;
        }

        if (size >= preferredIconSize && size < preferredSize) {
          preferredSize = size;
        }
      }
      // If there is an icon matching the preferred size, we return the result,
      // if there isn't, we will return the maximum available size.
      if (preferredSize === Number.MAX_VALUE) {
        preferredSize = max;
      }

      var url = icons[preferredSize];

      if (url) {
        return !(/^(http|https|data):/.test(url)) ? app.origin + url : url;
      } else {
        return '../style/images/default.png';
      }
    }
  };

  return function ctor_permissions_list() {
    return new PermissionsList();
  };
});