OLD | NEW |
(Empty) | |
| 1 # This Source Code Form is subject to the terms of the Mozilla Public |
| 2 # License, v. 2.0. If a copy of the MPL was not distributed with this file, |
| 3 # You can obtain one at http://mozilla.org/MPL/2.0/. |
| 4 |
| 5 __all__ = ['Profile', 'FirefoxProfile', 'ThunderbirdProfile'] |
| 6 |
| 7 import os |
| 8 import time |
| 9 import tempfile |
| 10 import uuid |
| 11 from addons import AddonManager |
| 12 from permissions import Permissions |
| 13 from shutil import rmtree |
| 14 |
| 15 try: |
| 16 import simplejson |
| 17 except ImportError: |
| 18 import json as simplejson |
| 19 |
| 20 class Profile(object): |
| 21 """Handles all operations regarding profile. Created new profiles, installs
extensions, |
| 22 sets preferences and handles cleanup.""" |
| 23 |
| 24 def __init__(self, |
| 25 profile=None, # Path to the profile |
| 26 addons=None, # String of one or list of addons to install |
| 27 addon_manifests=None, # Manifest for addons, see http://ahal.c
a/blog/2011/bulk-installing-fx-addons/ |
| 28 preferences=None, # Dictionary or class of preferences |
| 29 locations=None, # locations to proxy |
| 30 proxy=None, # setup a proxy - dict of server-loc,server-port,ss
l-port |
| 31 restore=True # If true remove all installed addons preferences
when cleaning up |
| 32 ): |
| 33 |
| 34 # if true, remove installed addons/prefs afterwards |
| 35 self.restore = restore |
| 36 |
| 37 # prefs files written to |
| 38 self.written_prefs = set() |
| 39 |
| 40 # our magic markers |
| 41 nonce = '%s %s' % (str(time.time()), uuid.uuid4()) |
| 42 self.delimeters = ('#MozRunner Prefs Start %s' % nonce,'#MozRunner Prefs
End %s' % nonce) |
| 43 |
| 44 # Handle profile creation |
| 45 self.create_new = not profile |
| 46 if profile: |
| 47 # Ensure we have a full path to the profile |
| 48 self.profile = os.path.abspath(os.path.expanduser(profile)) |
| 49 if not os.path.exists(self.profile): |
| 50 os.makedirs(self.profile) |
| 51 else: |
| 52 self.profile = self.create_new_profile() |
| 53 |
| 54 # set preferences |
| 55 if hasattr(self.__class__, 'preferences'): |
| 56 # class preferences |
| 57 self.set_preferences(self.__class__.preferences) |
| 58 self._preferences = preferences |
| 59 if preferences: |
| 60 # supplied preferences |
| 61 if isinstance(preferences, dict): |
| 62 # unordered |
| 63 preferences = preferences.items() |
| 64 # sanity check |
| 65 assert not [i for i in preferences |
| 66 if len(i) != 2] |
| 67 else: |
| 68 preferences = [] |
| 69 self.set_preferences(preferences) |
| 70 |
| 71 # set permissions |
| 72 self._locations = locations # store this for reconstruction |
| 73 self._proxy = proxy |
| 74 self.permissions = Permissions(self.profile, locations) |
| 75 prefs_js, user_js = self.permissions.network_prefs(proxy) |
| 76 self.set_preferences(prefs_js, 'prefs.js') |
| 77 self.set_preferences(user_js) |
| 78 |
| 79 # handle addon installation |
| 80 self.addon_manager = AddonManager(self.profile) |
| 81 self.addon_manager.install_addons(addons, addon_manifests) |
| 82 |
| 83 def exists(self): |
| 84 """returns whether the profile exists or not""" |
| 85 return os.path.exists(self.profile) |
| 86 |
| 87 def reset(self): |
| 88 """ |
| 89 reset the profile to the beginning state |
| 90 """ |
| 91 self.cleanup() |
| 92 if self.create_new: |
| 93 profile = None |
| 94 else: |
| 95 profile = self.profile |
| 96 self.__init__(profile=profile, |
| 97 addons=self.addon_manager.installed_addons, |
| 98 addon_manifests=self.addon_manager.installed_manifests, |
| 99 preferences=self._preferences, |
| 100 locations=self._locations, |
| 101 proxy = self._proxy) |
| 102 |
| 103 def create_new_profile(self): |
| 104 """Create a new clean profile in tmp which is a simple empty folder""" |
| 105 profile = tempfile.mkdtemp(suffix='.mozrunner') |
| 106 return profile |
| 107 |
| 108 |
| 109 ### methods for preferences |
| 110 |
| 111 def set_preferences(self, preferences, filename='user.js'): |
| 112 """Adds preferences dict to profile preferences""" |
| 113 |
| 114 |
| 115 # append to the file |
| 116 prefs_file = os.path.join(self.profile, filename) |
| 117 f = open(prefs_file, 'a') |
| 118 |
| 119 if preferences: |
| 120 |
| 121 # note what files we've touched |
| 122 self.written_prefs.add(filename) |
| 123 |
| 124 |
| 125 if isinstance(preferences, dict): |
| 126 # order doesn't matter |
| 127 preferences = preferences.items() |
| 128 |
| 129 # write the preferences |
| 130 f.write('\n%s\n' % self.delimeters[0]) |
| 131 _prefs = [(simplejson.dumps(k), simplejson.dumps(v) ) |
| 132 for k, v in preferences] |
| 133 for _pref in _prefs: |
| 134 f.write('user_pref(%s, %s);\n' % _pref) |
| 135 f.write('%s\n' % self.delimeters[1]) |
| 136 f.close() |
| 137 |
| 138 def pop_preferences(self, filename): |
| 139 """ |
| 140 pop the last set of preferences added |
| 141 returns True if popped |
| 142 """ |
| 143 |
| 144 lines = file(os.path.join(self.profile, filename)).read().splitlines() |
| 145 def last_index(_list, value): |
| 146 """ |
| 147 returns the last index of an item; |
| 148 this should actually be part of python code but it isn't |
| 149 """ |
| 150 for index in reversed(range(len(_list))): |
| 151 if _list[index] == value: |
| 152 return index |
| 153 s = last_index(lines, self.delimeters[0]) |
| 154 e = last_index(lines, self.delimeters[1]) |
| 155 |
| 156 # ensure both markers are found |
| 157 if s is None: |
| 158 assert e is None, '%s found without %s' % (self.delimeters[1], self.
delimeters[0]) |
| 159 return False # no preferences found |
| 160 elif e is None: |
| 161 assert s is None, '%s found without %s' % (self.delimeters[0], self.
delimeters[1]) |
| 162 |
| 163 # ensure the markers are in the proper order |
| 164 assert e > s, '%s found at %s, while %s found at %s' % (self.delimeters[
1], e, self.delimeters[0], s) |
| 165 |
| 166 # write the prefs |
| 167 cleaned_prefs = '\n'.join(lines[:s] + lines[e+1:]) |
| 168 f = file(os.path.join(self.profile, 'user.js'), 'w') |
| 169 f.write(cleaned_prefs) |
| 170 f.close() |
| 171 return True |
| 172 |
| 173 def clean_preferences(self): |
| 174 """Removed preferences added by mozrunner.""" |
| 175 for filename in self.written_prefs: |
| 176 if not os.path.exists(os.path.join(self.profile, filename)): |
| 177 # file has been deleted |
| 178 break |
| 179 while True: |
| 180 if not self.pop_preferences(filename): |
| 181 break |
| 182 |
| 183 ### cleanup |
| 184 |
| 185 def _cleanup_error(self, function, path, excinfo): |
| 186 """ Specifically for windows we need to handle the case where the window
s |
| 187 process has not yet relinquished handles on files, so we do a wait/t
ry |
| 188 construct and timeout if we can't get a clear road to deletion |
| 189 """ |
| 190 |
| 191 try: |
| 192 from exceptions import WindowsError |
| 193 from time import sleep |
| 194 def is_file_locked(): |
| 195 return excinfo[0] is WindowsError and excinfo[1].winerror == 32 |
| 196 |
| 197 if excinfo[0] is WindowsError and excinfo[1].winerror == 32: |
| 198 # Then we're on windows, wait to see if the file gets unlocked |
| 199 # we wait 10s |
| 200 count = 0 |
| 201 while count < 10: |
| 202 sleep(1) |
| 203 try: |
| 204 function(path) |
| 205 break |
| 206 except: |
| 207 count += 1 |
| 208 except ImportError: |
| 209 # We can't re-raise an error, so we'll hope the stuff above us will
throw |
| 210 pass |
| 211 |
| 212 def cleanup(self): |
| 213 """Cleanup operations for the profile.""" |
| 214 if self.restore: |
| 215 if self.create_new: |
| 216 if os.path.exists(self.profile): |
| 217 rmtree(self.profile, onerror=self._cleanup_error) |
| 218 else: |
| 219 self.clean_preferences() |
| 220 self.addon_manager.clean_addons() |
| 221 self.permissions.clean_db() |
| 222 |
| 223 __del__ = cleanup |
| 224 |
| 225 class FirefoxProfile(Profile): |
| 226 """Specialized Profile subclass for Firefox""" |
| 227 preferences = {# Don't automatically update the application |
| 228 'app.update.enabled' : False, |
| 229 # Don't restore the last open set of tabs if the browser has
crashed |
| 230 'browser.sessionstore.resume_from_crash': False, |
| 231 # Don't check for the default web browser |
| 232 'browser.shell.checkDefaultBrowser' : False, |
| 233 # Don't warn on exit when multiple tabs are open |
| 234 'browser.tabs.warnOnClose' : False, |
| 235 # Don't warn when exiting the browser |
| 236 'browser.warnOnQuit': False, |
| 237 # Only install add-ons from the profile and the application s
cope |
| 238 # Also ensure that those are not getting disabled. |
| 239 # see: https://developer.mozilla.org/en/Installing_extensions |
| 240 'extensions.enabledScopes' : 5, |
| 241 'extensions.autoDisableScopes' : 10, |
| 242 # Don't install distribution add-ons from the app folder |
| 243 'extensions.installDistroAddons' : False, |
| 244 # Dont' run the add-on compatibility check during start-up |
| 245 'extensions.showMismatchUI' : False, |
| 246 # Don't automatically update add-ons |
| 247 'extensions.update.enabled' : False, |
| 248 # Don't open a dialog to show available add-on updates |
| 249 'extensions.update.notifyUser' : False, |
| 250 # Suppress automatic safe mode after crashes |
| 251 'toolkit.startup.max_resumed_crashes' : -1, |
| 252 # Enable test mode to run multiple tests in parallel |
| 253 'focusmanager.testmode' : True, |
| 254 } |
| 255 |
| 256 class ThunderbirdProfile(Profile): |
| 257 preferences = {'extensions.update.enabled' : False, |
| 258 'extensions.update.notifyUser' : False, |
| 259 'browser.shell.checkDefaultBrowser' : False, |
| 260 'browser.tabs.warnOnClose' : False, |
| 261 'browser.warnOnQuit': False, |
| 262 'browser.sessionstore.resume_from_crash': False, |
| 263 # prevents the 'new e-mail address' wizard on new profile |
| 264 'mail.provider.enabled': False, |
| 265 } |
OLD | NEW |