OLD | NEW |
(Empty) | |
| 1 # Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. |
| 4 |
| 5 import json |
| 6 import os |
| 7 import time |
| 8 import zipfile |
| 9 |
| 10 from catapult_base import cloud_storage |
| 11 from profile_creators import profile_extender |
| 12 from telemetry.core import exceptions |
| 13 |
| 14 |
| 15 # Remote target upload directory in cloud storage for extensions. |
| 16 REMOTE_DIR = 'extension_set' |
| 17 |
| 18 # Target zip file. |
| 19 ZIP_NAME = 'extensions.zip' |
| 20 |
| 21 |
| 22 class InvalidExtensionArchiveError(exceptions.Error): |
| 23 """Exception thrown when remote archive is invalid or malformed. |
| 24 |
| 25 Remote archive should be located at REMOTE_DIR/ZIP_NAME. Upon failure, |
| 26 prompts user to update remote archive using update_remote_extensions |
| 27 script. |
| 28 """ |
| 29 |
| 30 def __init__(self, msg=''): |
| 31 msg += ('\nTry running\n' |
| 32 '\tpython update_remote_extensions.py -e extension_set.csv\n' |
| 33 'in src/tools/perf/profile_creator subdirectory.') |
| 34 super(InvalidExtensionArchiveError, self).__init__(msg) |
| 35 |
| 36 |
| 37 class ExtensionProfileExtender(profile_extender.ProfileExtender): |
| 38 """Creates a profile with many extensions.""" |
| 39 |
| 40 def __init__(self, finder_options): |
| 41 super(ExtensionProfileExtender, self).__init__(finder_options) |
| 42 self._extensions = [] |
| 43 finder_options.browser_options.disable_default_apps = False |
| 44 |
| 45 def Run(self): |
| 46 """Superclass override.""" |
| 47 # Download extensions from cloud and force-install extensions into profile. |
| 48 local_extensions_dir = os.path.join(self.profile_path, |
| 49 'external_extensions_crx') |
| 50 self._DownloadRemoteExtensions(cloud_storage.PARTNER_BUCKET, |
| 51 local_extensions_dir) |
| 52 self._LoadExtensions(local_extensions_dir, self.profile_path) |
| 53 |
| 54 try: |
| 55 self.SetUpBrowser() |
| 56 self._WaitForExtensionsToLoad() |
| 57 finally: |
| 58 self.TearDownBrowser() |
| 59 |
| 60 def RestrictedOSList(self): |
| 61 """Superclass override.""" |
| 62 return ['mac'] |
| 63 |
| 64 def _DownloadRemoteExtensions(self, remote_bucket, local_extensions_dir): |
| 65 """Downloads and unzips archive of common extensions to disk. |
| 66 |
| 67 Args: |
| 68 remote_bucket: bucket to download remote archive from. |
| 69 local_extensions_dir: destination extensions directory. |
| 70 |
| 71 Raises: |
| 72 InvalidExtensionArchiveError if remote archive is not found. |
| 73 """ |
| 74 remote_zip_path = os.path.join(REMOTE_DIR, ZIP_NAME) |
| 75 local_zip_path = os.path.join(local_extensions_dir, ZIP_NAME) |
| 76 try: |
| 77 cloud_storage.Get(remote_bucket, remote_zip_path, local_zip_path) |
| 78 except: |
| 79 raise InvalidExtensionArchiveError('Can\'t find archive at gs://%s/%s..' |
| 80 % (remote_bucket, remote_zip_path)) |
| 81 try: |
| 82 with zipfile.ZipFile(local_zip_path, 'r') as extensions_zip: |
| 83 extensions_zip.extractall(local_extensions_dir) |
| 84 finally: |
| 85 os.remove(local_zip_path) |
| 86 |
| 87 def _GetExtensionInfoFromCrx(self, crx_file): |
| 88 """Retrieves version + name of extension from CRX archive.""" |
| 89 with zipfile.ZipFile(crx_file, 'r') as crx_zip: |
| 90 manifest_contents = crx_zip.read('manifest.json') |
| 91 decoded_manifest = json.loads(manifest_contents) |
| 92 crx_version = decoded_manifest['version'] |
| 93 extension_name = decoded_manifest['name'] |
| 94 return (crx_version, extension_name) |
| 95 |
| 96 def _LoadExtensions(self, local_extensions_dir, profile_dir): |
| 97 """Loads extensions in _local_extensions_dir into user profile. |
| 98 |
| 99 Extensions are loaded according to platform specifications at |
| 100 https://developer.chrome.com/extensions/external_extensions.html |
| 101 |
| 102 Args: |
| 103 local_extensions_dir: directory containing CRX files. |
| 104 profile_dir: target profile directory for the extensions. |
| 105 |
| 106 Raises: |
| 107 InvalidExtensionArchiveError if archive contains a non-CRX file. |
| 108 """ |
| 109 ext_files = os.listdir(local_extensions_dir) |
| 110 external_ext_dir = os.path.join(profile_dir, 'External Extensions') |
| 111 os.makedirs(external_ext_dir) |
| 112 for ext_file in ext_files: |
| 113 ext_path = os.path.join(local_extensions_dir, ext_file) |
| 114 if not ext_file.endswith('.crx'): |
| 115 raise InvalidExtensionArchiveError('Archive contains non-crx file %s.' |
| 116 % ext_file) |
| 117 (version, name) = self._GetExtensionInfoFromCrx(ext_path) |
| 118 extension_info = { |
| 119 'external_crx': ext_path, |
| 120 'external_version': version, |
| 121 '_comment': name |
| 122 } |
| 123 ext_id = os.path.splitext(os.path.basename(ext_path))[0] |
| 124 extension_json_path = os.path.join(external_ext_dir, '%s.json' % ext_id) |
| 125 with open(extension_json_path, 'w') as f: |
| 126 f.write(json.dumps(extension_info)) |
| 127 self._extensions.append(ext_id) |
| 128 |
| 129 def _WaitForExtensionsToLoad(self): |
| 130 """Stall until browser has finished installing/loading all extensions.""" |
| 131 for extension_id in self._extensions: |
| 132 while True: |
| 133 try: |
| 134 self.browser.extensions.GetByExtensionId(extension_id) |
| 135 break |
| 136 except KeyError: |
| 137 # There's no event signalling when browser finishes installing |
| 138 # or loading an extension so re-check every 5 seconds. |
| 139 time.sleep(5) |
OLD | NEW |