Bug 1451733 - [mozprofile] Clean up the public facing addons API a bit r=jmaher

While we are removing a bunch of stuff and breaking backwards compatibility, I
figured this would be a good time to also change some of the APIs. These APIs
aren't used much in mozilla-central (and this patch updates the few places that
do).

This rolls the 'install_addons()' and 'install_addon_from_path' method into a
single 'install' method. This install method can accept a string or list of
paths to an individual addon (directory or .xpi), or a directory containing
addons.

This also renames Profile.addon_manager to Profile.addons, which reads better.

MozReview-Commit-ID: 7vDPnG4cKqu

--HG--
extra : rebase_source : 62f8613b9824e06e698d5af8dcbb4bcb07b8079e
This commit is contained in:
Andrew Halberstadt 2018-04-05 12:04:21 -04:00
parent caac402c4b
commit 69d04f8035
4 changed files with 110 additions and 115 deletions

View File

@ -120,11 +120,76 @@ class AddonManager(object):
except AddonFormatError:
return False
def install_addons(self, addons):
"""
Installs all types of addons
def _install_addon(self, path, unpack=False):
addons = [path]
:param addons: a list of addon paths to install
# if path is not an add-on, try to install all contained add-ons
try:
self.addon_details(path)
except AddonFormatError as e:
module_logger.warning('Could not install %s: %s' % (path, str(e)))
# If the path doesn't exist, then we don't really care, just return
if not os.path.isdir(path):
return
addons = [os.path.join(path, x) for x in os.listdir(path) if
self.is_addon(os.path.join(path, x))]
addons.sort()
# install each addon
for addon in addons:
# determine the addon id
addon_details = self.addon_details(addon)
addon_id = addon_details.get('id')
# if the add-on has to be unpacked force it now
# note: we might want to let Firefox do it in case of addon details
orig_path = None
if os.path.isfile(addon) and (unpack or addon_details['unpack']):
orig_path = addon
addon = tempfile.mkdtemp()
mozfile.extract(orig_path, addon)
# copy the addon to the profile
extensions_path = os.path.join(self.profile, 'extensions')
addon_path = os.path.join(extensions_path, addon_id)
if os.path.isfile(addon):
addon_path += '.xpi'
# move existing xpi file to backup location to restore later
if os.path.exists(addon_path):
self.backup_dir = self.backup_dir or tempfile.mkdtemp()
shutil.move(addon_path, self.backup_dir)
# copy new add-on to the extension folder
if not os.path.exists(extensions_path):
os.makedirs(extensions_path)
shutil.copy(addon, addon_path)
else:
# move existing folder to backup location to restore later
if os.path.exists(addon_path):
self.backup_dir = self.backup_dir or tempfile.mkdtemp()
shutil.move(addon_path, self.backup_dir)
# copy new add-on to the extension folder
shutil.copytree(addon, addon_path, symlinks=True)
# if we had to extract the addon, remove the temporary directory
if orig_path:
mozfile.remove(addon)
addon = orig_path
self._addons.append(addon_id)
self.installed_addons.append(addon)
def install(self, addons, **kwargs):
"""
Installs addons from a filepath or directory of addons in the profile.
:param addons: paths to .xpi or addon directories
:param unpack: whether to unpack unless specified otherwise in the install.rdf
"""
if not addons:
return
@ -133,7 +198,7 @@ class AddonManager(object):
if isinstance(addons, string_types):
addons = [addons]
for addon in set(addons):
self.install_from_path(addon)
self._install_addon(addon, **kwargs)
@classmethod
def _gen_iid(cls, addon_path):
@ -256,76 +321,6 @@ class AddonManager(object):
return details
def install_from_path(self, path, unpack=False):
"""
Installs addon from a filepath or directory of addons in the profile.
:param path: path to .xpi or directory of addons
:param unpack: whether to unpack unless specified otherwise in the install.rdf
"""
addons = [path]
# if path is not an add-on, try to install all contained add-ons
try:
self.addon_details(path)
except AddonFormatError as e:
module_logger.warning('Could not install %s: %s' % (path, str(e)))
# If the path doesn't exist, then we don't really care, just return
if not os.path.isdir(path):
return
addons = [os.path.join(path, x) for x in os.listdir(path) if
self.is_addon(os.path.join(path, x))]
addons.sort()
# install each addon
for addon in addons:
# determine the addon id
addon_details = self.addon_details(addon)
addon_id = addon_details.get('id')
# if the add-on has to be unpacked force it now
# note: we might want to let Firefox do it in case of addon details
orig_path = None
if os.path.isfile(addon) and (unpack or addon_details['unpack']):
orig_path = addon
addon = tempfile.mkdtemp()
mozfile.extract(orig_path, addon)
# copy the addon to the profile
extensions_path = os.path.join(self.profile, 'extensions')
addon_path = os.path.join(extensions_path, addon_id)
if os.path.isfile(addon):
addon_path += '.xpi'
# move existing xpi file to backup location to restore later
if os.path.exists(addon_path):
self.backup_dir = self.backup_dir or tempfile.mkdtemp()
shutil.move(addon_path, self.backup_dir)
# copy new add-on to the extension folder
if not os.path.exists(extensions_path):
os.makedirs(extensions_path)
shutil.copy(addon, addon_path)
else:
# move existing folder to backup location to restore later
if os.path.exists(addon_path):
self.backup_dir = self.backup_dir or tempfile.mkdtemp()
shutil.move(addon_path, self.backup_dir)
# copy new add-on to the extension folder
shutil.copytree(addon, addon_path, symlinks=True)
# if we had to extract the addon, remove the temporary directory
if orig_path:
mozfile.remove(addon)
addon = orig_path
self._addons.append(addon_id)
self.installed_addons.append(addon)
def remove_addon(self, addon_id):
"""Remove the add-on as specified by the id

