{-# LANGUAGE CPP, TypeSynonymInstances, OverloadedStrings,
GeneralizedNewtypeDeriving, DeriveDataTypeable,
FlexibleInstances #-}-- |A module for working with Firefox profiles. Firefox profiles are manipulated-- in pure code and then \"prepared\" for network transmission. moduleTest.WebDriver.Firefox.Profile(-- * ProfilesFirefoxProfile(..),PreparedFirefoxProfile-- * Preferences,FirefoxPref(..),ToFirefox(..),addPref,getPref,deletePref-- * Extensions,addExtension,deleteExtension-- * Loading and preparing profiles,loadProfile,prepareProfile,prepareTempProfile,prepareLoadedProfile)whereimportData.AesonimportData.Attoparsec.TextasAPimportqualifiedData.HashMap.StrictasHMimportqualifiedData.HashSetasHSimportData.Text(Text,pack)importData.Text.IOasTIOimportData.ByteString(ByteString)importqualifiedData.ByteString.Char8asSBSimportqualifiedData.ByteString.Lazy.Char8asLBSimportqualifiedData.ByteString.Base64asB64importData.FixedimportData.RatioimportData.IntimportData.WordimportData.CharimportSystem.IOimportSystem.FilePathhiding(hasExtension,addExtension)importSystem.DirectoryimportSystem.IO.TempimportCodec.Archive.ZipimportDistribution.Simple.UtilsimportDistribution.VerbosityimportControl.MonadimportControl.ApplicativeimportControl.Monad.IO.ClassimportControl.Exception.LiftedimportData.Typeable-- |This structure allows you to-- construct and manipulate Firefox profiles in pure code, deferring execution-- of IO operations until the profile is \"prepared\" using either-- 'prepareProfile' or one of the wrapper functions 'prepareTempProfile' and -- 'prepareLoadedProfile'.dataFirefoxProfile=FirefoxProfile{-- |Location of the profile in the local file systemprofileDir::FilePath-- |A set of filepaths pointing to Firefox extensions.-- These paths can either refer to an .xpi file-- or an extension directory,profileExts::HS.HashSetFilePath-- |A map of Firefox preferences. These are the settings-- found in the profile's prefs.js, and entries found in-- about:config,profilePrefs::HM.HashMapTextFirefoxPref}deriving(Eq,Show)-- |Represents a Firefox profile that has been prepared for -- network transmission. The profile cannot be modified in this form.newtypePreparedFirefoxProfile=PreparedFirefoxProfileByteStringderiving(Eq,Show,ToJSON,FromJSON)-- |A Firefox preference value. This is the subset of JSON values that excludes-- arrays and objects.dataFirefoxPref=PrefInteger!Integer|PrefDouble!Double|PrefString!Text|PrefBool!Boolderiving(Eq,Show)instanceToJSONFirefoxPrefwheretoJSONv=casevofPrefIntegeri->toJSONiPrefDoubled->toJSONdPrefStrings->toJSONsPrefBoolb->toJSONbinstanceExceptionProfileParseError-- |An error occured while attempting to parse a profile's prefs.js file newtypeProfileParseError=ProfileParseErrorStringderiving(Eq,Show,Read,Typeable)-- |A typeclass to convert types to Firefox preference valuesclassToFirefoxawheretoFirefox::a->FirefoxPrefinstanceToFirefoxTextwheretoFirefox=PrefStringinstanceToFirefoxStringwheretoFirefox=toFirefox.packinstanceToFirefoxBoolwheretoFirefox=PrefBoolinstanceToFirefoxIntegerwheretoFirefox=PrefInteger#define I(t) instance ToFirefox t where toFirefox = PrefInteger . toIntegerI(Int)I(Int8)I(Int16)I(Int32)I(Int64)I(Word)I(Word8)I(Word16)I(Word32)I(Word64)instanceToFirefoxDoublewheretoFirefox=PrefDoubleinstanceToFirefoxFloatwheretoFirefox=PrefDouble.realToFracinstance(Integrala)=>ToFirefox(Ratioa)wheretoFirefox=PrefDouble.realToFracinstance(HasResolutionr)=>ToFirefox(Fixedr)wheretoFirefox=PrefDouble.realToFrac-- |Retrieve a preference from a profile by key name.getPref::Text->FirefoxProfile->MaybeFirefoxPrefgetPrefk(FirefoxProfile__m)=HM.lookupkm-- |Add a new preference entry to a profile, overwriting any existing entry-- with the same key.addPref::ToFirefoxa=>Text->a->FirefoxProfile->FirefoxProfileaddPrefkvp=asMapp$HM.insertk(toFirefoxv)-- |Delete an existing preference entry from a profile. This operation is-- silent if the preference wasn't found.deletePref::Text->FirefoxProfile->FirefoxProfiledeletePrefkp=asMapp$HM.deletek-- |Add a new extension to the profile. The file path should refer to-- an .xpi file or an extension directory. This operation has no effect if-- the same extension has already been added to this profile.addExtension::FilePath->FirefoxProfile->FirefoxProfileaddExtensionpathp=asSetp$HS.insertpath-- |Delete an existing extension from the profile. The file path should refer-- to an .xpi file or an extension directory. This operation has no effect if-- the extension was never added to the profile.deleteExtension::FilePath->FirefoxProfile->FirefoxProfiledeleteExtensionpathp=asSetp$HS.deletepathasMap::FirefoxProfile->(HM.HashMapTextFirefoxPref->HM.HashMapTextFirefoxPref)->FirefoxProfileasMap(FirefoxProfilephshm)f=FirefoxProfilephs(fhm)asSet::FirefoxProfile->(HS.HashSetFilePath->HS.HashSetFilePath)->FirefoxProfileasSet(FirefoxProfilephshm)f=FirefoxProfilep(fhs)hmtempProfile::MonadIOm=>mFirefoxProfiletempProfile=liftIO$defaultProfile<$>mkTemp-- |Load an existing profile from the file system. Any prepared changes made to-- the FirefoxProfile will have no effect to the profile on disk.loadProfile::MonadIOm=>FilePath->mFirefoxProfileloadProfilepath=liftIO$doFirefoxProfile{profileDir=d}<-tempProfileFirefoxProfile<$>pured<*>getExtensions<*>getPrefswhereextD=path</>"extensions"userPref=path</>"prefs"<.>"js"getExtensions=HS.fromList.filter(`elem`[".",".."])<$>getDirectoryContentsextDgetPrefs=HM.fromList<$>(parsePrefs=<<TIO.readFileuserPref)parsePrefss=either(throwIO.ProfileParseError)return$parseOnlyprefsParsers-- |Prepare a FirefoxProfile for network transmission.-- Internally, this function constructs a Firefox profile within a temp -- directory, archives it as a zip file, and then base64 encodes the zipped -- data. The temporary directory is deleted afterwardsprepareProfile::MonadIOm=>FirefoxProfile->mPreparedFirefoxProfileprepareProfileFirefoxProfile{profileDir=d,profileExts=s,profilePrefs=m}=liftIO$docreateDirectoryIfMissingFalseextensionDextPaths<-mapMcanonicalizePath.HS.toList$sforM_extPathsinstallExtensionwithFileuserPrefsWriteModewriteUserPrefsprof<-PreparedFirefoxProfile.B64.encode.SBS.concat.LBS.toChunks.fromArchive<$>addFilesToArchive[OptRecursive]emptyArchive[d]removeDirectoryRecursivedreturnprofwhereextensionD=d</>"extensions"userPrefs=d</>"prefs"<.>"js"installExtensionePath=casesplitExtensionePathof(_,".xpi")->installOrdinaryFilesilentePathdest_->installDirectoryContentssilentePathdestwheredest=extensionD</>eFile(_,eFile)=splitFileNameePathwriteUserPrefsh=forM_(HM.toListm)$\(k,v)->LBS.hPuth.LBS.concat$["user_pref(",encodek,", ",encodev,");\n"]-- |Apply a function on an automatically generated default profile, and-- prepare the result. The FirefoxProfile passed to the handler function is-- the default profile used by sessions when Nothing is specifiedprepareTempProfile::MonadIOm=>(FirefoxProfile->FirefoxProfile)->mPreparedFirefoxProfileprepareTempProfilef=liftMftempProfile>>=prepareProfile-- |Convenience function to load an existing Firefox profile from disk, apply-- a handler function, and then prepare the result for network transmission.prepareLoadedProfile::MonadIOm=>FilePath->(FirefoxProfile->FirefoxProfile)->mPreparedFirefoxProfileprepareLoadedProfilepathf=liftMf(loadProfilepath)>>=prepareProfiledefaultProfile::FilePath->FirefoxProfiledefaultProfiled=FirefoxProfiledHS.empty$HM.fromList[("app.update.auto",PrefBoolFalse),("app.update.enabled",PrefBoolFalse),("browser.startup.page",PrefInteger0),("browser.download.manager.showWhenStarting",PrefBoolFalse),("browser.EULA.override",PrefBoolTrue),("browser.EULA.3.accepted",PrefBoolTrue),("browser.link.open_external",PrefInteger2),("browser.link.open_newwindow",PrefInteger2),("browser.offline",PrefBoolFalse),("browser.safebrowsing.enabled",PrefBoolFalse),("browser.search.update",PrefBoolFalse),("browser.sessionstore.resume_from_crash",PrefBoolFalse),("browser.shell.checkDefaultBrowser",PrefBoolFalse),("browser.tabs.warnOnClose",PrefBoolFalse),("browser.tabs.warnOnOpen",PrefBoolFalse),("browser.startup.page",PrefInteger0),("browser.safebrowsing.malware.enabled",PrefBoolFalse),("startup.homepage_welcome_url",PrefString"about:blank"),("devtools.errorconsole.enabled",PrefBoolTrue),("focusmanager.testmode",PrefBoolTrue),("dom.disable_open_during_load",PrefBoolFalse),("extensions.autoDisableScopes",PrefInteger10),("extensions.logging.enabled",PrefBoolTrue),("extensions.update.enabled",PrefBoolFalse),("extensions.update.notifyUser",PrefBoolFalse),("network.manage-offline-status",PrefBoolFalse),("network.http.max-connections-per-server",PrefInteger10),("network.http.phishy-userpass-length",PrefInteger255),("offline-apps.allow_by_default",PrefBoolTrue),("prompts.tab_modal.enabled",PrefBoolFalse),("security.fileuri.origin_policy",PrefInteger3),("security.fileuri.strict_origin_policy",PrefBoolFalse),("security.warn_entering_secure",PrefBoolFalse),("security.warn_submit_insecure",PrefBoolFalse),("security.warn_entering_secure.show_once",PrefBoolFalse),("security.warn_entering_weak",PrefBoolFalse),("security.warn_entering_weak.show_once",PrefBoolFalse),("security.warn_leaving_secure",PrefBoolFalse),("security.warn_leaving_secure.show_once",PrefBoolFalse),("security.warn_submit_insecure",PrefBoolFalse),("security.warn_viewing_mixed",PrefBoolFalse),("security.warn_viewing_mixed.show_once",PrefBoolFalse),("signon.rememberSignons",PrefBoolFalse),("toolkit.networkmanager.disable",PrefBoolTrue),("toolkit.telemetry.enabled",PrefBoolFalse),("toolkit.telemetry.prompted",PrefInteger2),("toolkit.telemetry.rejected",PrefBoolTrue),("javascript.options.showInConsole",PrefBoolTrue),("browser.dom.window.dump.enabled",PrefBoolTrue),("webdriver_accept_untrusted_certs",PrefBoolTrue),("webdriver_enable_native_events",native_events),("webdriver_assume_untrusted_issuer",PrefBoolTrue),("dom.max_script_run_time",PrefInteger30)]where#ifdef darwin_HOST_OS native_events=PrefBoolFalse#elsenative_events=PrefBoolTrue#endifmkTemp::IOFilePathmkTemp=dod<-getTemporaryDirectorycreateTempDirectoryd""-- firefox prefs.js parserprefsParser=manyprefLineprefLine=dopadSpaces$string"user_pref("k<-prefKeypadSpaces$char','v<-prefValpadSpaces$string");"endOfLinereturn(k,v)wherespaces=AP.takeWhileisSpacepadSpacesp=spaces>>p>>spacesprefKey=strprefVal=boolVal<|>stringVal<|>intVal<|>doubleValwhereboolVal=boolTrue<|>boolFalseboolTrue=string"true">>return(PrefBoolTrue)boolFalse=string"false">>return(PrefBoolFalse)stringVal=PrefString<$>strintVal=PrefInteger<$>signeddecimaldoubleVal=PrefDouble<$>doublestr=char'"'>>AP.takeWhile(not.(=='"'))<*char'"'