diff --git a/apps/privacy-panel/index.html b/apps/privacy-panel/index.html index b0dfb0cfc99c..282c31954145 100644 --- a/apps/privacy-panel/index.html +++ b/apps/privacy-panel/index.html @@ -25,6 +25,8 @@ + + @@ -39,6 +41,13 @@ + + + + + + + @@ -81,10 +90,15 @@

Privacy Panel

  • - + Remote Privacy Protection
  • +
  • + + Transparency Control + +
  • Guided Tour @@ -92,7 +106,7 @@

    Privacy Panel

  • - +
    @@ -106,6 +120,13 @@

    Privacy Panel

    + +
    +
    +
    +
    +
    +
    @@ -121,6 +142,8 @@

    Privacy Panel

    + + diff --git a/apps/privacy-panel/js/about/main.js b/apps/privacy-panel/js/about/main.js index c8776cc64a04..a62c42799db6 100644 --- a/apps/privacy-panel/js/about/main.js +++ b/apps/privacy-panel/js/about/main.js @@ -14,19 +14,15 @@ function(panels) { var About = { init: function() { - this.panel = document.getElementById('about'); - this.version = this.panel.querySelector('#privacy-panel-version'); - this.build = this.panel.querySelector('#privacy-panel-build'); - - panels.loadJSON('resources/about.json', function(data) { - this.regionsAndCities = data; - - this.version.textContent = data.version; - this.build.textContent = data.build; - }.bind(this)); + var version = document.getElementById('privacy-panel-version'); + var build = document.getElementById('privacy-panel-build'); + panels.loadJSON('resources/about.json', data => { + version.textContent = data.version; + build.textContent = data.build; + }); } + }; return About; - }); diff --git a/apps/privacy-panel/js/ala/app_list.js b/apps/privacy-panel/js/ala/app_list.js deleted file mode 100644 index 0d29ea82df98..000000000000 --- a/apps/privacy-panel/js/ala/app_list.js +++ /dev/null @@ -1,77 +0,0 @@ -/** - * ALA app list module. - * - * @module AppList - * @return {Object} - */ -define([], - -function() { - 'use strict'; - - function AppList() { - this.mozApps = navigator.mozApps; - } - - AppList.prototype = { - - get: function(filter, callback) { - var list = []; - - callback = callback || function() {}; - this.mozApps.mgmt.getAll().onsuccess = function(event) { - var apps = event.target.result; - apps.forEach(function(app) { - var manifest = app.manifest || app.updateManifest; - if (manifest.permissions && manifest.permissions[filter]) { - list.push(app); - } - }); - callback(list); - }; - }, - - icon: function(app) { - var manifest = app.manifest || app.updateManifest; - - if (!manifest.icons || !Object.keys(manifest.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 manifest.icons) { - if (manifest.icons.hasOwnProperty(size)) { - 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 = manifest.icons[preferredSize]; - - if (url) { - return !(/^(http|https|data):/.test(url)) ? app.origin + url : url; - } else { - return '../style/images/default.png'; - } - } - }; - - return new AppList(); - -}); diff --git a/apps/privacy-panel/js/ala/exception.js b/apps/privacy-panel/js/ala/exception.js index e4b948b858f6..809e6ebacc2e 100644 --- a/apps/privacy-panel/js/ala/exception.js +++ b/apps/privacy-panel/js/ala/exception.js @@ -1,19 +1,18 @@ /** * ALA exception panel. - * + * * @module ALAException * @return {Object} */ define([ 'panels', 'ala/blur_slider', - 'ala/app_list', 'ala/exceptions', 'shared/settings_listener', 'shared/settings_helper' ], -function(panels, BlurSlider, appList, alaExceptions, SettingsListener, +function(panels, BlurSlider, alaExceptions, SettingsListener, SettingsHelper) { 'use strict'; @@ -28,7 +27,7 @@ function(panels, BlurSlider, appList, alaExceptions, SettingsListener, /** * Initialize ALA exception panel. - * + * * @method init * @constructor */ diff --git a/apps/privacy-panel/js/ala/exceptions.js b/apps/privacy-panel/js/ala/exceptions.js index e9eeec00abc3..c21411435057 100644 --- a/apps/privacy-panel/js/ala/exceptions.js +++ b/apps/privacy-panel/js/ala/exceptions.js @@ -1,18 +1,17 @@ /** * ALA exceptions panel. - * + * * @module ExceptionsPanel * @return {Object} */ define([ 'panels', 'ala/blur_slider', - 'ala/app_list', 'shared/settings_listener', 'shared/settings_helper' ], -function(panels, BlurSlider, appList, SettingsListener, SettingsHelper) { +function(panels, BlurSlider, SettingsListener, SettingsHelper) { 'use strict'; function ExceptionsPanel() { @@ -24,7 +23,7 @@ function(panels, BlurSlider, appList, SettingsListener, SettingsHelper) { /** * Initialize ALA exceptions panel. - * + * * @method init * @constructor */ @@ -61,8 +60,6 @@ function(panels, BlurSlider, appList, SettingsListener, SettingsHelper) { } // render app list - var manifest, icon, appSettings, type, li; - this.apps.forEach(function(item, index) { // remove Privacy Panel application from list @@ -70,14 +67,9 @@ function(panels, BlurSlider, appList, SettingsListener, SettingsHelper) { return; } - manifest = item.manifest || item.updateManifest; - icon = appList.icon(item); - - type = undefined; - appSettings = this.exceptionsList[item.origin]; - + var type; + var appSettings = this.exceptionsList[item.origin]; if (appSettings) { - type = appSettings.type; switch (appSettings.type) { case 'user-defined': type = 'User defined'; @@ -92,15 +84,16 @@ function(panels, BlurSlider, appList, SettingsListener, SettingsHelper) { type = 'No location'; break; default: + type = appSettings.type; break; } } - li = this.renderAppItem({ + var li = this.renderAppItem({ origin: item.origin, - name: manifest.name, + name: item.name, index: index, - iconSrc: icon, + iconSrc: item.iconURL, type: type }); diff --git a/apps/privacy-panel/js/ala/main.js b/apps/privacy-panel/js/ala/main.js index 47f18a1b2b2a..04f0a3bc4aa2 100644 --- a/apps/privacy-panel/js/ala/main.js +++ b/apps/privacy-panel/js/ala/main.js @@ -1,13 +1,13 @@ /** * ALA main panel. - * + * * @module AlaPanel * @return {Object} */ define([ 'panels', + 'app_list', 'ala/blur_slider', - 'ala/app_list', 'ala/exception', 'ala/exceptions', 'ala/define_custom_location', @@ -15,7 +15,7 @@ define([ 'shared/settings_helper' ], -function(panels, BlurSlider, appList, alaException, alaExceptions, alaDCL, +function(panels, appList, BlurSlider, alaException, alaExceptions, alaDCL, SettingsListener, SettingsHelper) { 'use strict'; @@ -51,11 +51,9 @@ function(panels, BlurSlider, appList, alaException, alaExceptions, alaDCL, this._prepareDCLData(); // prepare app list that uses geolocation - appList.get('geolocation', function(apps) { - - // init alaExceptions module - alaExceptions.init(apps); - }.bind(this)); + appList.init().then(function() { + alaExceptions.init(appList.getFilteredApps('geolocation')); + }); // init alaException module alaException.init(); diff --git a/apps/privacy-panel/js/app.js b/apps/privacy-panel/js/app.js index 04646b036b98..7749ed7aa718 100644 --- a/apps/privacy-panel/js/app.js +++ b/apps/privacy-panel/js/app.js @@ -65,10 +65,11 @@ function(panels, root, about) { require([ 'ala/main', 'rpp/main', + 'tc/main', 'sms/main' ], - function(ala, rpp, commands) { + function(ala, rpp, tc, commands) { // load all templates for location accuracy sections panels.load('ala', function() { ala.init(); @@ -79,6 +80,11 @@ function(panels, root, about) { rpp.init(); }); + // load all templates for transparency control + panels.load('tc', function() { + tc.init(); + }); + commands.init(); }); }); diff --git a/apps/privacy-panel/js/app_list.js b/apps/privacy-panel/js/app_list.js new file mode 100644 index 000000000000..e9175323308b --- /dev/null +++ b/apps/privacy-panel/js/app_list.js @@ -0,0 +1,321 @@ +/** + * App List module. + * + * @module AppList + * @return {Object} + */ +define([], function() { + 'use strict'; + + var _ = navigator.mozL10n.get; + var _lang = navigator.mozL10n.language.code; + + + /** + * Supported application sorting methods: + * by name, by trust level, by developer name. + * Note: sorting by name works for permissions as well. + */ + var _orderBy = { + name: (a, b) => a.name.localeCompare(b.name, _lang), // default + trust: (a, b) => a.trust > b.trust, // 'certified', 'privileged', 'web' + vendor: (a, b) => a.vendor.localeCompare(b.vendor, _lang) + }; + + + /**************************************************************************** + * Applications - private helpers + */ + + var _applications = []; // array of {DOMApplication} representations + var _defaultIconURL = '../style/images/default.png'; + + /** + * Get the list of installed apps. + */ + function _getApplications(onsuccess, onerror) { + onsuccess = typeof onsuccess === 'function' ? onsuccess : function() {}; + onerror = typeof onerror === 'function' ? onerror : function() {}; + + var mozAppsMgmt = navigator.mozApps && navigator.mozApps.mgmt; + if (!mozAppsMgmt) { + console.error('navigator.mozApps.mgmt is undefined'); + onerror(); + return; + } + + var req = mozAppsMgmt.getAll(); + req.onerror = onerror; + req.onsuccess = event => { + _applications = event.target.result.map(_makeAppRepresentation) + .sort(_orderBy.name); + onsuccess(); + }; + } + + /** + * Get the app icon that best suits the device display size. + */ + function _getBestIconURL(app) { + var icons = (app.manifest || app.updateManifest).icons; + if (!icons || !Object.keys(icons).length) { + return _defaultIconURL; + } + + // The preferred size is 30 pixels by default. + // On an HDPI device, we may use a larger size 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 _defaultIconURL; + } + return !(/^(http|https|data):/.test(url)) ? app.origin + url : url; + } + + /** + * Create a representation of a {DOMApplication} instance. + * .name: localized name + * .trust: trust level (= certified, privileged, web) + * .vendor: developer name + * .iconURL: URL of the best icon for the current display + * .permissions: filtered list of permissions that are actually used + * .manifest: application manifest + * .origin: application origin + */ + function _makeAppRepresentation(app) { + var manifest = app.manifest || app.updateManifest || {}; + + var trust = 'web'; + if (manifest.type === 'certified' || manifest.type === 'privileged') { + trust = manifest.type; + } + + var name = manifest.name; + if (manifest.locales && + manifest.locales[_lang] && + manifest.locales[_lang].name) { + name = manifest.locales[_lang].name; + } + + var vendor = ''; + if (manifest.developer && manifest.developer.name) { + vendor = manifest.developer.name; + } + + return { + name: name, + trust: trust, + vendor: vendor, + get iconURL() { return _getBestIconURL(app); }, + get permissions() { return _getPermissions(app); }, + manifest: manifest, + origin: app.origin + }; + } + + + /**************************************************************************** + * Permissions - private helpers + */ + + var _showAllPermissions = false; + var _permTable = { // will be fetched from /resources/permissions_table.json + plainPermissions: [], + composedPermissions: [], + accessModes: [] + }; + + /** + * Get a localized name & description for the given permission key. + */ + function _localizePermission(permKey) { + var l10nKey = 'perm-' + permKey.replace(':', '-'); + return { + key: permKey, + name: _(l10nKey) || permKey, + desc: _(l10nKey + '-description') || '' + }; + } + + /** + * Get an array of app permissions. + * + * Rather than using the declared permission list in the manifest, + * check that each permission is valid and really used by the app. + * + * Each permission is an object with the following properties: + * .key: permission key. + * .value: permission value ('deny', 'ask', 'grant'). + * .access: access mode ('readonly', 'readwrite', etc.). + * .name: localized name (human-readable key). + * .desc: localized description. + * .explicit: true if the permission value can be changed by the user; + * false otherwise (i.e. internal/certified app). + */ + function _getPermissions(app) { + var permissions = []; + + var mozPerms = navigator.mozPermissionSettings; + if (!mozPerms) { + console.error('navigator.mozPermissionSettings is undefined'); + return permissions; + } + + function pushIfValid(permKey, accessMode) { + var key = accessMode ? permKey + '-' + accessMode : permKey; + var value = mozPerms.get(key, app.manifestURL, app.origin, false); + if (value && value !== 'unknown') { + var perm = _localizePermission(permKey); + perm.value = value; + perm.explicit = + mozPerms.isExplicit(key, app.manifestURL, app.origin, false); + permissions.push(perm); + return true; // valid + } + return false; // not valid + } + + if (_showAllPermissions) { // check all permissions listed in the manifest + var manifest = app.manifest || app.updateManifest; + if (manifest && manifest.permissions) { + for (var perm in manifest.permissions) { + var access = manifest.permissions[perm].access; + if (access) { + pushIfValid(perm, 'read'); // XXX + } else { + pushIfValid(perm); + } + } + } + } else { // only check permissions listed in _permTable + // Note: this is the behavior of the Settings/Apps panel + _permTable.plainPermissions.forEach(key => pushIfValid(key)); + _permTable.composedPermissions.forEach(key => + _permTable.accessModes.some(mode => pushIfValid(key, mode))); + } + + return permissions.sort(_orderBy.name); + } + + + /**************************************************************************** + * Public API + */ + + /** + * AppList + * + * @constructor + */ + function AppList() {} + + AppList.prototype = { + + /** + * Initialize the AppList. + * + * @method init + * @param {Object} permissionTable [optional] + * @return {Promise} + */ + init: function init(permissionTable) { + if (permissionTable) { + _permTable = permissionTable; + } + return new Promise(function(resolve, reject) { + if (_applications.length) { // already initialized + resolve(); + } else { + _getApplications(resolve, reject); + window.addEventListener('applicationinstall', _getApplications); + window.addEventListener('applicationuninstall', _getApplications); + } + }); + }, + + /** + * List of supported permissions. + * + * @property permissions + * @return {Array} Array of supported permissions + */ + get permissions() { + return _permTable.plainPermissions + .concat(_permTable.composedPermissions) + .map(_localizePermission) + .sort(_orderBy.name); + }, + + /** + * List of installed applications. + * + * @property applications + * @return {Array} Array of extended {DOMApplication} objects + */ + get applications() { + return _applications; + }, + + /** + * List of installed applications using a specific permission. + * + * @method getFilteredApps + * @param {String} filter Permission to match + * @return {Array} + */ + getFilteredApps: function getFilteredApps(filter) { + return _applications.filter(app => app.manifest.permissions && + filter in app.manifest.permissions); + }, + + /** + * List of installed applications grouped by name, trust level or vendor. + * + * @method getSortedApps + * @param {String} sortKey Either 'name', 'trust' or 'vendor' + * @return {Object} + */ + getSortedApps: function getSortedApps(sortKey) { + var sorted = {}; + if (!(sortKey in _orderBy)) { + throw 'unsupported application sort key: "' + sortKey + '"'; + } + + _applications.forEach(app => { + var header = app[sortKey]; + if (!(header in sorted)) { + sorted[header] = []; + } + sorted[header].push(app); + }); + + for (var header in sorted) { + sorted[header].sort(_orderBy.name); + } + + return sorted; + } + + }; + + return new AppList(); +}); diff --git a/apps/privacy-panel/js/panels.js b/apps/privacy-panel/js/panels.js index 518a887c56a0..9501b912d4a9 100644 --- a/apps/privacy-panel/js/panels.js +++ b/apps/privacy-panel/js/panels.js @@ -1,6 +1,6 @@ /** * Handles panels. - * + * * @module PanelController * @return {Object} */ @@ -18,7 +18,7 @@ function(lazyLoader, SettingsListener, SettingsHelper) { PanelController.prototype = { /** - * Load needed templates + * Load needed templates. * * @method load * @param {Array} sections @@ -160,7 +160,7 @@ function(lazyLoader, SettingsListener, SettingsHelper) { * Show section * * @private - * @mrthod showSection + * @method showSection * @param element * @param {Boolean} back */ diff --git a/apps/privacy-panel/js/tc/app_details.js b/apps/privacy-panel/js/tc/app_details.js new file mode 100644 index 000000000000..fe3e90748c2b --- /dev/null +++ b/apps/privacy-panel/js/tc/app_details.js @@ -0,0 +1,136 @@ +/** + * App Details panel: list all permissions for the current application. + * + * @module TcAppDetailsPanel + * @return {Object} + */ + +define([], function() { + 'use strict'; + + var _debug = false; // display the manifest 'permissions' object + + var _panel = null; + var _explicitPermContainer = null; + var _implicitPermContainer = null; + + var _currentApp = null; + + + /** + * Helper object for the app_permissions subpanel. + * + * @constructor + */ + function TcAppDetailsPanel() {} + + TcAppDetailsPanel.prototype = { + + /** + * Initialize the App Permissions panel. + * + * @method init + */ + init: function init(permissionTable) { + _panel = document.getElementById('tc-appDetails'); + _panel.addEventListener('pagerendered', event => + this.renderAppDetails(event.detail)); + + _explicitPermContainer = document.getElementById('tc-perm-explicit'); + _implicitPermContainer = document.getElementById('tc-perm-implicit'); + + // re-order the permission list + window.addEventListener('localized', function tcAppPanelLangChange() { + this.renderPermDetails(_currentApp); + }.bind(this)); + + // in case some explicit permissions have been changed in the Settings app + window.addEventListener('visibilitychange', function tcAppPanelVis() { + if (!document.hidden) { + this.renderAppDetails(_currentApp); + } + }.bind(this)); + }, + + /** + * Render the App Permissions panel. + * + * @method renderAppDetails + * @param {DOMApplication} app + */ + renderAppDetails: function renderAppDetails(app) { + if (!app) { + return; + } + + _currentApp = app; // in case we need to refresh this panel + _panel.querySelector('h1').textContent = app.name; + + if (_debug) { + _panel.querySelector('.debug').hidden = false; + _panel.querySelector('.debug pre').textContent = + ' origin: ' + app.origin + '\n' + + JSON.stringify(app.manifest.permissions, null, 4); + } + + var appInfo = _panel.querySelector('.app-info a'); + appInfo.querySelector('img').src = app.iconURL; + appInfo.querySelector('span').textContent = app.name; + + var explicit = []; + var implicit = []; + app.permissions.forEach(perm => { + if (perm.explicit) { + explicit.push(perm); + } else { + implicit.push(perm); + } + }); + this._showPermissionList(_explicitPermContainer, explicit); + this._showPermissionList(_implicitPermContainer, implicit); + }, + + _showPermissionList: function _showPermissionList(container, permissions) { + container.hidden = true; + if (!permissions.length) { + return; + } + + var list = container.querySelector('.permission-list'); + list.innerHTML = ''; + + permissions.forEach(perm => { + var item = document.createElement('li'); + var link = document.createElement('a'); + var name = document.createElement('span'); + name.textContent = perm.name; + link.appendChild(name); + + // Note: the value is always 'allow' for non-explicit permissions + if (perm.explicit) { + var value = document.createElement('span'); + value.setAttribute('data-l10n-id', 'tc-explicit-' + perm.value); + link.appendChild(value); + } + + item.classList.add('perm-info'); + item.dataset.key = perm.key; // Marionette hook + item.appendChild(link); + + if (perm.desc) { + var desc = document.createElement('p'); + desc.classList.add('description'); + desc.textContent = perm.desc; + item.appendChild(desc); + } + + list.appendChild(item); + }); + + container.hidden = false; + }, + + }; + + return new TcAppDetailsPanel(); +}); diff --git a/apps/privacy-panel/js/tc/applications.js b/apps/privacy-panel/js/tc/applications.js new file mode 100644 index 000000000000..a05ca3185740 --- /dev/null +++ b/apps/privacy-panel/js/tc/applications.js @@ -0,0 +1,131 @@ +/** + * Transparency Control -- Application List panel. + * + * @module TcApplicationsPanel + * @return {Object} + */ +define([ + 'panels', + 'app_list', + 'tc/app_details' +], + +function(panels, appList, appDetails) { + 'use strict'; + + var _appListContainer; + + /** + * TC-Applications panel + * + * @constructor + */ + function TcApplicationsPanel() {} + + TcApplicationsPanel.prototype = { + + /** + * Initialize the Applications panel and its subpanel + * + * @method init + * @param {Object} permissionTable List of supported permissions. + */ + init: function init(permissionTable) { + _appListContainer = document.getElementById('tc-appList'); + var sortKeySelect = document.getElementById('tc-sortKey'); + + var refreshAppList = function refreshAppList() { + this.renderAppList(sortKeySelect.value); + }.bind(this); + sortKeySelect.addEventListener('change', refreshAppList); + window.addEventListener('applicationinstall', refreshAppList); + window.addEventListener('applicationuninstall', refreshAppList); + + // some apps might have a localized name in their manifest + window.addEventListener('localized', refreshAppList); + + appList.init(permissionTable).then(this.renderAppList.bind(this), + error => console.error(error)); + + appDetails.init(); + }, + + /** + * Render the Applications panel. + * + * @method renderAppList + * @param {String} sortKey [optional] Either 'name', 'trust', 'vendor'. + */ + renderAppList: function renderAppList(sortKey) { + this._clear(); + if (!sortKey || sortKey === 'name') { + // apps are already sorted by name, just display them + this._showAppList(appList.applications); + } + else { + var apps = appList.getSortedApps(sortKey); + // sorting by headers work because the sort key is either: + // - a "vendor" name, in which case it makes sense to sort by name + // - 'certified|privileged|web', which luckily matches the order we want + Object.keys(apps).sort().forEach(header => { + var l10nPrefix = (sortKey === 'trust') ? 'tc-trust-' : ''; + this._showAppSeparator(header, l10nPrefix); + this._showAppList(apps[header], header); + }); + } + }, + + _clear: function _clear() { + _appListContainer.innerHTML = ''; + }, + + _showAppSeparator: function _showAppSeparator(separator, l10nPrefix) { + if (!separator) { + return; + } + var header = document.createElement('header'); + var title = document.createElement('h2'); + if (l10nPrefix) { + title.setAttribute('data-l10n-id', l10nPrefix + separator); + } else { // vendor names don't need any localization + title.textContent = separator; + } + header.appendChild(title); + _appListContainer.appendChild(header); + }, + + _showAppList: function _showAppList(apps, groupKey) { + var list = document.createElement('ul'); + if (groupKey) { + list.dataset.key = groupKey; // Marionette key + } + + apps.forEach(app => { + var item = document.createElement('li'); + var link = document.createElement('a'); + var icon = document.createElement('img'); + var name = document.createElement('span'); + + icon.src = app.iconURL; + name.textContent = app.name; + link.classList.add('menu-item'); + link.appendChild(icon); + link.appendChild(name); + link.addEventListener('click', function showAppDetails() { + panels.show({ id: 'tc-appDetails', options: app }); + }); + + item.classList.add('app-element'); + item.dataset.key = app.name; // Marionette hook + item.appendChild(link); + + list.appendChild(item); + }); + + _appListContainer.appendChild(list); + } + + }; + + return new TcApplicationsPanel(); +}); diff --git a/apps/privacy-panel/js/tc/main.js b/apps/privacy-panel/js/tc/main.js new file mode 100644 index 000000000000..edbf5218219f --- /dev/null +++ b/apps/privacy-panel/js/tc/main.js @@ -0,0 +1,40 @@ +/** + * Transparency Control panel. + * + * @module TcPanel + * @return {Object} + */ +define([ + 'panels', + 'tc/applications', + 'tc/permissions' +], + +function(panels, applicationsPanel, permissionsPanel) { + 'use strict'; + + /** + * Transparency Control panel. + * + * @constructor + */ + function TcPanel() {} + + TcPanel.prototype = { + + /** + * Initialize the Transparency Control panel and its sub-panels. + * + * @method init + */ + init: function init() { + panels.loadJSON('resources/permissions_table.json', data => { + applicationsPanel.init(data); + permissionsPanel.init(data); + }); + } + + }; + + return new TcPanel(); +}); diff --git a/apps/privacy-panel/js/tc/perm_details.js b/apps/privacy-panel/js/tc/perm_details.js new file mode 100644 index 000000000000..364d5fe7c02d --- /dev/null +++ b/apps/privacy-panel/js/tc/perm_details.js @@ -0,0 +1,98 @@ +/** + * Permission Details panel: list all applications for the current permission. + * + * @module TcPermDetailsPanel + * @return {Object} + */ +define(['app_list'], function(appList) { + 'use strict'; + + var _panel = null; + var _permInfo = null; + var _permApps = null; + var _permGroup = null; + + var _currentPerm = null; + + /** + * Helper object for the perm_applications subpanel. + * + * @constructor + */ + function TcPermDetailsPanel() {} + + TcPermDetailsPanel.prototype = { + + /** + * Initialize the Permission Details panel. + * + * @method init + */ + init: function init() { + _panel = document.getElementById('tc-permDetails'); + _panel.addEventListener('pagerendered', + event => this.renderPermDetails(event.detail)); + + _permInfo = _panel.querySelector('.perm-info'); + _permApps = _panel.querySelector('.app-list'); + _permGroup = _panel.querySelector('.permission-group'); + + window.addEventListener('localized', function tcPermDetailsLangChange() { + this.renderPermDetails(_currentPerm); + }.bind(this)); + + // in case some explicit permissions have been changed in the Settings app + window.addEventListener('visibilitychange', function tcPermDetailsVis() { + if (!document.hidden) { + this.renderPermDetails(_currentPerm); + } + }.bind(this)); + }, + + /** + * Render the Permission Details panel. + * + * @method renderPermDetails + * @param {Object} perm + */ + renderPermDetails: function renderPermDetails(perm) { + if (!perm) { + return; + } + + _currentPerm = perm; // in case we need to refresh this panel + _panel.querySelector('h1').textContent = perm.name; + + _permInfo.querySelector('span').textContent = perm.name; + _permInfo.querySelector('p').textContent = perm.desc; + + var apps = appList.getFilteredApps(perm.key); + _permGroup.hidden = !apps.length; + + _permApps.innerHTML = ''; + apps.forEach(app => { + var item = document.createElement('li'); + var link = document.createElement('a'); + var icon = document.createElement('img'); + var name = document.createElement('span'); + + icon.src = app.iconURL; + name.textContent = app.name; + + link.classList.add('menu-item'); + link.appendChild(icon); + link.appendChild(name); + + item.classList.add('app-element'); + item.classList.add('app-info'); // hide the menu arrow + item.dataset.key = app.name; // Marionette hook + item.appendChild(link); + + _permApps.appendChild(item); + }); + } + + }; + + return new TcPermDetailsPanel(); +}); diff --git a/apps/privacy-panel/js/tc/permissions.js b/apps/privacy-panel/js/tc/permissions.js new file mode 100644 index 000000000000..987ed1ca2975 --- /dev/null +++ b/apps/privacy-panel/js/tc/permissions.js @@ -0,0 +1,87 @@ +/** + * Transparency Control -- Permissions List panel. + * + * @module TcPermissionsPanel + * @return {Object} + */ +define([ + 'panels', + 'app_list', + 'tc/perm_details' +], + +function(panels, appList, permDetails) { + 'use strict'; + + var _permListContainer; + + /** + * TC-Permissions panel + * + * @constructor + */ + function TcPermissionsPanel() {} + + TcPermissionsPanel.prototype = { + + /** + * Initialize the Permissions panel and its subpanel + * + * @method init + * @param {Object} permissionTable List of supported permissions. + */ + init: function init(permissionTable) { + _permListContainer = document.getElementById('tc-permList'); + + appList.init(permissionTable).then(this.renderPermissionList.bind(this), + error => console.error(error)); + + permDetails.init(); + + // in case some explicit permissions have been changed in the Settings app + window.addEventListener('visibilitychange', function tcPermPanelVis() { + if (!document.hidden) { + this.renderPermissionList(); + } + }.bind(this)); + + // when the language is changed, permissions must be re-ordered + window.addEventListener('localized', + this.renderPermissionList.bind(this)); + }, + + /** + * Render the Permissions panel. + * + * @method renderAppList + * @param {String} sortKey [optional] Either 'name', 'trust', 'vendor'. + */ + renderPermissionList: function renderPermissionList() { + _permListContainer.innerHTML = ''; + + var list = document.createElement('ul'); + appList.permissions.forEach(perm => { + var item = document.createElement('li'); + var link = document.createElement('a'); + var name = document.createElement('span'); + + name.textContent = perm.name; + link.dataset.key = perm.key; // easy Marionette hook + link.appendChild(name); + link.classList.add('menu-item'); + link.classList.add('panel-link'); + link.addEventListener('click', function showAppDetails() { + panels.show({ id: 'tc-permDetails', options: perm }); + }); + + item.appendChild(link); + list.appendChild(item); + }); + + _permListContainer.appendChild(list); + } + + }; + + return new TcPermissionsPanel(); +}); diff --git a/apps/privacy-panel/locales/permissions.en-US.properties b/apps/privacy-panel/locales/permissions.en-US.properties new file mode 100644 index 000000000000..525c10e7c8c8 --- /dev/null +++ b/apps/privacy-panel/locales/permissions.en-US.properties @@ -0,0 +1,93 @@ +# https://developer.mozilla.org/en-US/Apps/Build/App_permissions + +# Explicit permissions (hosted and privileged applications) +perm-alarms-description = Schedule a notification, or schedule an application to be started. +perm-audio-capture-description = Access audio input devices, e.g. microphone. +perm-audio-channel-alarm-description = Play clock alarms and calendar alarms. +perm-audio-channel-content-description = Play music and video. +perm-audio-channel-normal-description = Play UI sounds, Web content, music, radio. +perm-audio-channel-notification-description = Play notification message sounds. +perm-browser-description = Enables the app to implement a browser in an iframe. +perm-camera-description = Take photos, shoot video, record audio, and control the camera. + +perm-contacts-description = Add, read, or modify contacts from the address book on the device and read contacts from the SIM. +perm-contacts-readonly-description = Read contacts from the address book on the device and read contacts from the SIM. +perm-contacts-readwrite-description = Read or modify contacts from the address book on the device and read contacts from the SIM. +perm-contacts-readcreate-description = Add, read, or modify contacts from the address book on the device and read contacts from the SIM. +perm-contacts-createonly-description = Add contacts to the address book on the device. + +perm-desktop-notification-description = Display a notification on the user’s device. + +perm-device-storage-music-description = Add, read or modify music files stored on the device. +perm-device-storage-music-readonly-description = Read music files stored on the device. +perm-device-storage-music-readwrite-description = Read or modify music files stored on the device. +perm-device-storage-music-readcreate-description = Add, read or modify music files stored on the device. +perm-device-storage-music-createonly-description = Store new music files on the device. + +perm-device-storage-pictures-description = Add, read or modify picture files stored on the device. +perm-device-storage-pictures-readonly-description = Read picture files stored on the device. +perm-device-storage-pictures-readwrite-description = Read or modify picture files stored on the device. +perm-device-storage-pictures-readcreate-description = Add, read or modify picture files stored on the device. +perm-device-storage-pictures-createonly-description = Store new picture files on the device. + +perm-device-storage-sdcard-description = Add, read or modify files stored on the device’s SD card. +perm-device-storage-sdcard-readonly-description = Read files stored on the device’s SD card. +perm-device-storage-sdcard-readwrite-description = Read or modify files stored on the device’s SD card. +perm-device-storage-sdcard-readcreate-description = Add, read or modify files stored on the device’s SD card. +perm-device-storage-sdcard-createonly-description = Store new files on the device’s SD card. + +perm-device-storage-videos-description = Add, read or modify video files stored on the device. +perm-device-storage-videos-readonly-description = Read video files stored on the device. +perm-device-storage-videos-readwrite-description = Read or modify video files stored on the device. +perm-device-storage-videos-readcreate-description = Add, read or modify video files stored on the device. +perm-device-storage-videos-createonly-description = Store new video files on the device. + +perm-fmradio-description = Control the FM radio. +perm-geolocation-description = Obtain the current location of the user. +perm-input-description = Allow the app to act as a virtual keyboard by listening to focus change events in other apps. +perm-mobilenetwork-description = Obtain mobile network information (MCC, MNC, etc.). +perm-push-description = Enable an app to wake up to receive notification. +perm-storage-description = Utilize storage without size limitations. +perm-systemXHR-description = Allow anonymous cross-origin XHR without the target site having CORS enabled. +perm-tcp-socket-description = Create TCP sockets and communicate over them. +perm-video-capture-description = Access video input devices, e.g. camera. + +# Implicit permissions (certified/internal applications) +perm-attention-description = Allow content to open a window in front of all other content. +perm-audio-channel-ringer-description = Play sounds for incoming phone calls. +perm-audio-channel-telephony-description = Play sounds for phone calls, VoIP calls. +perm-audio-channel-publicnotification-description = Play public notification sounds. +perm-background-sensors-description = Listen to proximity sensor events in the background. +perm-backgroundservice-description = Enable an app to run in the background. +perm-bluetooth-description = Low level access to Bluetooth hardware. +perm-cellbroadcast-description = Fires an event when a specific type of cell network message is received (an emergency network notification). + +perm-device-storage-apps-description = Add, read, or modify files stored in the apps location on the device. +perm-device-storage-apps-readonly-description = Read files stored in the apps location on the device. +perm-device-storage-apps-readwrite-description = Read, or modify files stored in the apps location on the device. +perm-device-storage-apps-readcreate-description = Add, read, or modify files stored in the apps location on the device. +perm-device-storage-apps-createonly-description = Store new files in the apps location on the device. + +perm-embed-apps-description = Ability to embed apps in mozApp frames. +perm-idle-description = Notify the app if the user is idle. +perm-mobileconnection-description = Obtain information about the current mobile voice and data connection. +perm-network-events-description = Monitor network uploads and downloads. +perm-networkstats-manage-description = Obtain statistics of data usage per interface. +perm-open-remote-window-description = Open windows in a new process. +perm-permissions-description = Allow an app to manage permissions of other apps. +perm-power-description = Turn the screen on or off, control CPU, device power, etc. + +perm-settings-description = Configure or read device settings. +perm-settings-readonly-description = Read device settings. +perm-settings-readwrite-description = Read or modify device settings. +perm-settings-readcreate-description = Add, read or modify device settings. +perm-settings-createonly-description = Add device settings. + +perm-sms-description = Send and receive SMS messages. +perm-telephony-description = Access all telephony-related APIs to make and receive phone calls. +perm-time-description = Set current time. +perm-voicemail-description = Access voicemail. +perm-webapps-manage-description = Manage installed Open Web Apps. +perm-wifi-manage-description = Enumerate available Wi-Fi networks, get signal strength, connect to a network. +perm-wappush-description = Receive WAP Push messages. + diff --git a/apps/privacy-panel/locales/privacypanel.en-US.properties b/apps/privacy-panel/locales/privacypanel.en-US.properties index 2421d705202f..86ca3f3d29f0 100644 --- a/apps/privacy-panel/locales/privacypanel.en-US.properties +++ b/apps/privacy-panel/locales/privacypanel.en-US.properties @@ -2,6 +2,7 @@ privacy-panel = Privacy Panel location-accuracy = Location Accuracy remote-privacy-protection = Remote Privacy Protection +transparency-control = Transparency Control guided-tour = Guided Tour @@ -20,7 +21,7 @@ app-list-description = This list shows all the apps, which are allowed to use yo # ALA-5 (ALA: No Custom Location Alert) attention = Attention! -ala-custom-location-alert = You haven't set a custom location yet. Please set this before working on other settings! +ala-custom-location-alert = You haven’t set a custom location yet. Please set this before working on other settings! # ALA-6 (ALA: Custom Location) @@ -130,14 +131,41 @@ passcode-doesnt-match = Passcode doesn’t match. Try again. +# TC-1 (Transparency Control) +tc-panel = Transparency Control +tc-applications = Applications +tc-permissions = Permissions + + +# TC-2 (TC: Applications) +tc-sort-alphabetical = Alphabetical +tc-sort-trustLevel = Trust Level +tc-sort-vendor = Vendor +# LOCALIZATION NOTE: “certified” is a technical term, for the end-user “internal” should be used instead +tc-trust-certified = Internal Apps +tc-trust-privileged = Privileged Apps +tc-trust-web = Web Apps + + +# TC-3 (TC: Permissions) +# LOCALIZATION NOTE: to the user, the only meaningful permissions are the “explicit” ones; hence, we only mention “implicit” for the implicit/internal permissions. +tc-explicit-permissions = Permissions +tc-implicit-permissions = Implicit Permissions +tc-explicit-prompt = Ask +tc-explicit-deny = Deny +tc-explicit-allow = Grant +tc-apps-accessing-permission = Apps accessing this permission + + + # GT-1 (Guided Tour intro- screen) gt-main-header = Welcome to the Privacy Panel! -gt-main-desc = This app will help you enhance your privacy protection and enables you to lock or find your phone if it's lost. Just have a closer look. It takes only 2 minutes! +gt-main-desc = This app will help you enhance your privacy protection and enables you to lock or find your phone if it’s lost. Just have a closer look. It takes only 2 minutes! # GT-2 (GT: ALA intro screen) gt-ala-explain-header = What is Location Adjustment good for? -gt-ala-explain-desc = Many apps access your geolocation, like the addressbook or the camera. If you don't want to disclose your exact position, you can adjust the accuracy of your current location that is used by apps, set a custom location or hide your geolocation. +gt-ala-explain-desc = Many apps access your geolocation, like the addressbook or the camera. If you don’t want to disclose your exact position, you can adjust the accuracy of your current location that is used by apps, set a custom location or hide your geolocation. # GT-3 (GT: ALA general settings screen ) @@ -162,7 +190,7 @@ gt-rpp-explain-desc = If your phone is stolen or lost, you can use the Remote Pr # GT-7 (GT: RPP passphrase) gt-rpp-passphrase-header = How to set your personal passphrase? -gt-rpp-passphrase-desc = Tap "Remote Privacy Protection" and set your passphrase to secure your remote features. If you have forgotten your actual passphrase you can reset it using your SIM PIN or lockscreen passcode. +gt-rpp-passphrase-desc = Tap “Remote Privacy Protection” and set your passphrase to secure your remote features. If you have forgotten your actual passphrase you can reset it using your SIM PIN or lockscreen passcode. # GT-8 (GT: RPP locate phone) @@ -206,7 +234,7 @@ about-privacy-panel = About Privacy Panel version = Version build-id = Build-ID about-header = About the Privacy Panel -about-description = Together with Deutsche Telecom the Mozilla Foundation developed the Privacy Panel to enable the user to take back control of the personal data ... +about-description = Together with Deutsche Telecom the Mozilla Foundation developed the Privacy Panel to enable the user to take back control of the personal data… diff --git a/apps/privacy-panel/locales/privacypanel.fr.properties b/apps/privacy-panel/locales/privacypanel.fr.properties new file mode 100644 index 000000000000..c6900f4e4f8a --- /dev/null +++ b/apps/privacy-panel/locales/privacypanel.fr.properties @@ -0,0 +1,241 @@ +# Main Page +privacy-panel = Privacy Panel +location-accuracy = Location Accuracy +remote-privacy-protection = Remote Privacy Protection +transparency-control = Transparency Control +guided-tour = Guided Tour + + +# ALA-3 (ALA: Adjust Location Accuracy) +use-geolocation = Use geolocation +use-location-adjustment = Use location adjustment +location-adjustment-information = Remember: Location Adjustment influences the location Firefox OS provides to apps. It will not affect your IP-address or locale settings, so some services might still be able to locate you. +location-adjustment-description = Adjust the global accuracy level of your current location for all apps, define and set a permanent fixed location and add exceptions for single apps. +adjustment = Adjustment +add-exceptions = Add exceptions + + +# ALA-4 (ALA: Exception List) +app-list-description = This list shows all the apps, which are allowed to use your location. + + +# ALA-5 (ALA: No Custom Location Alert) +attention = Attention! +ala-custom-location-alert = You haven’t set a custom location yet. Please set this before working on other settings! + + +# ALA-6 (ALA: Custom Location) +custom-location = Custom Location +set-custom-location = Set custom location +custom-location-description = Set a custom location which is used as a fixed position. + + +# ALA-7 (ALA: Precise Location) +precise = Precise + + +# ALA-8 (ALA: No Location) +no-location = No Location + + +# ALA-9 (ALA: Exception App) +exceptions-application-description = Adjust the custom location accuracy level of the following app: + + +# ALA-10 (ALA: Define Custom Location) +define-custom-location = Define custom location +choose-a-region-city = Choose a region/city +region = Region +city = City +enter-gps-coordinates = Enter GPS Coordinates +enter-gps-description = Please enter the GPS coordinates for longitude from -180.0 to 180 and latitude from -90.0 to 90.0 degree with a dot to separate full degrees lower values. +latitude = Latitude +longitude = Longitude + + +# ALA-13 (ALA: System Default) +global-settings = Global Settings + + + +# RPP-1 (RPP: Register) +rpp-panel = Remote Privacy Protection +rpp-new-password-description = Please set a passphrase to secure your remote features (maximum 100 characters). +new-passphrase.placeholder = New passphrase +confirm-new-passphrase.placeholder = Confirm new passphrase + + +# RPP-2 (RPP: Login) +rpp-login-description = Please enter your passphrase. +rpp-change-password = Forgot/Change your passphrase? +passphrase.placeholder = Passphrase + + +# RPP-3 (RPP: Reset/Change PassPhrase) +rpp-change-password-description = To reset your passphrase please enter Passcode lock/SIM PIN and new passphrase. +sim1 = SIM 1 +sim2 = SIM 2 +enter-sim1.placeholder = Enter SIM 1 Pin Code +enter-sim2.placeholder = Enter SIM 2 Pin Code +enter-passcode.placeholder = Enter PassCode + + +# RPP-1, RPP-2, RPP-3 (RPP: Validation) +passphrase-wrong = Passphrase is wrong! +passphrase-empty = Passphrase is empty! +passphrase-too-long = Passphrase is too long! +passphrase-invalid = You cannot use special characters! +passphrase-different = Confirmation must match passphrase! +pin-empty = Passcode lock is empty! +pin-invalid = Wrong Passcode lock! +pin-different = Wrong Passcode lock! +sim-invalid = Wrong SIM PIN! + + +# RPP-4 (RPP: Features) +remote-locate = Remote Locate +remote-locate-desc = You can locate this device via SMS as following: “RPP locate YOURPASSPHRASE” +remote-ring = Remote Ring +remote-ring-desc = You can ring this device via SMS as following: “RPP ring YOURPASSPHRASE” +remote-lock = Remote Lock +remote-lock-desc = You can lock this device via SMS as following: “RPP lock YOURPASSPHRASE” + + +# RPP-5 (RPP: No LockScreen Alert) +rpp-lockscreen-alert = Please activate lockscreen AND passcode in the system settings to use the Remote Privacy Protection. + + +# RPP-7 (RPP: Screen Lock) +lock-screen = Screen Lock +screen-lock-header = Screen Lock +passcode-lock = Passcode Lock +require-passcode = Require passcode +immediately = Immediately +after-one-minute = After 1 minute +after-five-minutes = After 5 minutes +after-fifteen-minutes = After 15 minutes +after-thirty-minutes = After 30 minutes +after-one-hour = After 1 hour +change-passcode = Change passcode +change = Change +create = Create +passcode-heading = Passcode +current-passcode = Current passcode +new-passcode = New passcode +enter-passcode = Enter passcode +create-a-passcode = Create a Passcode +passcode = Passcode +confirm-passcode = Confirm Passcode +incorrect-passcode = Passcode is incorrect +passcode-doesnt-match = Passcode doesn’t match. Try again. + + + +# TC-1 (Transparency Control) +tc-panel = TrAnSpArEnCy CoNtRoL +tc-applications = ApPlIcAtIoNs +tc-permissions = PeRmIsSiOnS + + +# TC-2 (TC: Applications) +tc-sort-alphabetical = Alphabetique +tc-sort-trustLevel = Niveau de confiance +tc-sort-vendor = Éditeur +# LOCALIZATION NOTE: “certified” is a technical term, for the end-user “internal” should be used instead +tc-trust-certified = Apps internes +tc-trust-privileged = Apps privilégiées +tc-trust-web = Applications Web + + +# TC-3 (TC: Permissions) +# LOCALIZATION NOTE: to the user, the only meaningful permissions are the “explicit” ones; hence, we only mention “implicit” for the implicit/internal permissions. +tc-explicit-permissions = Permissions +tc-implicit-permissions = Implicit Permissions +tc-explicit-prompt = Ask +tc-explicit-deny = Deny +tc-explicit-allow = Grant +tc-apps-accessing-permission = Apps accessing this permission + + + +# GT-1 (Guided Tour intro- screen) +gt-main-header = Welcome to the Privacy Panel! +gt-main-desc = This app will help you enhance your privacy protection and enables you to lock or find your phone if it’s lost. Just have a closer look. It takes only 2 minutes! + + +# GT-2 (GT: ALA intro screen) +gt-ala-explain-header = What is Location Adjustment good for? +gt-ala-explain-desc = Many apps access your geolocation, like the addressbook or the camera. If you don’t want to disclose your exact position, you can adjust the accuracy of your current location that is used by apps, set a custom location or hide your geolocation. + + +# GT-3 (GT: ALA general settings screen ) +gt-ala-blur-header = How to set Location Adjustment? +gt-ala-blur-desc = Not every app needs your exact location to work properly. Instead of sharing your exact coordinates, you can choose to effectively blur your location to e.g. 50 miles around you. This way, your weather app will still work, but you are not disclosing your exact location. Remember: Location Adjustment will not completely hide you! + + +# GT-4 (GT: ALA custom location) +gt-ala-custom-header = What is Custom Location? +gt-ala-custom-desc = With the custom location setting you are able to hide your real location and set it to another place worldwide. You choose where you are! + + +# GT-5 (GT: ALA per-app settings) +gt-ala-exceptions-header = What is location per app? +gt-ala-exceptions-desc = Here you will find an overview of all apps, that can access your geolocation. You can adjust the settings for each app individually. They overwrite the global location accuracy for that app. + + +# GT-6 (GT: RPP intro screen) +gt-rpp-explain-header = Remote Privacy Protection? What is it? +gt-rpp-explain-desc = If your phone is stolen or lost, you can use the Remote Privacy Protection. With another phone you can locate it, let it ring or even lock it remotely. You first have to set your personal passphrase and enable the features you will want to use (locate, ring and lock). + + +# GT-7 (GT: RPP passphrase) +gt-rpp-passphrase-header = How to set your personal passphrase? +gt-rpp-passphrase-desc = Tap “Remote Privacy Protection” and set your passphrase to secure your remote features. If you have forgotten your actual passphrase you can reset it using your SIM PIN or lockscreen passcode. + + +# GT-8 (GT: RPP locate phone) +gt-rpp-locate-header = How to locate my phone? +gt-rpp-locate-desc1 = If your phone is lost, just take the phone of a friend and send a SMS with +gt-rpp-locate-command = “RPP locate YOURPASSPHRASE“ +gt-rpp-locate-desc2 = to your phone number and you‘ll get the exact GPS coordinates via SMS in return. Make sure to set up the passphrase! + + +# GT-9 (GT: RPP ring phone) +gt-rpp-ring-header = How to ring your phone? +gt-rpp-ring-desc1 = It‘s easy! Just send an SMS with +gt-rpp-ring-command = “RPP ring YOURPASSPHRASE” +gt-rpp-ring-desc2 = to your phone number and it will ring until you find it and unlock it. + + +# GT-10 (GT: RPP lock phone) +gt-rpp-lock-header = How to lock my phone? +gt-rpp-lock-desc1 = If you have lost your phone and you want to lock it remotely just send an SMS with +gt-rpp-lock-command = “RPP lock YOURPASSPHRASE” +gt-rpp-lock-desc2 = to your phone number. The phone will lock itself and only you will be able to unlock it with your regular screen passcode. + + + +# Buttons +get-started = Get Started! +finish-tour = Finish Tour +ok = OK +back = Back +next = Next + + +# SMS messages +sms-ring = Your device should ring now and was locked. You can unlock it with your passcode. +sms-lock = Your device was locked. You can unlock it with your passcode. +sms-locate = Your device coordinates are @{{latitude}},{{longitude}} and your device was locked. You can unlock it with your passcode. + + +# About page +about-privacy-panel = About Privacy Panel +version = Version +build-id = Build-ID +about-header = About the Privacy Panel +about-description = Together with Deutsche Telecom the Mozilla Foundation developed the Privacy Panel to enable the user to take back control of the personal data… + + + + diff --git a/apps/privacy-panel/manifest.webapp b/apps/privacy-panel/manifest.webapp index 65d6194d6615..57bcd70e0913 100644 --- a/apps/privacy-panel/manifest.webapp +++ b/apps/privacy-panel/manifest.webapp @@ -11,6 +11,7 @@ "geolocation-noprompt":{}, "audio-channel-ringer": {}, "mobileconnection":{}, + "permissions": {}, "settings":{ "access": "readwrite" }, "webapps-manage":{}, "sms":{}, diff --git a/apps/privacy-panel/resources/about.json b/apps/privacy-panel/resources/about.json index 2aa26b75c36b..3dacf58b8925 100644 --- a/apps/privacy-panel/resources/about.json +++ b/apps/privacy-panel/resources/about.json @@ -1,4 +1,4 @@ { - "version": "1.0", - "build": "11/25/2014" + "version": "1.1", + "build": "2015-04-01" } diff --git a/apps/privacy-panel/resources/permissions_table.json b/apps/privacy-panel/resources/permissions_table.json new file mode 100644 index 000000000000..326ae9377c76 --- /dev/null +++ b/apps/privacy-panel/resources/permissions_table.json @@ -0,0 +1,55 @@ +{ + "composedPermissions" : [ + "contacts", + "device-storage:apps", + "device-storage:pictures", + "device-storage:videos", + "device-storage:music", + "device-storage:sdcard", + "settings" + ], + "accessModes" : [ + "read", + "write", + "create" + ], + "plainPermissions" : [ + "geolocation", + "camera", + "alarms", + "tcp-socket", + "network-events", + "sms", + "telephony", + "browser", + "bluetooth", + "mobileconnection", + "power", + "permissions", + "fmradio", + "attention", + "webapps-manage", + "backgroundservice", + "desktop-notification", + "networkstats-manage", + "wifi-manage", + "systemXHR", + "voicemail", + "idle", + "time", + "embed-apps", + "storage", + "background-sensors", + "cellbroadcast", + "audio-channel-normal", + "audio-channel-content", + "audio-channel-notification", + "audio-channel-alarm", + "audio-channel-telephony", + "audio-channel-ringer", + "audio-channel-publicnotification", + "open-remote-window", + "audio-capture", + "video-capture" + ] +} diff --git a/apps/privacy-panel/style/panels.css b/apps/privacy-panel/style/panels.css index c9443429a718..1f81e3d8e607 100644 --- a/apps/privacy-panel/style/panels.css +++ b/apps/privacy-panel/style/panels.css @@ -53,6 +53,7 @@ input[type="range"].blur-slider { position: absolute; top: 1.6rem; width: 3rem; + filter: url('shadow.svg#drop-shadow'); } .app-info { @@ -138,6 +139,33 @@ section[data-geolocation="true"][data-ala="true"][data-type="user-defined"] .typ padding-top: 0; } + +/** + * Transparency Control + */ + +section[role="region"] .permission-group header h2 { + margin-top: 1.5rem; +} + +.permission-list li * { + padding-top: 0.5em; +} + +.perm-info a { + padding-top: 0; + min-height: 3rem; +} + +.perm-info a span { + min-height: 3rem; +} + +ul li > a span:nth-of-type(2):not(.button) { + line-height: 3rem; +} + + /** * Guided tour */ @@ -391,9 +419,11 @@ section[data-section="gt"] .btn { color: #e30613; } + /** * RPP auth */ + #rpp-main[data-login-box="true"] #rpp-login { display: block; } diff --git a/apps/privacy-panel/style/shadow.svg b/apps/privacy-panel/style/shadow.svg new file mode 100644 index 000000000000..93643efe9ce6 --- /dev/null +++ b/apps/privacy-panel/style/shadow.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/privacy-panel/templates/about/main.html b/apps/privacy-panel/templates/about/main.html index 89185edcfd61..b1a19eadf50c 100644 --- a/apps/privacy-panel/templates/about/main.html +++ b/apps/privacy-panel/templates/about/main.html @@ -25,7 +25,7 @@

    About the Privacy Panel

    Together with Deutsche Telecom the Mozilla Foundation developed the Privacy Panel to enable the user to take back control of their - personal data ... + personal data…

    diff --git a/apps/privacy-panel/templates/tc/app_details.html b/apps/privacy-panel/templates/tc/app_details.html new file mode 100644 index 000000000000..205bbebf2186 --- /dev/null +++ b/apps/privacy-panel/templates/tc/app_details.html @@ -0,0 +1,43 @@ + + + diff --git a/apps/privacy-panel/templates/tc/applications.html b/apps/privacy-panel/templates/tc/applications.html new file mode 100644 index 000000000000..8a6c990b0613 --- /dev/null +++ b/apps/privacy-panel/templates/tc/applications.html @@ -0,0 +1,26 @@ + + + diff --git a/apps/privacy-panel/templates/tc/main.html b/apps/privacy-panel/templates/tc/main.html new file mode 100644 index 000000000000..cd44337fe3fc --- /dev/null +++ b/apps/privacy-panel/templates/tc/main.html @@ -0,0 +1,25 @@ + + + diff --git a/apps/privacy-panel/templates/tc/perm_details.html b/apps/privacy-panel/templates/tc/perm_details.html new file mode 100644 index 000000000000..acb72af338ee --- /dev/null +++ b/apps/privacy-panel/templates/tc/perm_details.html @@ -0,0 +1,26 @@ + + + diff --git a/apps/privacy-panel/templates/tc/permissions.html b/apps/privacy-panel/templates/tc/permissions.html new file mode 100644 index 000000000000..435ee75fdc07 --- /dev/null +++ b/apps/privacy-panel/templates/tc/permissions.html @@ -0,0 +1,12 @@ + + + diff --git a/apps/privacy-panel/test/marionette/ala_main_test.js b/apps/privacy-panel/test/marionette/ala_main_test.js index ab57c4d072ca..229d78e1ac36 100644 --- a/apps/privacy-panel/test/marionette/ala_main_test.js +++ b/apps/privacy-panel/test/marionette/ala_main_test.js @@ -3,7 +3,7 @@ var assert = require('assert'); var AlaMainPanel = require('./lib/panels/ala_main'); -marionette('check ala main panel', function() { +marionette('adjustable location accuracy panel', function() { var client = marionette.client({ settings: { 'privacy-panel-gt-complete': true, @@ -54,9 +54,9 @@ marionette('check ala main panel', function() { assert.ok(!typeBlur.displayed()); assert.ok(!typeCustom.displayed()); - + /**@todo: test select values change */ - + // turn geolocation off geolocationSwitcher.click(); @@ -65,7 +65,7 @@ marionette('check ala main panel', function() { }); assert.ok(!geolocationTypeBox.displayed()); - + // turn geolocation on geolocationSwitcher.click(); client.waitFor(function() { @@ -92,4 +92,4 @@ marionette('check ala main panel', function() { return !useLocationBlurBox.displayed(); }); }); -}); \ No newline at end of file +}); diff --git a/apps/privacy-panel/test/marionette/apps/chubby-unicorn/index.html b/apps/privacy-panel/test/marionette/apps/chubby-unicorn/index.html new file mode 100644 index 000000000000..b178a6b9d523 --- /dev/null +++ b/apps/privacy-panel/test/marionette/apps/chubby-unicorn/index.html @@ -0,0 +1,10 @@ + + + + + Chubby Unicorn + + +

    I’m a Unicorn.

    + + diff --git a/apps/privacy-panel/test/marionette/apps/chubby-unicorn/manifest.webapp b/apps/privacy-panel/test/marionette/apps/chubby-unicorn/manifest.webapp new file mode 100644 index 000000000000..245dbee0316e --- /dev/null +++ b/apps/privacy-panel/test/marionette/apps/chubby-unicorn/manifest.webapp @@ -0,0 +1,11 @@ +{ + "name": "Chubby Unicorn", + "description": "Chubby Unicorn mobile app", + "launch_path": "/index.html", + "developer": { + "name": "Unicorns Ltd.", + "url": "https://chubby.unicorn.com/" + }, + "type": "web", + "permissions": {} +} diff --git a/apps/privacy-panel/test/marionette/apps/flying-platypus/index.html b/apps/privacy-panel/test/marionette/apps/flying-platypus/index.html new file mode 100644 index 000000000000..863f962542bc --- /dev/null +++ b/apps/privacy-panel/test/marionette/apps/flying-platypus/index.html @@ -0,0 +1,10 @@ + + + + + Flying Platypus + + +

    I’m a platypus.

    + + diff --git a/apps/privacy-panel/test/marionette/apps/flying-platypus/manifest.webapp b/apps/privacy-panel/test/marionette/apps/flying-platypus/manifest.webapp new file mode 100644 index 000000000000..0ada4b849de9 --- /dev/null +++ b/apps/privacy-panel/test/marionette/apps/flying-platypus/manifest.webapp @@ -0,0 +1,13 @@ +{ + "name": "Flying Platypus", + "description": "Flying Platypus mobile app", + "launch_path": "/index.html", + "developer": { + "name": "Platypus Corp.", + "url": "https://flying.platypus.com/" + }, + "type": "privileged", + "permissions": { + "storage": {} + } +} diff --git a/apps/privacy-panel/test/marionette/apps/mighty-duck/index.html b/apps/privacy-panel/test/marionette/apps/mighty-duck/index.html new file mode 100644 index 000000000000..14a4db530388 --- /dev/null +++ b/apps/privacy-panel/test/marionette/apps/mighty-duck/index.html @@ -0,0 +1,10 @@ + + + + + Mighty Duck + + +

    I’m a duck.

    + + diff --git a/apps/privacy-panel/test/marionette/apps/mighty-duck/manifest.webapp b/apps/privacy-panel/test/marionette/apps/mighty-duck/manifest.webapp new file mode 100644 index 000000000000..a2ed2e500a88 --- /dev/null +++ b/apps/privacy-panel/test/marionette/apps/mighty-duck/manifest.webapp @@ -0,0 +1,13 @@ +{ + "name": "Mighty Duck", + "description": "Mighty Duck mobile app", + "launch_path": "/index.html", + "developer": { + "name": "Ducklings Inc.", + "url": "https://mighty.duck.com/" + }, + "type": "privileged", + "permissions": { + "contacts": {} + } +} diff --git a/apps/privacy-panel/test/marionette/lib/panels/root.js b/apps/privacy-panel/test/marionette/lib/panels/root.js index 6f4234759b94..559faa516831 100644 --- a/apps/privacy-panel/test/marionette/lib/panels/root.js +++ b/apps/privacy-panel/test/marionette/lib/panels/root.js @@ -16,6 +16,7 @@ RootPanel.prototype = { rootPanel: '#root', alaPanel: '#ala-main', rppPanel: '#rpp-main', + tcPanel: '#tc-main', gtPanel: '#gt-main' }, @@ -31,6 +32,10 @@ RootPanel.prototype = { this.client.findElement('#menu-item-rpp').tap(); }, + tapOnTcMenuItem: function() { + this.client.findElement('#menu-item-tc').tap(); + }, + tapOnGtMenuItem: function() { this.client.findElement('#menu-item-gt').tap(); }, @@ -45,6 +50,11 @@ RootPanel.prototype = { return this.client.findElement(this.selectors.rppPanel).displayed(); }, + isTcDisplayed: function() { + this.waitForPanelToDissapear(this.selectors.rootPanel); + return this.client.findElement(this.selectors.tcPanel).displayed(); + }, + isGtDisplayed: function() { this.waitForPanelToDissapear(this.selectors.rootPanel); return this.client.findElement(this.selectors.gtPanel).displayed(); diff --git a/apps/privacy-panel/test/marionette/lib/panels/tc_main.js b/apps/privacy-panel/test/marionette/lib/panels/tc_main.js new file mode 100644 index 000000000000..5ef2ed8fccf7 --- /dev/null +++ b/apps/privacy-panel/test/marionette/lib/panels/tc_main.js @@ -0,0 +1,97 @@ +'use strict'; + +var Base = require('../base'); + +function TcMainPanel(client) { + Base.call(this, client); +} + +module.exports = TcMainPanel; + +TcMainPanel.prototype = { + + __proto__: Base.prototype, + + selectors: { + rootPanel: '#root', + tcPanel: '#tc-main', + appPanel: '#tc-applications', + appEntry: '#tc-appList li', + appDetail: '#tc-appDetails', + permPanel: '#tc-permissions', + permEntry: '#tc-permList li', + permDetail: '#tc-permDetails', + sortKey: '#tc-sortKey' + }, + + init: function() { + this.launch(); + this.client.findElement('#menu-item-tc').tap(); + this.waitForPanelToDissapear(this.selectors.rootPanel); + }, + + // panels & sub-panels + + get appPanel() { + return this.client.findElement(this.selectors.appPanel); + }, + + get appDetail() { + return this.client.findElement(this.selectors.appDetail); + }, + + get permPanel() { + return this.client.findElement(this.selectors.permPanel); + }, + + get permDetail() { + return this.client.findElement(this.selectors.permDetail); + }, + + // panel transitions + + isAppDisplayed: function() { + this.waitForPanelToDissapear(this.selectors.tcPanel); + return this.appPanel.displayed(); + }, + + isPermDisplayed: function() { + this.waitForPanelToDissapear(this.selectors.tcPanel); + return this.permPanel.displayed(); + }, + + isAppDetailDisplayed: function() { + this.waitForPanelToDissapear(this.selectors.appPanel); + return this.appDetail.displayed(); + }, + + isPermDetailDisplayed: function() { + this.waitForPanelToDissapear(this.selectors.permPanel); + return this.permDetail.displayed(); + }, + + // UI actions + + tapOnAppMenuItem: function() { + this.client.findElement('a[href="' + this.selectors.appPanel + '"]').tap(); + }, + + tapOnPermMenuItem: function() { + this.client.findElement('a[href="' + this.selectors.permPanel + '"]').tap(); + }, + + tapOnAppEntryItem: function() { + this.client.findElement(this.selectors.appEntry).tap(); + }, + + tapOnPermEntryItem: function() { + this.client.findElement(this.selectors.permEntry).tap(); + }, + + sortApps: function(sortKey, selectorToFind) { + var select = this.client.findElement(this.selectors.sortKey); + this.tapSelectOption(select, sortKey); + this.waitForElement(selectorToFind); + } + +}; diff --git a/apps/privacy-panel/test/marionette/root_test.js b/apps/privacy-panel/test/marionette/root_test.js index 7153d0a63d1b..4dfd5a2a245c 100644 --- a/apps/privacy-panel/test/marionette/root_test.js +++ b/apps/privacy-panel/test/marionette/root_test.js @@ -18,7 +18,7 @@ marionette('root panel', function() { test('root page elements', function() { var menuItems = client.findElements('#root li'); - assert.ok(menuItems.length === 3); + assert.ok(menuItems.length === 4); }); test('ability to load ala panel', function() { @@ -31,6 +31,11 @@ marionette('root panel', function() { assert.ok(subject.isRppDisplayed()); }); + test('ability to load tc panel', function() { + subject.tapOnTcMenuItem(); + assert.ok(subject.isTcDisplayed()); + }); + test('ability to load guided tour panel', function() { subject.tapOnGtMenuItem(); assert.ok(subject.isGtDisplayed()); diff --git a/apps/privacy-panel/test/marionette/tc_main_test.js b/apps/privacy-panel/test/marionette/tc_main_test.js new file mode 100644 index 000000000000..7a79379ca9b3 --- /dev/null +++ b/apps/privacy-panel/test/marionette/tc_main_test.js @@ -0,0 +1,136 @@ +/* global __dirname */ +'use strict'; + +var assert = require('assert'); +var TcMainPanel = require('./lib/panels/tc_main'); + +/** + * For this test, we're using these three fake apps: + * + * +--------+-------------------+----------------------+-----------------------+ + * | | mightyD | chubbyU | flyingP | + * +--------+-------------------+----------------------+-----------------------+ + * | name | Mighty Duck | Chubby Unicorn | Flying Platypus | + * | trust | privileged | web | privileged | + * | vendor | Ducklings Inc. | Unicorns Ltd. | Platypus Corp. | + * | origin | mighty.duck.com | chubby.unicorn.com | flying.platypus.com | + * | path | /apps/mighty-duck | /apps/chubby-unicorn | /apps/flying-platypus | + * +--------+-------------------+----------------------+-----------------------+ + */ + +marionette('transparency control panels', function() { + + var client = marionette.client({ + settings: { + 'privacy-panel-gt-complete': true + }, + apps: { + 'mighty.duck.com': __dirname + '/apps/mighty-duck', + 'chubby.unicorn.com': __dirname + '/apps/chubby-unicorn', + 'flying.platypus.com': __dirname + '/apps/flying-platypus' + } + }); + + var subject; + setup(function() { + subject = new TcMainPanel(client); + subject.init(); + }); + + + /** + * useful selectors + */ + var app = { + chubbyU: 'li[data-key="Chubby Unicorn"]', // fake app + flyingP: 'li[data-key="Flying Platypus"]', // fake app + mightyD: 'li[data-key="Mighty Duck"]', // fake app + browser: 'li[data-key="Browser"]' // real Browser app, uses Contacts perm + }; + var vendor = { + chubbyU: 'ul[data-key="Unicorns Ltd."]', + flyingP: 'ul[data-key="Platypus Corp."]', + mightyD: 'ul[data-key="Ducklings Inc."]', + browser: 'ul[data-key="The Gaia Team"]' + }; + var trust = { + 'certified': 'ul[data-key="certified"]', + 'privileged': 'ul[data-key="privileged"]', + 'web': 'ul[data-key="web"]' + }; + var perm = { + contacts: '[data-key="contacts"]', + storage: '[data-key="storage"]' + }; + + + test('main panel', function() { + var menuItems = client.findElements('#tc-main li'); + assert.ok(menuItems.length === 2); + }); + + test('Applications panel and sub-panel', function() { + + function find(selector) { + return subject.appPanel.findElement(selector); + } + + // load Applications panel + subject.tapOnAppMenuItem(); + assert.ok(subject.isAppDisplayed()); + + // check that all fake apps are displayed alphabetically by default + assert.ok(find(app.chubbyU)); + assert.ok(find(app.flyingP)); + assert.ok(find(app.mightyD)); + assert.ok(find(app.chubbyU + ' ~ ' + app.flyingP)); + assert.ok(find(app.flyingP + ' ~ ' + app.mightyD)); + + // switch to "Trust Level" order + subject.sortApps('Trust Level', trust.web); + // check that each app is in its 'Trust Level' group + assert.ok(find(trust.privileged + ' > ' + app.flyingP)); + assert.ok(find(trust.privileged + ' > ' + app.mightyD)); + assert.ok(find(trust.web + ' > ' + app.chubbyU)); + // these two apps belong to the same group, and should be sorted + assert.ok(find(app.flyingP + ' ~ ' + app.mightyD)); + + // switch to "Vendor" order + subject.sortApps('Vendor', vendor.chubbyU); + // check that each app is in its 'Vendor' group + assert.ok(find(vendor.chubbyU + ' > ' + app.chubbyU)); + assert.ok(find(vendor.flyingP + ' > ' + app.flyingP)); + assert.ok(find(vendor.mightyD + ' > ' + app.mightyD)); + // check that 'Vendor' groups are sorted alphabetically + assert.ok(find(vendor.mightyD + ' ~ ' + vendor.flyingP)); + assert.ok(find(vendor.flyingP + ' ~ ' + vendor.chubbyU)); + + // open 'Browser' details and check that 'Contacts' is listed + find(app.browser).tap(); + assert.ok(subject.isAppDetailDisplayed()); + assert.ok(subject.appDetail.findElement(perm.contacts)); + + }); + + test('Permissions panel and sub-panel', function() { + + function find(selector) { + return subject.permPanel.findElement(selector); + } + + // load Permissions panel + subject.tapOnPermMenuItem(); + assert.ok(subject.isPermDisplayed()); + + // check that this panel displays permission items + assert.ok(find(perm.contacts)); + assert.ok(find(perm.storage)); + + // open 'Contacts' details and check that 'Browser' is listed + find(perm.contacts).tap(); + assert.ok(subject.isPermDetailDisplayed()); + assert.ok(subject.permDetail.findElement(app.browser)); + + }); + +}); diff --git a/apps/privacy-panel/test/unit/ala/app_list_test.js b/apps/privacy-panel/test/unit/ala/app_list_test.js deleted file mode 100644 index 9c6c96269c03..000000000000 --- a/apps/privacy-panel/test/unit/ala/app_list_test.js +++ /dev/null @@ -1,110 +0,0 @@ -'use strict'; - -var realMozApps; -var fakeApp1; -var fakeApp2; - -suite('ALA AppList', function() { - suiteSetup(function(done) { - - var apps = [{ - manifest: { - name: 'Mozilla Fake App 1', - launch_path: '/fakeapp1/index.html', - permissions: { - permission_1: {} - }, - icons: { - 84: '/style/icons/settings_84.png', - 126: '/style/icons/settings_126.png', - 142: 'style/icons/settings_142.png', - 189: '/style/icons/settings_189.png', - 284: '/style/icons/settings_284.png' - } - }, - manifestURL: 'http://fakeapp1/manifest.webapp' - }, { - manifest: { - name: 'Mozilla Fake App 2', - launch_path: '/fakeapp2/index.html', - permissions: { - permission_2: {} - } - }, - manifestURL: 'http://fakeapp2/manifest.webapp' - }, { - manifest: { - name: 'Mozilla Fake App 3', - launch_path: '/fakeapp3/index.html', - permissions: { - permission_2: {} - } - } - }]; - - require(['mocks/mock_navigator_moz_apps'], function(mozApps) { - realMozApps = navigator.mozApps; - navigator.mozApps = mozApps; - navigator.mozApps.mApps = apps; - done(); - }); - }); - - setup(function(done) { - require(['ala/app_list'], appList => { - this.subject = appList; - done(); - }); - }); - - suiteTeardown(function() { - navigator.mozApps = realMozApps; - }); - - test('should get one apps', function(done) { - this.subject.get('permission_1', function(result) { - assert.lengthOf(result, 1); - assert.equal(result[0].manifest.name, 'Mozilla Fake App 1'); - fakeApp1 = result[0]; - done(); - }); - }); - - test('should get list of apps', function(done) { - this.subject.get('permission_2', function(result) { - assert.lengthOf(result, 2); - - assert.equal(result[0].manifest.name, 'Mozilla Fake App 2'); - fakeApp2 = result[0]; - - assert.equal(result[1].manifest.name, 'Mozilla Fake App 3'); - done(); - }); - }); - - test('should get empty list of apps when we are giving invalid permission', - function(done) { - this.subject.get('invalidPermission', function(result) { - assert.isArray(result); - assert.lengthOf(result, 0); - done(); - }); - } - ); - - test('should get path to app icon', - function(done) { - var icon1 = this.subject.icon(fakeApp1); - assert.notEqual(icon1, '../style/images/default.png'); - done(); - } - ); - - test('should get default path to app icon if path is not set in manifest', - function(done) { - var icon2 = this.subject.icon(fakeApp2); - assert.equal(icon2, '../style/images/default.png'); - done(); - } - ); -}); diff --git a/apps/privacy-panel/test/unit/app_list_test.js b/apps/privacy-panel/test/unit/app_list_test.js new file mode 100644 index 000000000000..cdca54f3b575 --- /dev/null +++ b/apps/privacy-panel/test/unit/app_list_test.js @@ -0,0 +1,166 @@ +'use strict'; + +var realMozApps; +var realMozL10n; +var fakeApp1; +var fakeApp2; +var fakeApp3; + +suite('AppList', function() { + + suiteSetup(function(done) { + var apps = [ + { + manifest: { + name: 'Mozilla Fake App 1', + launch_path: '/fakeapp1/index.html', + type: 'privileged', + permissions: { + permission_1: {} + }, + developer: { + name: 'Flying Platypus', + url: 'https://flying.platypus.com/' + }, + icons: { + 84: '/style/icons/settings_84.png', + 126: '/style/icons/settings_126.png', + 142: 'style/icons/settings_142.png', + 189: '/style/icons/settings_189.png', + 284: '/style/icons/settings_284.png' + } + }, + manifestURL: 'http://fakeapp1/manifest.webapp' + }, + { + manifest: { + name: 'Mozilla Fake App 2', + launch_path: '/fakeapp2/index.html', + type: 'privileged', + developer: { + name: 'Mighty Duck', + url: 'https://mighty.duck.com/' + }, + permissions: { + permission_2: {} + } + }, + manifestURL: 'http://fakeapp2/manifest.webapp' + }, + { + manifest: { + name: 'Mozilla Fake App 3', + launch_path: '/fakeapp3/index.html', + type: 'web', + developer: { + name: 'Mighty Duck', + url: 'https://mighty.duck.com/' + }, + permissions: { + permission_2: {} + } + } + } + ]; + + require([ + 'mocks/mock_navigator_moz_apps', + 'mocks/mock_l10n' + ], + function(mozApps, mozL10n) { + realMozApps = navigator.mozApps; + navigator.mozApps = mozApps; + navigator.mozApps.mApps = apps; + + realMozL10n = navigator.mozL10n; + navigator.mozL10n = mozL10n; + + done(); + }); + }); + + setup(function(done) { + require(['app_list'], appList => { + this.subject = appList; + done(); + }); + }); + + suiteTeardown(function() { + navigator.mozApps = realMozApps; + navigator.mozL10n = realMozL10n; + }); + + test('initializing, should get all apps', function(done) { + this.subject.init().then(function() { + var result = this.subject.applications; + assert.lengthOf(result, 3); + assert.equal(result[0].manifest.name, 'Mozilla Fake App 1'); + assert.equal(result[1].manifest.name, 'Mozilla Fake App 2'); + assert.equal(result[2].manifest.name, 'Mozilla Fake App 3'); + fakeApp1 = result[0]; + fakeApp2 = result[1]; + fakeApp3 = result[2]; + done(); + }.bind(this)); + }); + + test('should get one app', function(done) { + var result = this.subject.getFilteredApps('permission_1'); + assert.lengthOf(result, 1); + assert.equal(result[0].manifest.name, 'Mozilla Fake App 1'); + done(); + }); + + test('should get a list of apps', function(done) { + var result = this.subject.getFilteredApps('permission_2'); + assert.lengthOf(result, 2); + assert.equal(result[0].manifest.name, 'Mozilla Fake App 2'); + assert.equal(result[1].manifest.name, 'Mozilla Fake App 3'); + done(); + }); + + test('should get an empty list of apps for an invalid permission', + function(done) { + var result = this.subject.getFilteredApps('invalidPermission'); + assert.isArray(result); + assert.lengthOf(result, 0); + done(); + } + ); + + test('should get path to app icon', + function(done) { + assert.notEqual(fakeApp1.iconURL, '../style/images/default.png'); + done(); + } + ); + + test('should get default path to app icon if path is not set in manifest', + function(done) { + assert.equal(fakeApp2.iconURL, '../style/images/default.png'); + done(); + } + ); + + test('should get an array of app groups (one group per trust level)', + function(done) { + assert.deepEqual(this.subject.getSortedApps('trust'), { + 'privileged': [ fakeApp1, fakeApp2 ], + 'web': [ fakeApp3 ] + }); + done(); + } + ); + + test('should get an array of app groups (one group per vendor)', + function(done) { + assert.deepEqual(this.subject.getSortedApps('vendor'), { + 'Flying Platypus': [ fakeApp1 ], + 'Mighty Duck': [ fakeApp2, fakeApp3 ] + }); + done(); + } + ); + +});