View File

@ -132,8 +132,8 @@ class Profile(object):
self.set_preferences(user_js)
# handle add-on installation
self.addon_manager = AddonManager(self.profile, restore=self.restore)
self.addon_manager.install_addons(self._addons)
self.addons = AddonManager(self.profile, restore=self.restore)
self.addons.install(self._addons)
def __enter__(self):
return self

View File

@ -30,13 +30,13 @@ class TestAddonsManager(unittest.TestCase):
self.logger.setLevel(mozlog.ERROR)
self.profile = mozprofile.profile.Profile()
self.am = self.profile.addon_manager
self.am = self.profile.addons
self.profile_path = self.profile.profile
self.tmpdir = tempfile.mkdtemp()
self.addCleanup(mozfile.remove, self.tmpdir)
def test_install_addons_multiple_same_source(self):
def test_install_multiple_same_source(self):
# Generate installer stubs for all possible types of addons
addon_xpi = generate_addon('test-addon-1@mozilla.org',
path=self.tmpdir)
@ -45,18 +45,18 @@ class TestAddonsManager(unittest.TestCase):
xpi=False)
# The same folder should not be installed twice
self.am.install_addons([addon_folder, addon_folder])
self.am.install([addon_folder, addon_folder])
self.assertEqual(self.am.installed_addons, [addon_folder])
self.am.clean()
# The same XPI file should not be installed twice
self.am.install_addons([addon_xpi, addon_xpi])
self.am.install([addon_xpi, addon_xpi])
self.assertEqual(self.am.installed_addons, [addon_xpi])
self.am.clean()
# Even if it is the same id the add-on should be installed twice, if
# specified via XPI and folder
self.am.install_addons([addon_folder, addon_xpi])
self.am.install([addon_folder, addon_xpi])
self.assertEqual(len(self.am.installed_addons), 2)
self.assertIn(addon_folder, self.am.installed_addons)
self.assertIn(addon_xpi, self.am.installed_addons)
@ -69,13 +69,13 @@ class TestAddonsManager(unittest.TestCase):
zipped.extractall(self.tmpdir)
finally:
zipped.close()
self.am.install_from_path(self.tmpdir)
self.am.install(self.tmpdir)
self.assertEqual(len(self.am.installed_addons), 1)
self.assertTrue(os.path.isdir(self.am.installed_addons[0]))
def test_install_webextension(self):
addon = os.path.join(here, 'addons', 'apply-css.xpi')
self.am.install_from_path(addon)
self.am.install(addon)
self.assertEqual(len(self.am.installed_addons), 1)
self.assertTrue(os.path.isfile(self.am.installed_addons[0]))
self.assertEqual('apply-css.xpi', os.path.basename(self.am.installed_addons[0]))
@ -85,7 +85,7 @@ class TestAddonsManager(unittest.TestCase):
def test_install_webextension_sans_id(self):
addon = os.path.join(here, 'addons', 'apply-css-sans-id.xpi')
self.am.install_from_path(addon)
self.am.install(addon)
self.assertEqual(len(self.am.installed_addons), 1)
self.assertTrue(os.path.isfile(self.am.installed_addons[0]))
@ -94,7 +94,7 @@ class TestAddonsManager(unittest.TestCase):
details = self.am.addon_details(self.am.installed_addons[0])
self.assertIn('@temporary-addon', details['id'])
def test_install_from_path_xpi(self):
def test_install_xpi(self):
addons_to_install = []
addons_installed = []
@ -102,14 +102,14 @@ class TestAddonsManager(unittest.TestCase):
for ext in ['test-addon-1@mozilla.org', 'test-addon-2@mozilla.org']:
temp_addon = generate_addon(ext, path=self.tmpdir)
addons_to_install.append(self.am.addon_details(temp_addon)['id'])
self.am.install_from_path(temp_addon)
self.am.install(temp_addon)
# Generate a list of addons installed in the profile
addons_installed = [str(x[:-len('.xpi')]) for x in os.listdir(os.path.join(
self.profile.profile, 'extensions'))]
self.assertEqual(addons_to_install.sort(), addons_installed.sort())
def test_install_from_path_folder(self):
def test_install_folder(self):
# Generate installer stubs for all possible types of addons
addons = []
addons.append(generate_addon('test-addon-1@mozilla.org',
@ -126,11 +126,11 @@ class TestAddonsManager(unittest.TestCase):
xpi=False))
addons.sort()
self.am.install_from_path(self.tmpdir)
self.am.install(self.tmpdir)
self.assertEqual(self.am.installed_addons, addons)
def test_install_from_path_unpack(self):
def test_install_unpack(self):
# Generate installer stubs for all possible types of addons
addon_xpi = generate_addon('test-addon-unpack@mozilla.org',
path=self.tmpdir)
@ -141,34 +141,34 @@ class TestAddonsManager(unittest.TestCase):
path=self.tmpdir)
# Test unpack flag for add-on as XPI
self.am.install_from_path(addon_xpi)
self.am.install(addon_xpi)
self.assertEqual(self.am.installed_addons, [addon_xpi])
self.am.clean()
# Test unpack flag for add-on as folder
self.am.install_from_path(addon_folder)
self.am.install(addon_folder)
self.assertEqual(self.am.installed_addons, [addon_folder])
self.am.clean()
# Test forcing unpack an add-on
self.am.install_from_path(addon_no_unpack, unpack=True)
self.am.install(addon_no_unpack, unpack=True)
self.assertEqual(self.am.installed_addons, [addon_no_unpack])
self.am.clean()
def test_install_from_path_after_reset(self):
def test_install_after_reset(self):
# Installing the same add-on after a reset should not cause a failure
addon = generate_addon('test-addon-1@mozilla.org',
path=self.tmpdir, xpi=False)
# We cannot use self.am because profile.reset() creates a new instance
self.profile.addon_manager.install_from_path(addon)
self.profile.addons.install(addon)
self.profile.reset()
self.profile.addon_manager.install_from_path(addon)
self.assertEqual(self.profile.addon_manager.installed_addons, [addon])
self.profile.addons.install(addon)
self.assertEqual(self.profile.addons.installed_addons, [addon])
def test_install_from_path_backup(self):
def test_install_backup(self):
staged_path = os.path.join(self.profile_path, 'extensions')
# Generate installer stubs for all possible types of addons
@ -182,10 +182,10 @@ class TestAddonsManager(unittest.TestCase):
name='test-addon-1-dupe@mozilla.org')
# Test backup of xpi files
self.am.install_from_path(addon_xpi)
self.am.install(addon_xpi)
self.assertIsNone(self.am.backup_dir)
self.am.install_from_path(addon_xpi)
self.am.install(addon_xpi)
self.assertIsNotNone(self.am.backup_dir)
self.assertEqual(os.listdir(self.am.backup_dir),
['test-addon-1@mozilla.org.xpi'])
@ -196,10 +196,10 @@ class TestAddonsManager(unittest.TestCase):
self.am.clean()
# Test backup of folders
self.am.install_from_path(addon_folder)
self.am.install(addon_folder)
self.assertIsNone(self.am.backup_dir)
self.am.install_from_path(addon_folder)
self.am.install(addon_folder)
self.assertIsNotNone(self.am.backup_dir)
self.assertEqual(os.listdir(self.am.backup_dir),
['test-addon-1@mozilla.org'])
@ -210,10 +210,10 @@ class TestAddonsManager(unittest.TestCase):
self.am.clean()
# Test backup of xpi files with another file name
self.am.install_from_path(addon_name)
self.am.install(addon_name)
self.assertIsNone(self.am.backup_dir)
self.am.install_from_path(addon_xpi)
self.am.install(addon_xpi)
self.assertIsNotNone(self.am.backup_dir)
self.assertEqual(os.listdir(self.am.backup_dir),
['test-addon-1@mozilla.org.xpi'])
@ -223,7 +223,7 @@ class TestAddonsManager(unittest.TestCase):
['test-addon-1@mozilla.org.xpi'])
self.am.clean()
def test_install_from_path_invalid_addons(self):
def test_install_invalid_addons(self):
# Generate installer stubs for all possible types of addons
addons = []
addons.append(generate_addon('test-addon-invalid-no-manifest@mozilla.org',
@ -232,17 +232,17 @@ class TestAddonsManager(unittest.TestCase):
addons.append(generate_addon('test-addon-invalid-no-id@mozilla.org',
path=self.tmpdir))
self.am.install_from_path(self.tmpdir)
self.am.install(self.tmpdir)
self.assertEqual(self.am.installed_addons, [])
@unittest.skip("Feature not implemented as part of AddonManger")
def test_install_from_path_error(self):
""" Check install_from_path raises an error with an invalid addon"""
def test_install_error(self):
""" Check install raises an error with an invalid addon"""
temp_addon = generate_addon('test-addon-invalid-version@mozilla.org')
# This should raise an error here
self.am.install_from_path(temp_addon)
self.am.install(temp_addon)
def test_addon_details(self):
# Generate installer stubs for a valid and invalid add-on manifest
@ -276,7 +276,7 @@ class TestAddonsManager(unittest.TestCase):
addon_one = generate_addon('test-addon-1@mozilla.org')
addon_two = generate_addon('test-addon-2@mozilla.org')
self.am.install_addons(addon_one)
self.am.install(addon_one)
installed_addons = [str(x[:-len('.xpi')]) for x in os.listdir(os.path.join(
self.profile.profile, 'extensions'))]
@ -285,7 +285,7 @@ class TestAddonsManager(unittest.TestCase):
# Cleanup addons
duplicate_profile = mozprofile.profile.Profile(profile=self.profile.profile,
addons=addon_two)
duplicate_profile.addon_manager.clean()
duplicate_profile.addons.clean()
addons_after_cleanup = [str(x[:-len('.xpi')]) for x in os.listdir(os.path.join(
duplicate_profile.profile, 'extensions'))]
@ -311,7 +311,7 @@ class TestAddonsManager(unittest.TestCase):
am = mozprofile.addons.AddonManager(profile, restore=True)
for addon in addons:
am.install_from_path(addon)
am.install(addon)
# now its there
self.assertEqual(os.listdir(profile), ['extensions'])
@ -335,7 +335,7 @@ class TestAddonsManager(unittest.TestCase):
addons.append(generate_addon('test-addon-2@mozilla.org',
path=self.tmpdir))
self.am.install_from_path(self.tmpdir)
self.am.install(self.tmpdir)
extensions_path = os.path.join(self.profile_path, 'extensions')
staging_path = os.path.join(extensions_path)

View File

@ -125,7 +125,7 @@ class FFSetup(object):
# installing addons
LOG.info("Installing Add-ons:")
LOG.info(extensions)
profile.addon_manager.install_addons(extensions)
profile.addons.install(extensions)
# installing webextensions
webextensions = self.test_config.get('webextensions', None)
@ -143,7 +143,7 @@ class FFSetup(object):
if not os.path.exists(filename):
continue
LOG.info(filename)
profile.addon_manager.install_from_path(filename)
profile.addons.install(filename)
def _run_profile(self):
runner_cls = mozrunner.runners.get(