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 & force-install extensions into profile. | |
robliao
2015/07/21 19:49:52
Nit: & -> and
sydli
2015/07/21 20:33:04
Done.
| |
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. """ | |
robliao
2015/07/21 19:49:52
Remove leading and trailing whitespace from doc.
sydli
2015/07/21 20:33:04
Done.
| |
131 while True: | |
132 time.sleep(5) | |
robliao
2015/07/21 19:49:51
Comment why we're using a sleep instead of waiting
sydli
2015/07/21 20:33:04
Done.
| |
133 exists_unloaded_extension = False | |
robliao
2015/07/21 19:49:51
exists_unloaded_extension looks unnecessary.
You c
sydli
2015/07/21 20:33:04
Switched the loops as per your suggestion. Cleaner
| |
134 for extension_id in self._extensions: | |
135 try: | |
136 self.browser.extensions.GetByExtensionId(extension_id) | |
137 except KeyError: | |
138 exists_unloaded_extension = True | |
139 if not exists_unloaded_extension: | |
140 break | |
141 | |
OLD | NEW |