Backed out changeset 0cafbf944d89 (bug 1318879) for robocop failures a=backout

This commit is contained in:
Wes Kocher 2016-11-21 13:01:21 -08:00
parent 53901256a5
commit efeab1563a
11 changed files with 710 additions and 1 deletions

View File

@ -36,6 +36,7 @@ from mozscreenshot import printstatus, dump_screen
# ---------------------------------------------------------------
_DEFAULT_PREFERENCE_FILE = os.path.join(SCRIPT_DIR, 'prefs_general.js')
_DEFAULT_APPS_FILE = os.path.join(SCRIPT_DIR, 'webapps_mochitest.json')
_DEFAULT_WEB_SERVER = "127.0.0.1"
_DEFAULT_HTTP_PORT = 8888

View File

@ -1650,6 +1650,17 @@ toolbar#nav-bar {
# get extensions to install
extensions = self.getExtensionsToInstall(options)
# web apps
appsPath = os.path.join(
SCRIPT_DIR,
'profile_data',
'webapps_mochitest.json')
if os.path.exists(appsPath):
with open(appsPath) as apps_file:
apps = json.load(apps_file)
else:
apps = None
# preferences
preferences = [
os.path.join(
@ -1701,6 +1712,7 @@ toolbar#nav-bar {
addons=extensions,
locations=self.locations,
preferences=prefs,
apps=apps,
proxy=proxy
)

View File

@ -18,3 +18,4 @@ from permissions import *
from prefs import *
from profile import *
from view import *
from webapps import *

View File

@ -12,6 +12,7 @@ import mozfile
from permissions import Permissions
from prefs import Preferences
from shutil import copytree
from webapps import WebappCollection
__all__ = ['Profile',
'FirefoxProfile',
@ -43,12 +44,13 @@ class Profile(object):
# profile.cleanup() has been called here
"""
def __init__(self, profile=None, addons=None, addon_manifests=None,
def __init__(self, profile=None, addons=None, addon_manifests=None, apps=None,
preferences=None, locations=None, proxy=None, restore=True):
"""
:param profile: Path to the profile
:param addons: String of one or list of addons to install
:param addon_manifests: Manifest for addons (see http://bit.ly/17jQ7i6)
:param apps: Dictionary or class of webapps to install
:param preferences: Dictionary or class of preferences
:param locations: ServerLocations object
:param proxy: Setup a proxy
@ -56,6 +58,7 @@ class Profile(object):
"""
self._addons = addons
self._addon_manifests = addon_manifests
self._apps = apps
self._locations = locations
self._proxy = proxy
@ -113,6 +116,10 @@ class Profile(object):
self.addon_manager = AddonManager(self.profile, restore=self.restore)
self.addon_manager.install_addons(self._addons, self._addon_manifests)
# handle webapps
self.webapps = WebappCollection(profile=self.profile, apps=self._apps)
self.webapps.update_manifests()
def __enter__(self):
return self
@ -135,6 +142,8 @@ class Profile(object):
self.addon_manager.clean()
if getattr(self, 'permissions', None) is not None:
self.permissions.clean_db()
if getattr(self, 'webapps', None) is not None:
self.webapps.clean()
# If it's a temporary profile we have to remove it
if self.create_new:

View File

@ -0,0 +1,281 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
"""
Handles installing open webapps (https://developer.mozilla.org/en-US/docs/Apps)
to a profile. A webapp object is a dict that contains some metadata about
the webapp and must at least include a name, description and manifestURL.
Each webapp has a manifest (https://developer.mozilla.org/en-US/docs/Apps/Manifest).
Additionally there is a separate json manifest that keeps track of the installed
webapps, their manifestURLs and their permissions.
"""
from string import Template
import json
import os
import shutil
import mozfile
__all__ = ["Webapp", "WebappCollection", "WebappFormatException", "APP_STATUS_NOT_INSTALLED",
"APP_STATUS_INSTALLED", "APP_STATUS_PRIVILEGED", "APP_STATUS_CERTIFIED"]
# from http://hg.mozilla.org/mozilla-central/file/add0b94c2c0b/caps/idl/nsIPrincipal.idl#l163
APP_STATUS_NOT_INSTALLED = 0
APP_STATUS_INSTALLED = 1
APP_STATUS_PRIVILEGED = 2
APP_STATUS_CERTIFIED = 3
class WebappFormatException(Exception):
"""thrown for invalid webapp objects"""
class Webapp(dict):
"""A webapp definition"""
required_keys = ('name', 'description', 'manifestURL')
def __init__(self, *args, **kwargs):
try:
dict.__init__(self, *args, **kwargs)
except (TypeError, ValueError):
raise WebappFormatException("Webapp object should be an instance of type 'dict'")
self.validate()
def __eq__(self, other):
"""Webapps are considered equal if they have the same name"""
if not isinstance(other, self.__class__):
return False
return self['name'] == other['name']
def __ne__(self, other):
"""Webapps are considered not equal if they have different names"""
return not self.__eq__(other)
def validate(self):
# TODO some keys are required if another key has a certain value
for key in self.required_keys:
if key not in self:
raise WebappFormatException("Webapp object missing required key '%s'" % key)
class WebappCollection(object):
"""A list-like object that collects webapps and updates the webapp manifests"""
json_template = Template(""""$name": {
"name": "$name",
"origin": "$origin",
"installOrigin": "$origin",
"receipt": null,
"installTime": 132333986000,
"manifestURL": "$manifestURL",
"localId": $localId,
"id": "$name",
"appStatus": $appStatus,
"csp": "$csp"
}""")
manifest_template = Template("""{
"name": "$name",
"csp": "$csp",
"description": "$description",
"launch_path": "/",
"developer": {
"name": "Mozilla",
"url": "https://mozilla.org/"
},
"permissions": [
],
"locales": {
"en-US": {
"name": "$name",
"description": "$description"
}
},
"default_locale": "en-US",
"icons": {
}
}
""")
def __init__(self, profile, apps=None, json_template=None, manifest_template=None):
"""
:param profile: the file path to a profile
:param apps: [optional] a list of webapp objects or file paths to json files describing
webapps
:param json_template: [optional] string template describing the webapp json format
:param manifest_template: [optional] string template describing the webapp manifest format
"""
if not isinstance(profile, basestring):
raise TypeError("Must provide path to a profile, received '%s'" % type(profile))
self.profile = profile
self.webapps_dir = os.path.join(self.profile, 'webapps')
self.backup_dir = os.path.join(self.profile, '.mozprofile_backup', 'webapps')
self._apps = []
self._installed_apps = []
if apps:
if not isinstance(apps, (list, set, tuple)):
apps = [apps]
for app in apps:
if isinstance(app, basestring) and os.path.isfile(app):
self.extend(self.read_json(app))
else:
self.append(app)
self.json_template = json_template or self.json_template
self.manifest_template = manifest_template or self.manifest_template
def __getitem__(self, index):
return self._apps.__getitem__(index)
def __setitem__(self, index, value):
return self._apps.__setitem__(index, Webapp(value))
def __delitem__(self, index):
return self._apps.__delitem__(index)
def __len__(self):
return self._apps.__len__()
def __contains__(self, value):
return self._apps.__contains__(Webapp(value))
def append(self, value):
return self._apps.append(Webapp(value))
def insert(self, index, value):
return self._apps.insert(index, Webapp(value))
def extend(self, values):
return self._apps.extend([Webapp(v) for v in values])
def remove(self, value):
return self._apps.remove(Webapp(value))
def _write_webapps_json(self, apps):
contents = []
for app in apps:
contents.append(self.json_template.substitute(app))
contents = '{\n' + ',\n'.join(contents) + '\n}\n'
webapps_json_path = os.path.join(self.webapps_dir, 'webapps.json')
webapps_json_file = open(webapps_json_path, "w")
webapps_json_file.write(contents)
webapps_json_file.close()
def _write_webapp_manifests(self, write_apps=[], remove_apps=[]):
# Write manifests for installed apps
for app in write_apps:
manifest_dir = os.path.join(self.webapps_dir, app['name'])
manifest_path = os.path.join(manifest_dir, 'manifest.webapp')
if not os.path.isfile(manifest_path):
if not os.path.isdir(manifest_dir):
os.mkdir(manifest_dir)
manifest = self.manifest_template.substitute(app)
manifest_file = open(manifest_path, "a")
manifest_file.write(manifest)
manifest_file.close()
# Remove manifests for removed apps
for app in remove_apps:
self._installed_apps.remove(app)
manifest_dir = os.path.join(self.webapps_dir, app['name'])
mozfile.remove(manifest_dir)
def update_manifests(self):
"""Updates the webapp manifests with the webapps represented in this collection
If update_manifests is called a subsequent time, there could have been apps added or
removed to the collection in the interim. The manifests will be adjusted accordingly
"""
apps_to_install = [app for app in self._apps if app not in self._installed_apps]
apps_to_remove = [app for app in self._installed_apps if app not in self._apps]
if apps_to_install == apps_to_remove == []:
# nothing to do
return
if not os.path.isdir(self.webapps_dir):
os.makedirs(self.webapps_dir)
elif not self._installed_apps:
shutil.copytree(self.webapps_dir, self.backup_dir)
webapps_json_path = os.path.join(self.webapps_dir, 'webapps.json')
webapps_json = []
if os.path.isfile(webapps_json_path):
webapps_json = self.read_json(webapps_json_path, description="description")
webapps_json = [a for a in webapps_json if a not in apps_to_remove]
# Iterate over apps already in webapps.json to determine the starting local
# id and to ensure apps are properly formatted
start_id = 1
for local_id, app in enumerate(webapps_json):
app['localId'] = local_id + 1
start_id += 1
if not app.get('csp'):
app['csp'] = ''
if not app.get('appStatus'):
app['appStatus'] = 3
# Append apps_to_install to the pre-existent apps
for local_id, app in enumerate(apps_to_install):
app['localId'] = local_id + start_id
# ignore if it's already installed
if app in webapps_json:
start_id -= 1
continue
webapps_json.append(app)
self._installed_apps.append(app)
# Write the full contents to webapps.json
self._write_webapps_json(webapps_json)
# Create/remove manifest file for each app.
self._write_webapp_manifests(apps_to_install, apps_to_remove)
def clean(self):
"""Remove all webapps that were installed and restore profile to previous state"""
if self._installed_apps:
mozfile.remove(self.webapps_dir)
if os.path.isdir(self.backup_dir):
shutil.copytree(self.backup_dir, self.webapps_dir)
mozfile.remove(self.backup_dir)
self._apps = []
self._installed_apps = []
@classmethod
def read_json(cls, path, **defaults):
"""Reads a json file which describes a set of webapps. The json format is either a
dictionary where each key represents the name of a webapp (e.g B2G format) or a list
of webapp objects.
:param path: Path to a json file defining webapps
:param defaults: Default key value pairs added to each webapp object if key doesn't exist
Returns a list of Webapp objects
"""
f = open(path, 'r')
app_json = json.load(f)
f.close()
apps = []
if isinstance(app_json, dict):
for k, v in app_json.iteritems():
v['name'] = k
apps.append(v)
else:
apps = app_json
if not isinstance(apps, list):
apps = [apps]
ret = []
for app in apps:
d = defaults.copy()
d.update(app)
ret.append(Webapp(**d))
return ret

View File

@ -0,0 +1,50 @@
[{ "name": "http_example_org",
"csp": "",
"origin": "http://example.org",
"manifestURL": "http://example.org/manifest.webapp",
"description": "http://example.org App",
"appStatus": 1
},
{ "name": "https_example_com",
"csp": "",
"origin": "https://example.com",
"manifestURL": "https://example.com/manifest.webapp",
"description": "https://example.com App",
"appStatus": 1
},
{ "name": "http_test1_example_org",
"csp": "",
"origin": "http://test1.example.org",
"manifestURL": "http://test1.example.org/manifest.webapp",
"description": "http://test1.example.org App",
"appStatus": 1
},
{ "name": "http_test1_example_org_8000",
"csp": "",
"origin": "http://test1.example.org:8000",
"manifestURL": "http://test1.example.org:8000/manifest.webapp",
"description": "http://test1.example.org:8000 App",
"appStatus": 1
},
{ "name": "http_sub1_test1_example_org",
"csp": "",
"origin": "http://sub1.test1.example.org",
"manifestURL": "http://sub1.test1.example.org/manifest.webapp",
"description": "http://sub1.test1.example.org App",
"appStatus": 1
},
{ "name": "https_example_com_privileged",
"csp": "",
"origin": "https://example.com",
"manifestURL": "https://example.com/manifest_priv.webapp",
"description": "https://example.com Privileged App",
"appStatus": 2
},
{ "name": "https_example_com_certified",
"csp": "",
"origin": "https://example.com",
"manifestURL": "https://example.com/manifest_cert.webapp",
"description": "https://example.com Certified App",
"appStatus": 3
}
]

View File

@ -0,0 +1,37 @@
{
"https_example_csp_certified": {
"csp": "default-src *; script-src 'self'; object-src 'none'; style-src 'self' 'unsafe-inline'",
"origin": "https://example.com",
"manifestURL": "https://example.com/manifest_csp_cert.webapp",
"description": "https://example.com certified app with manifest policy",
"appStatus": 3
},
"https_example_csp_installed": {
"csp": "default-src *; script-src 'self'; object-src 'none'; style-src 'self' 'unsafe-inline'",
"origin": "https://example.com",
"manifestURL": "https://example.com/manifest_csp_inst.webapp",
"description": "https://example.com installed app with manifest policy",
"appStatus": 1
},
"https_example_csp_privileged": {
"csp": "default-src *; script-src 'self'; object-src 'none'; style-src 'self' 'unsafe-inline'",
"origin": "https://example.com",
"manifestURL": "https://example.com/manifest_csp_priv.webapp",
"description": "https://example.com privileged app with manifest policy",
"appStatus": 2
},
"https_a_domain_certified": {
"csp": "",
"origin": "https://acertified.com",
"manifestURL": "https://acertified.com/manifest.webapp",
"description": "https://acertified.com certified app",
"appStatus": 3
},
"https_a_domain_privileged": {
"csp": "",
"origin": "https://aprivileged.com",
"manifestURL": "https://aprivileged.com/manifest.webapp",
"description": "https://aprivileged.com privileged app ",
"appStatus": 2
}
}

View File

@ -6,6 +6,7 @@
[test_nonce.py]
[bug785146.py]
[test_clone_cleanup.py]
[test_webapps.py]
[test_profile.py]
[test_profile_view.py]
[test_addons.py]

View File

@ -0,0 +1,202 @@
#!/usr/bin/env python
"""
test installing and managing webapps in a profile
"""
import os
import shutil
import unittest
from tempfile import mkdtemp
from mozprofile.webapps import WebappCollection, Webapp, WebappFormatException
here = os.path.dirname(os.path.abspath(__file__))
class WebappTest(unittest.TestCase):
"""Tests reading, installing and cleaning webapps
from a profile.
"""
manifest_path_1 = os.path.join(here, 'files', 'webapps1.json')
manifest_path_2 = os.path.join(here, 'files', 'webapps2.json')
def setUp(self):
self.profile = mkdtemp(prefix='test_webapp')
self.webapps_dir = os.path.join(self.profile, 'webapps')
self.webapps_json_path = os.path.join(self.webapps_dir, 'webapps.json')
def tearDown(self):
shutil.rmtree(self.profile)
def test_read_json_manifest(self):
"""Tests WebappCollection.read_json"""
# Parse a list of webapp objects and verify it worked
manifest_json_1 = WebappCollection.read_json(self.manifest_path_1)
self.assertEqual(len(manifest_json_1), 7)
for app in manifest_json_1:
self.assertIsInstance(app, Webapp)
for key in Webapp.required_keys:
self.assertIn(key, app)
# Parse a dictionary of webapp objects and verify it worked
manifest_json_2 = WebappCollection.read_json(self.manifest_path_2)
self.assertEqual(len(manifest_json_2), 5)
for app in manifest_json_2:
self.assertIsInstance(app, Webapp)
for key in Webapp.required_keys:
self.assertIn(key, app)
def test_invalid_webapp(self):
"""Tests a webapp with a missing required key"""
webapps = WebappCollection(self.profile)
# Missing the required key "description", exception should be raised
self.assertRaises(WebappFormatException, webapps.append, {'name': 'foo'})
def test_webapp_collection(self):
"""Tests the methods of the WebappCollection object"""
webapp_1 = {'name': 'test_app_1',
'description': 'a description',
'manifestURL': 'http://example.com/1/manifest.webapp',
'appStatus': 1}
webapp_2 = {'name': 'test_app_2',
'description': 'another description',
'manifestURL': 'http://example.com/2/manifest.webapp',
'appStatus': 2}
webapp_3 = {'name': 'test_app_2',
'description': 'a third description',
'manifestURL': 'http://example.com/3/manifest.webapp',
'appStatus': 3}
webapps = WebappCollection(self.profile)
self.assertEqual(len(webapps), 0)
# WebappCollection should behave like a list
def invalid_index():
webapps[0]
self.assertRaises(IndexError, invalid_index)
# Append a webapp object
webapps.append(webapp_1)
self.assertTrue(len(webapps), 1)
self.assertIsInstance(webapps[0], Webapp)
self.assertEqual(len(webapps[0]), len(webapp_1))
self.assertEqual(len(set(webapps[0].items()) & set(webapp_1.items())), len(webapp_1))
# Remove a webapp object
webapps.remove(webapp_1)
self.assertEqual(len(webapps), 0)
# Extend a list of webapp objects
webapps.extend([webapp_1, webapp_2])
self.assertEqual(len(webapps), 2)
self.assertTrue(webapp_1 in webapps)
self.assertTrue(webapp_2 in webapps)
self.assertNotEquals(webapps[0], webapps[1])
# Insert a webapp object
webapps.insert(1, webapp_3)
self.assertEqual(len(webapps), 3)
self.assertEqual(webapps[1], webapps[2])
for app in webapps:
self.assertIsInstance(app, Webapp)
# Assigning an invalid type (must be accepted by the dict() constructor) should throw
def invalid_type():
webapps[2] = 1
self.assertRaises(WebappFormatException, invalid_type)
def test_install_webapps(self):
"""Test installing webapps into a profile that has no prior webapps"""
webapps = WebappCollection(self.profile, apps=self.manifest_path_1)
self.assertFalse(os.path.exists(self.webapps_dir))
# update the webapp manifests for the first time
webapps.update_manifests()
self.assertFalse(os.path.isdir(os.path.join(self.profile, webapps.backup_dir)))
self.assertTrue(os.path.isfile(self.webapps_json_path))
webapps_json = webapps.read_json(self.webapps_json_path, description="fake description")
self.assertEqual(len(webapps_json), 7)
for app in webapps_json:
self.assertIsInstance(app, Webapp)
manifest_json_1 = webapps.read_json(self.manifest_path_1)
manifest_json_2 = webapps.read_json(self.manifest_path_2)
self.assertEqual(len(webapps_json), len(manifest_json_1))
for app in webapps_json:
self.assertTrue(app in manifest_json_1)
# Remove one of the webapps from WebappCollection after it got installed
removed_app = manifest_json_1[2]
webapps.remove(removed_app)
# Add new webapps to the collection
webapps.extend(manifest_json_2)
# update the webapp manifests a second time
webapps.update_manifests()
self.assertFalse(os.path.isdir(os.path.join(self.profile, webapps.backup_dir)))
self.assertTrue(os.path.isfile(self.webapps_json_path))
webapps_json = webapps.read_json(self.webapps_json_path, description="a description")
self.assertEqual(len(webapps_json), 11)
# The new apps should be added
for app in webapps_json:
self.assertIsInstance(app, Webapp)
self.assertTrue(os.path.isfile(os.path.join(self.webapps_dir, app['name'],
'manifest.webapp')))
# The removed app should not exist in the manifest
self.assertNotIn(removed_app, webapps_json)
self.assertFalse(os.path.exists(os.path.join(self.webapps_dir, removed_app['name'])))
# Cleaning should delete the webapps directory entirely
# since there was nothing there before
webapps.clean()
self.assertFalse(os.path.isdir(self.webapps_dir))
def test_install_webapps_preexisting(self):
"""Tests installing webapps when the webapps directory already exists"""
manifest_json_2 = WebappCollection.read_json(self.manifest_path_2)
# Synthesize a pre-existing webapps directory
os.mkdir(self.webapps_dir)
shutil.copyfile(self.manifest_path_2, self.webapps_json_path)
for app in manifest_json_2:
app_path = os.path.join(self.webapps_dir, app['name'])
os.mkdir(app_path)
f = open(os.path.join(app_path, 'manifest.webapp'), 'w')
f.close()
webapps = WebappCollection(self.profile, apps=self.manifest_path_1)
self.assertTrue(os.path.exists(self.webapps_dir))
# update webapp manifests for the first time
webapps.update_manifests()
# A backup should be created
self.assertTrue(os.path.isdir(os.path.join(self.profile, webapps.backup_dir)))
# Both manifests should remain installed
webapps_json = webapps.read_json(self.webapps_json_path, description='a fake description')
self.assertEqual(len(webapps_json), 12)
for app in webapps_json:
self.assertIsInstance(app, Webapp)
self.assertTrue(os.path.isfile(os.path.join(self.webapps_dir, app['name'],
'manifest.webapp')))
# Upon cleaning the backup should be restored
webapps.clean()
self.assertFalse(os.path.isdir(os.path.join(self.profile, webapps.backup_dir)))
# The original webapps should still be installed
webapps_json = webapps.read_json(self.webapps_json_path)
for app in webapps_json:
self.assertIsInstance(app, Webapp)
self.assertTrue(os.path.isfile(os.path.join(self.webapps_dir, app['name'],
'manifest.webapp')))
self.assertEqual(webapps_json, manifest_json_2)
if __name__ == '__main__':
unittest.main()

View File

@ -6,6 +6,7 @@
mochitest_profile_files = [
'prefs_general.js',
'webapps_mochitest.json',
]
TEST_HARNESS_FILES.testing.mochitest.profile_data += mochitest_profile_files

View File

@ -0,0 +1,114 @@
[
{
"name": "http_example_org",
"csp": "",
"origin": "http://example.org",
"manifestURL": "http://example.org/manifest.webapp",
"description": "http://example.org App",
"appStatus": 1
},
{
"name": "https_example_com",
"csp": "",
"origin": "https://example.com",
"manifestURL": "https://example.com/manifest.webapp",
"description": "https://example.com App",
"appStatus": 1
},
{
"name": "http_test1_example_org",
"csp": "",
"origin": "http://test1.example.org",
"manifestURL": "http://test1.example.org/manifest.webapp",
"description": "http://test1.example.org App",
"appStatus": 1
},
{
"name": "http_test1_example_org_8000",
"csp": "",
"origin": "http://test1.example.org:8000",
"manifestURL": "http://test1.example.org:8000/manifest.webapp",
"description": "http://test1.example.org:8000 App",
"appStatus": 1
},
{
"name": "http_sub1_test1_example_org",
"csp": "",
"origin": "http://sub1.test1.example.org",
"manifestURL": "http://sub1.test1.example.org/manifest.webapp",
"description": "http://sub1.test1.example.org App",
"appStatus": 1
},
{
"name": "https_example_com_privileged",
"csp": "",
"origin": "https://example.com",
"manifestURL": "https://example.com/manifest_priv.webapp",
"description": "https://example.com Privileged App",
"appStatus": 2
},
{
"name": "https_example_com_certified",
"csp": "",
"origin": "https://example.com",
"manifestURL": "https://example.com/manifest_cert.webapp",
"description": "https://example.com Certified App",
"appStatus": 3
},
{
"name": "https_example_csp_certified",
"csp": "default-src *; script-src 'self'; object-src 'none'; style-src 'self' 'unsafe-inline'",
"origin": "https://example.com",
"manifestURL": "https://example.com/manifest_csp_cert.webapp",
"description": "https://example.com Certified App with manifest policy",
"appStatus": 3
},
{
"name": "https_example_csp_installed",
"csp": "default-src *; script-src 'self'; object-src 'none'; style-src 'self' 'unsafe-inline'",
"origin": "https://example.com",
"manifestURL": "https://example.com/manifest_csp_inst.webapp",
"description": "https://example.com Installed App with manifest policy",
"appStatus": 1
},
{
"name": "https_example_csp_privileged",
"csp": "default-src *; script-src 'self'; object-src 'none'; style-src 'self' 'unsafe-inline'",
"origin": "https://example.com",
"manifestURL": "https://example.com/manifest_csp_priv.webapp",
"description": "https://example.com Privileged App with manifest policy",
"appStatus": 2
},
{
"name": "https_a_domain_certified",
"csp": "",
"origin": "https://acertified.com",
"manifestURL": "https://acertified.com/manifest.webapp",
"description": "https://acertified.com Certified App",
"appStatus": 3
},
{
"name": "https_a_domain_privileged",
"csp": "",
"origin": "https://aprivileged.com",
"manifestURL": "https://aprivileged.com/manifest.webapp",
"description": "https://aprivileged.com Privileged App ",
"appStatus": 2
},
{
"name": "test_desktop_hosted_launch",
"csp": "",
"origin": "http://127.0.0.1:8888/",
"manifestURL": "http://127.0.0.1:8888/sample.manifest",
"description": "Hosted app",
"appStatus": 1
},
{
"name": "test_desktop_packaged_launch",
"csp": "",
"origin": "app://test_desktop_packaged_launch",
"manifestURL": "http://127.0.0.1:8888/sample.manifest",
"description": "Packaged App",
"appStatus": 2
}
]