# 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/.importConfigParserimportosimportmozinfofromfirefox_puppeteer.baseimportBaseLibfromfirefox_puppeteer.api.appinfoimportAppInfoclassActiveUpdate(BaseLib):def__getattr__(self,attr):value=self.marionette.execute_script(""" let ums = Components.classes['@mozilla.org/updates/update-manager;1'] .getService(Components.interfaces.nsIUpdateManager); return ums.activeUpdate[arguments[0]]; """,script_args=[attr])ifvalue:returnvalueelse:raiseAttributeError('{} has no attribute {}'.format(self.__class__.__name__,attr))@propertydefexists(self):"""Checks if there is an active update. :returns: True if there is an active update """active_update=self.marionette.execute_script(""" let ums = Components.classes['@mozilla.org/updates/update-manager;1'] .getService(Components.interfaces.nsIUpdateManager); return ums.activeUpdate; """)returnbool(active_update)defget_patch_at(self,patch_index):"""Use nsIUpdate.getPatchAt to return a patch from an update. :returns: JSON data for an nsIUpdatePatch object """returnself.marionette.execute_script(""" let ums = Components.classes['@mozilla.org/updates/update-manager;1'] .getService(Components.interfaces.nsIUpdateManager); return ums.activeUpdate.getPatchAt(arguments[0]); """,script_args=[patch_index])@propertydefpatch_count(self):"""Get the patchCount from the active update. :returns: The patch count """returnself.marionette.execute_script(""" let ums = Components.classes['@mozilla.org/updates/update-manager;1'] .getService(Components.interfaces.nsIUpdateManager); return ums.activeUpdate.patchCount; """)@propertydefselected_patch(self):"""Get the selected patch for the active update. :returns: JSON data for the selected patch """returnself.marionette.execute_script(""" let ums = Components.classes['@mozilla.org/updates/update-manager;1'] .getService(Components.interfaces.nsIUpdateManager); return ums.activeUpdate.selectedPatch; """)classMARChannels(BaseLib):"""Class to handle the allowed MAR channels as listed in update-settings.ini."""INI_SECTION='Settings'INI_OPTION='ACCEPTED_MAR_CHANNEL_IDS'classMARConfigParser(ConfigParser.ConfigParser):"""INI parser which allows to read and write MAR config files. Virtually identical to the original method, but delimit keys and values with '=' instead of ' = ' """defwrite(self,fp):"""Write an .ini-format representation of the configuration state."""ifself._defaults:fp.write("[%s]\n"%ConfigParser.DEFAULTSECT)for(key,value)inself._defaults.items():fp.write("%s=%s\n"%(key,str(value).replace('\n','\n\t')))fp.write("\n")forsectioninself._sections:fp.write("[%s]\n"%section)for(key,value)inself._sections[section].items():ifkey=="__name__":continueif(valueisnotNone)or(self._optcre==self.OPTCRE):key="=".join((key,str(value).replace('\n','\n\t')))fp.write("%s\n"%(key))fp.write("\n")def__init__(self,marionette):BaseLib.__init__(self,marionette)self.config_file_path=self.marionette.execute_script(""" Components.utils.import('resource://gre/modules/Services.jsm'); let file = Services.dirsvc.get('GreD', Components.interfaces.nsIFile); file.append('update-settings.ini'); return file.path; """)self.config=self.MARConfigParser()self.config.optionxform=str@propertydefchannels(self):"""The currently accepted MAR channels. :returns: A set of channel names """# Make sure to always read the current file contentsself.config.read(self.config_file_path)returnset(self.config.get(self.INI_SECTION,self.INI_OPTION).split(','))@channels.setterdefchannels(self,channels):"""Set the accepted MAR channels. :param channels: A set of channel names """self.config.set(self.INI_SECTION,self.INI_OPTION,','.join(channels))withopen(self.config_file_path,'wb')asconfigfile:self.config.write(configfile)defadd_channels(self,channels):"""Add additional MAR channels. :param channels: A set of channel names to add """self.channels=self.channels|set(channels)defremove_channels(self,channels):"""Remove MAR channels. :param channels: A set of channel names to remove """self.channels=self.channels-set(channels)classSoftwareUpdate(BaseLib):"""The SoftwareUpdate API adds support for an easy access to the update process."""PREF_APP_DISTRIBUTION='distribution.id'PREF_APP_DISTRIBUTION_VERSION='distribution.version'PREF_APP_UPDATE_CHANNEL='app.update.channel'PREF_APP_UPDATE_URL='app.update.url'PREF_DISABLED_ADDONS='extensions.disabledAddons'def__init__(self,marionette):BaseLib.__init__(self,marionette)self.app_info=AppInfo(marionette)self._mar_channels=MARChannels(marionette)self._active_update=ActiveUpdate(marionette)@propertydefABI(self):"""Get the customized ABI for the update service. :returns: ABI version """abi=self.app_info.XPCOMABIifmozinfo.isMac:abi+=self.marionette.execute_script(""" let macutils = Components.classes['@mozilla.org/xpcom/mac-utils;1'] .getService(Components.interfaces.nsIMacUtils); if (macutils.isUniversalBinary) { return '-u-' + macutils.architecturesInBinary; } return ''; """)returnabi@propertydefactive_update(self):""" Holds a reference to an :class:`ActiveUpdate` object."""returnself._active_update@propertydefallowed(self):"""Check if the user has permissions to run the software update :returns: Status if the user has the permissions """returnself.marionette.execute_script(""" let aus = Components.classes['@mozilla.org/updates/update-service;1'] .getService(Components.interfaces.nsIApplicationUpdateService); return aus.canCheckForUpdates && aus.canApplyUpdates; """)@propertydefbuild_info(self):"""Return information of the current build version :returns: A dictionary of build information """update_url=self.get_formatted_update_url(True)return{'buildid':self.app_info.appBuildID,'channel':self.update_channel,'disabled_addons':self.marionette.get_pref(self.PREF_DISABLED_ADDONS),'locale':self.app_info.locale,'mar_channels':self.mar_channels.channels,'update_url':update_url,'update_snippet':self.get_update_snippet(update_url),'user_agent':self.app_info.user_agent,'version':self.app_info.version}@propertydefis_complete_update(self):"""Return true if the offered update is a complete update :returns: True if the offered update is a complete update """# Throw when isCompleteUpdate is called without an update. This should# never happen except if the test is incorrectly written.assertself.active_update.exists,'An active update has been found'patch_count=self.active_update.patch_countassertpatch_count==1orpatch_count==2,\'An update must have one or two patches included'# Ensure Partial and Complete patches produced have unique urlsifpatch_count==2:patch0_url=self.active_update.get_patch_at(0)['URL']patch1_url=self.active_update.get_patch_at(1)['URL']assertpatch0_url!=patch1_url,\'Partial and Complete download URLs are different'returnself.active_update.selected_patch['type']=='complete'@propertydefmar_channels(self):""" Holds a reference to a :class:`MARChannels` object."""returnself._mar_channels@propertydefos_version(self):"""Returns information about the OS version :returns: The OS version """returnself.marionette.execute_script(""" Components.utils.import("resource://gre/modules/Services.jsm"); let osVersion; try { osVersion = Services.sysinfo.getProperty("name") + " " + Services.sysinfo.getProperty("version"); } catch (ex) { } if (osVersion) { try { osVersion += " (" + Services.sysinfo.getProperty("secondaryLibrary") + ")"; } catch (e) { // Not all platforms have a secondary widget library, // so an error is nothing to worry about. } osVersion = encodeURIComponent(osVersion); } return osVersion; """)@propertydefpatch_info(self):""" Returns information of the active update in the queue."""info={'channel':self.update_channel}if(self.active_update.exists):info['buildid']=self.active_update.buildIDinfo['is_complete']=self.is_complete_updateinfo['size']=self.active_update.selected_patch['size']info['type']=self.update_typeinfo['url_mirror']= \self.active_update.selected_patch['finalURL']or'n/a'info['version']=self.active_update.appVersionreturninfo@propertydefstaging_directory(self):""" Returns the path to the updates staging directory."""returnself.marionette.execute_script(""" let aus = Components.classes['@mozilla.org/updates/update-service;1'] .getService(Components.interfaces.nsIApplicationUpdateService); return aus.getUpdatesDirectory().path; """)@propertydefupdate_channel(self):"""Return the currently used update channel."""returnself.marionette.get_pref(self.PREF_APP_UPDATE_CHANNEL,default_branch=True)@update_channel.setterdefupdate_channel(self,channel):"""Set the update channel to be used for update checks. :param channel: New update channel to use """self.marionette.set_pref(self.PREF_APP_UPDATE_CHANNEL,channel,default_branch=True)@propertydefupdate_url(self):"""Return the update URL used for update checks."""returnself.marionette.get_pref(self.PREF_APP_UPDATE_URL,default_branch=True)@update_url.setterdefupdate_url(self,url):"""Set the update URL to be used for update checks. :param url: New update URL to use """self.marionette.set_pref(self.PREF_APP_UPDATE_URL,url,default_branch=True)@propertydefupdate_type(self):"""Returns the type of the active update."""returnself.active_update.typedefforce_fallback(self):"""Update the update.status file and set the status to 'failed:6'"""withopen(os.path.join(self.staging_directory,'update.status'),'w')asf:f.write('failed: 6\n')defget_update_snippet(self,update_url):"""Retrieve contents of the update snippet. :param update_url: URL to the update snippet """snippet=Nonetry:importurllib2response=urllib2.urlopen(update_url)snippet=response.read()exceptException:passreturnsnippetdefget_formatted_update_url(self,force=False):"""Retrieve the formatted AUS update URL the update snippet is retrieved from. :param force: Boolean flag to force an update check :returns: The URL of the update snippet """# Format the URL by replacing placeholdersurl=self.marionette.execute_script(""" Components.utils.import("resource://gre/modules/UpdateUtils.jsm") return UpdateUtils.formatUpdateURL(arguments[0]); """,script_args=[self.update_url])ifforce:if'?'inurl:url+='&'else:url+='?'url+='force=1'returnurl