diff --git a/src/firefox/index.js b/src/firefox/index.js index b9c1655c6d..6ba0a60c0a 100644 --- a/src/firefox/index.js +++ b/src/firefox/index.js @@ -239,8 +239,38 @@ export type UseProfileParams = { app?: PreferencesAppName, configureThisProfile?: ConfigureProfileFn, customPrefs?: FirefoxPreferences, + createProfileFinder?: typeof defaultCreateProfileFinder, }; +export interface IProfileFinder { + getPath(string): Promise; +} + +export function defaultCreateProfileFinder(userDirectoryPath?: string) { + const finder = new FirefoxProfile.Finder(userDirectoryPath); + const readProfiles = promisify(finder.readProfiles, finder); + const getPath = promisify(finder.getPath, finder); + return async (profileName: string) => { + const profilesIniPath = path.join( + userDirectoryPath || FirefoxProfile.Finder.locateUserDirectory(), + 'profiles.ini'); + try { + await fs.stat(profilesIniPath); + } catch (error) { + if (isErrorWithCode('ENOENT', error)) { + log.warn('No firefox profiles exist'); + } + } + await readProfiles(); + const hasProfileName = finder.profiles.filter( + (profileDef) => profileDef.Name === profileName).length !== 0; + if (hasProfileName) { + return await getPath(profileName); + } + }; +} + + // Use the target path as a Firefox profile without cloning it export async function useProfile( @@ -249,9 +279,38 @@ export async function useProfile( app, configureThisProfile = configureProfile, customPrefs = {}, + createProfileFinder = defaultCreateProfileFinder, }: UseProfileParams = {}, ): Promise { - const profile = new FirefoxProfile({destinationDirectory: profilePath}); + let destinationDirectory; + const getProfilePath = createProfileFinder(); + const dirExists = await isDirectory(profilePath); + if (dirExists) { + log.debug(`Using profile directory from "${profilePath}"`); + if (profilePath === getProfilePath('default') || + profilePath === getProfilePath('dev-edition-default')) { + throw new WebExtError( + `Cannot use profile at "${profilePath}"` + ); + } + destinationDirectory = profilePath; + } else { + log.debug(`Assuming ${profilePath} is a named profile`); + if (profilePath === 'default' || + profilePath === 'dev-edition-default') { + throw new WebExtError( + `Cannot use the blacklisted named profile "${profilePath}"` + ); + } + destinationDirectory = getProfilePath(profilePath); + if (!destinationDirectory) { + throw new UsageError( + `The request "${profilePath}" profile name + cannot be resolved to a profile path` + ); + } + } + const profile = new FirefoxProfile({destinationDirectory}); return await configureThisProfile(profile, {app, customPrefs}); } diff --git a/tests/unit/test-firefox/test.firefox.js b/tests/unit/test-firefox/test.firefox.js index 9c238951fd..b62198e9e3 100644 --- a/tests/unit/test-firefox/test.firefox.js +++ b/tests/unit/test-firefox/test.firefox.js @@ -300,30 +300,220 @@ describe('firefox', () => { describe('useProfile', () => { it('resolves to a FirefoxProfile instance', () => withBaseProfile( - (baseProfile) => { - const configureThisProfile = (profile) => Promise.resolve(profile); - return firefox.useProfile(baseProfile.path(), {configureThisProfile}) - .then((profile) => { - assert.instanceOf(profile, FirefoxProfile); + async (baseProfile) => { + try { + const app = 'fennec'; + const configureThisProfile = (profile) => Promise.resolve(profile); + const createProfileFinder = () => { + return (profilePath) => Promise.resolve(profilePath); + }; + const profile = await firefox.useProfile(baseProfile.path(), { + app, + configureThisProfile, + createProfileFinder, }); + assert.instanceOf(profile, FirefoxProfile); + } catch (error) { + throw error; + } } - )); + )); it('configures a profile', () => withBaseProfile( - (baseProfile) => { - const configureThisProfile = - sinon.spy((profile) => Promise.resolve(profile)); - const app = 'fennec'; - const profilePath = baseProfile.path(); - return firefox.useProfile(profilePath, {app, configureThisProfile}) - .then((profile) => { - assert.equal(configureThisProfile.called, true); - assert.equal(configureThisProfile.firstCall.args[0], profile); - assert.equal(configureThisProfile.firstCall.args[1].app, app); - }); + async (baseProfile) => { + try { + const app = 'fennec'; + const configureThisProfile = + sinon.spy((profile) => Promise.resolve(profile)); + const createProfileFinder = () => { + return (profilePath) => Promise.resolve(profilePath); + }; + const profilePath = baseProfile.path(); + const profile = await firefox.useProfile(profilePath, + { + app, + configureThisProfile, + createProfileFinder, + }); + assert.equal(configureThisProfile.called, true); + assert.equal(configureThisProfile.firstCall.args[0], profile); + assert.equal(configureThisProfile.firstCall.args[1].app, app); + } catch (error) { + throw error; + } } )); + // it('configures a named profile', async () => { + // try { + // const app = 'fennec'; + // const configureThisProfile = + // sinon.spy((profile) => Promise.resolve(profile)); + // const profileName = 'test'; + // const profileFinder = { + // getPath: sinon.spy((name) => + // Promise.resolve(name)), + // hasProfileName: () => Promise.resolve(true), + // }; + // const createProfileFinder = () => profileFinder; + // const profile = await firefox.useProfile(profileName, + // { + // app, + // configureThisProfile, + // createProfileFinder, + // }); + // assert.equal(configureThisProfile.called, true); + // assert.equal(configureThisProfile.firstCall.args[0], profile); + // assert.equal(configureThisProfile.firstCall.args[1].app, app); + // assert.equal(profileFinder.getPath.callCount, 3); + // } catch (error) { + // throw error; + // } + // } + // ); + // + // it('configures a profile with given path', () => withTempDir( + // async (tmpDir) => { + // try { + // const app = 'fennec'; + // const configureThisProfile = + // sinon.spy((profile) => Promise.resolve(profile)); + // const profilePath = tmpDir.path(); + // const profileFinder = { + // getPath: sinon.spy((pathToProfile) => + // Promise.resolve(pathToProfile)), + // hasProfileName: () => Promise.resolve(true), + // }; + // const createProfileFinder = () => profileFinder; + // const profile = await firefox.useProfile(profilePath, + // { + // app, + // configureThisProfile, + // createProfileFinder, + // }); + // assert.equal(configureThisProfile.called, true); + // assert.equal(configureThisProfile.firstCall.args[0], profile); + // assert.equal(configureThisProfile.firstCall.args[1].app, app); + // assert.equal(profileFinder.getPath.callCount, 2); + // } catch (error) { + // throw error; + // } + // } + // )); + // + // it('does not configure named profile default', async () => { + // try { + // const app = 'fennec'; + // const configureThisProfile = + // sinon.spy((profile) => Promise.resolve(profile)); + // const createProfileFinder = () => { + // return { + // getPath: () => Promise.resolve(), + // hasProfileName: () => Promise.resolve(true), + // }; + // }; + // const profile = await firefox.useProfile('default', + // { + // app, + // configureThisProfile, + // createProfileFinder, + // }); + // assert.equal(configureThisProfile.called, true); + // assert.equal(configureThisProfile.firstCall.args[0], profile); + // assert.equal(configureThisProfile.firstCall.args[1].app, app); + // } catch (error) { + // assert.instanceOf(error, WebExtError); + // assert.match(error.message, + // /Cannot use the blacklisted named profile "default"+/); + // } + // }); + // + // it('does not configure named profile dev-edition-default', async () => { + // try { + // const app = 'fennec'; + // const configureThisProfile = + // sinon.spy((profile) => Promise.resolve(profile)); + // const createProfileFinder = () => { + // return { + // getPath: () => Promise.resolve(), + // hasProfileName: () => Promise.resolve(true), + // }; + // }; + // const profile = await firefox.useProfile('dev-edition-default', + // { + // app, + // configureThisProfile, + // createProfileFinder, + // }); + // assert.equal(configureThisProfile.called, true); + // assert.equal(configureThisProfile.firstCall.args[0], profile); + // assert.equal(configureThisProfile.firstCall.args[1].app, app); + // } catch (error) { + // assert.instanceOf(error, WebExtError); + // assert.match(error.message, + // /Cannot use the blacklisted named profile "dev-edition-default"+/); + // } + // }); + // + // it('does not configure profile at default', () => withTempDir( + // async (tmpDir) => { + // try { + // const app = 'fennec'; + // const configureThisProfile = + // sinon.spy((profile) => Promise.resolve(profile)); + // const defaultPath = tmpDir.path(); + // const createProfileFinder = () => { + // return { + // getPath: () => Promise.resolve(defaultPath), + // hasProfileName: () => Promise.resolve(true), + // }; + // }; + // const profile = await firefox.useProfile(defaultPath, + // { + // app, + // configureThisProfile, + // createProfileFinder, + // }); + // assert.equal(configureThisProfile.called, true); + // assert.equal(configureThisProfile.firstCall.args[0], profile); + // assert.equal(configureThisProfile.firstCall.args[1].app, app); + // } catch (error) { + // assert.instanceOf(error, WebExtError); + // assert.match(error.message, + // /Cannot use profile at+/); + // } + // } + // )); + // + // it('does not configure profile at dev-edition-default', () => withTempDir( + // async (tmpDir) => { + // try { + // const app = 'fennec'; + // const configureThisProfile = + // sinon.spy((profile) => Promise.resolve(profile)); + // const defaultDevPath = tmpDir.path(); + // const createProfileFinder = () => { + // return { + // getPath: () => Promise.resolve(defaultDevPath), + // hasProfileName: () => Promise.resolve(true), + // }; + // }; + // const profile = await firefox.useProfile(defaultDevPath, + // { + // app, + // configureThisProfile, + // createProfileFinder, + // }); + // assert.equal(configureThisProfile.called, true); + // assert.equal(configureThisProfile.firstCall.args[0], profile); + // assert.equal(configureThisProfile.firstCall.args[1].app, app); + // } catch (error) { + // assert.instanceOf(error, WebExtError); + // assert.match(error.message, + // /Cannot use profile at+/); + // } + // } + // )); }); describe('configureProfile', () => { @@ -585,4 +775,39 @@ describe('firefox', () => { }); }); + +// describe('defaultCreateProfileFinder', () => { +// it('gives a warning if no firefox profiles exist', () => withTempDir( +// async (tmpDir) => { +// try { +// const profilesPath = tmpDir.path(); +// const profileFinder = firefox.defaultCreateProfileFinder( +// profilesPath); +// const profileExists = await profileFinder.hasProfileName('test'); +// assert.equal(profileExists, false); +// } catch (e) { +// throw e; +// } +// } +// )); +// it('gives a warning if no firefox profiles exist', () => withTempDir( +// async (tmpDir) => { +// try { +// const profilesPath = tmpDir.path(); +// const profileFinder = firefox.defaultCreateProfileFinder( +// profilesPath); +// const profilesIniPath = path.join(profilesPath, 'profiles.ini'); +// const profileContents = `[Profile0] +// Name=test +// IsRelative=1 +// Path=fake-profile.test`; +// await fs.writeFile(profilesIniPath, profileContents); +// const profileExists = await profileFinder.hasProfileName('test'); +// assert.equal(profileExists, true); +// } catch (e) { +// throw e; +// } +// } +// )); +// }); });