Skip to content

Commit

Permalink
Adds run command -> install and launch extension
Browse files Browse the repository at this point in the history
  • Loading branch information
kumar303 committed Mar 2, 2016
1 parent ab5578f commit 373746e
Show file tree
Hide file tree
Showing 14 changed files with 633 additions and 4 deletions.
4 changes: 1 addition & 3 deletions .flowconfig
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ log.file=./artifacts/flow.log
# dependencies. Ignoring these speeds up the standalone flow command by 2x.
#
# DO NOT EDIT: ignored-dependencies: this list is auto-generated by a grunt task
# 9877100539d1b841afc8ba07fc895ed11ce2f9d7ed224780cd9ff3bd30887568
# 181c34cd94fd821760c6050e5e2c75cdc97a8a7bbcfd48b92c462edc58c63734
.*/node_modules/.bin.*
.*/node_modules/Base64.*
.*/node_modules/JSV.*
Expand Down Expand Up @@ -282,7 +282,6 @@ log.file=./artifacts/flow.log
.*/node_modules/find-up.*
.*/node_modules/find-versions.*
.*/node_modules/findup-sync.*
.*/node_modules/firefox-profile.*
.*/node_modules/first-chunk-stream.*
.*/node_modules/flat-cache.*
.*/node_modules/flow-bin.*
Expand All @@ -297,7 +296,6 @@ log.file=./artifacts/flow.log
.*/node_modules/fs-promise.*
.*/node_modules/fs-readdir-recursive.*
.*/node_modules/fsevents.*
.*/node_modules/fx-runner.*
.*/node_modules/gaze.*
.*/node_modules/generate-function.*
.*/node_modules/generate-object-property.*
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
"babel-polyfill": "6.6.1",
"es6-error": "2.0.2",
"es6-promisify": "3.0.0",
"firefox-profile": "^0.3.11",
"fx-runner": "1.0.1",
"stream-to-promise": "1.1.0",
"tmp": "0.0.28",
"yargs": "4.2.0",
Expand Down
4 changes: 3 additions & 1 deletion src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import path from 'path';
import {readFileSync} from 'fs';

import build from './cmd/build';
import run from './cmd/run';
import {Program} from './program';


Expand Down Expand Up @@ -39,7 +40,8 @@ export function main() {
});

program
.command('build', 'Create a web extension package from source', build);
.command('build', 'Create a web extension package from source', build)
.command('run', 'Run the web extension', run);

program.run();
}
35 changes: 35 additions & 0 deletions src/cmd/run.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/* @flow */
import buildExtension from './build';
import {ProgramOptions} from '../program';
import * as defaultFirefox from '../firefox';
import {withTempDir} from '../util/temp-dir';
import getValidatedManifest from '../util/manifest';


export default function run(
{sourceDir}: ProgramOptions,
{firefox=defaultFirefox}: Object = {}): Promise {

console.log(`Running web extension from ${sourceDir}`);

return getValidatedManifest(sourceDir)
.then((manifestData) => withTempDir(
(tmpDir) =>
Promise.all([
buildExtension({sourceDir, buildDir: tmpDir.path()},
{manifestData}),
firefox.createProfile(),
])
.then((result) => {
let [buildResult, profile] = result;
return firefox.installExtension(
{
manifestData,
extensionPath: buildResult.extensionPath,
profile,
})
.then(() => profile);
})
.then((profile) => firefox.run(profile))
));
}
133 changes: 133 additions & 0 deletions src/firefox/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/* @flow */
import nodeFs from 'fs';
import path from 'path';
import defaultFxRunner from 'fx-runner/lib/run';
import FirefoxProfile from 'firefox-profile';
import streamToPromise from 'stream-to-promise';

import * as fs from '../util/promised-fs';
import {onlyErrorsWithCode, WebExtError} from '../errors';
import {getPrefs as defaultPrefGetter} from './preferences';


export const defaultFirefoxEnv = {
XPCOM_DEBUG_BREAK: 'stack',
NS_TRACE_MALLOC_DISABLE_STACKS: '1',
};

export function run(
profile: FirefoxProfile, {fxRunner=defaultFxRunner}: Object = {}): Promise {

console.log(`Running Firefox with profile at ${profile.path()}`);
return fxRunner(
{
'binary': null,
'binary-args': null,
'no-remote': true,
'foreground': true,
'profile': profile.path(),
'env': {
...process.env,
...defaultFirefoxEnv,
},
'verbose': true,
})
.then((results) => {
return new Promise((resolve) => {
let firefox = results.process;

console.log(`Executing Firefox binary: ${results.binary}`);
console.log(`Executing Firefox with args: ${results.args.join(' ')}`);

firefox.on('error', (error) => {
// TODO: show a nice error when it can't find Firefox.
// if (/No such file/.test(err) || err.code === 'ENOENT') {
console.log(`Firefox error: ${error}`);
throw error;
});

firefox.stderr.on('data', (data) => {
console.log(`stdout: ${data.toString().trim()}`);
});

firefox.stdout.on('data', function(data) {
console.log(`stderr: ${data.toString().trim()}`);
});

firefox.on('close', () => {
console.log('Firefox closed');
resolve();
});
});
});
}


export function createProfile(
app: string = 'firefox',
{getPrefs=defaultPrefGetter}: Object = {}): Promise {

return new Promise((resolve) => {
// The profile is created in a self-destructing temp dir.
// TODO: add option to copy a profile.
// https://github.com/mozilla/web-ext/issues/69
let profile = new FirefoxProfile();

// Set default preferences.
// TODO: support custom preferences.
// https://github.com/mozilla/web-ext/issues/88
let prefs = getPrefs(app);
Object.keys(prefs).forEach((pref) => {
profile.setPreference(pref, prefs[pref]);
});
profile.updatePreferences();

resolve(profile);
});
}


class InstallationConfig {
manifestData: Object;
profile: FirefoxProfile;
extensionPath: string;
}

export function installExtension(
{manifestData, profile, extensionPath}: InstallationConfig): Promise {

// This more or less follows
// https://github.com/saadtazi/firefox-profile-js/blob/master/lib/firefox_profile.js#L531
// (which is broken for web extensions).
// TODO: maybe uplift a patch that supports web extensions instead?

return new Promise(
(resolve) => {
if (!profile.extensionsDir) {
throw new WebExtError('profile.extensionsDir was unexpectedly empty');
}
resolve(fs.stat(profile.extensionsDir));
})
.catch(onlyErrorsWithCode('ENOENT', () => {
console.log(`Creating extensions directory: ${profile.extensionsDir}`);
return fs.mkdir(profile.extensionsDir);
}))
.then(() => {
let readStream = nodeFs.createReadStream(extensionPath);
let id = manifestData.applications.gecko.id;

// TODO: also support copying a directory of code to this
// destination. That is, to name the directory ${id}.
// https://github.com/mozilla/web-ext/issues/70
let destPath = path.join(profile.extensionsDir, `${id}.xpi`);
let writeStream = nodeFs.createWriteStream(destPath);

console.log(`Copying ${extensionPath} to ${destPath}`);
readStream.pipe(writeStream);

return Promise.all([
streamToPromise(readStream),
streamToPromise(writeStream),
]);
});
}
90 changes: 90 additions & 0 deletions src/firefox/preferences.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/* @flow */
import {WebExtError} from '../errors';


export function getPrefs(app: string = 'firefox'): Object {
let appPrefs = prefs[app];
if (!appPrefs) {
throw new WebExtError(`Unsupported application: ${app}`);
}
return {
...prefs.common,
...appPrefs,
};
}


var prefs = {};

prefs.common = {
// Allow debug output via dump to be printed to the system console
'browser.dom.window.dump.enabled': true,
// Warn about possibly incorrect code.
'javascript.options.strict': true,
'javascript.options.showInConsole': true,

// Allow remote connections to the debugger.
'devtools.debugger.remote-enabled' : true,

// Turn off platform logging because it is a lot of info.
'extensions.logging.enabled': false,

// Disable extension updates and notifications.
'extensions.checkCompatibility.nightly' : false,
'extensions.update.enabled' : false,
'extensions.update.notifyUser' : false,

// From:
// http://hg.mozilla.org/mozilla-central/file/1dd81c324ac7/build/automation.py.in//l372
// Only load extensions from the application and user profile.
// AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_APPLICATION
'extensions.enabledScopes' : 5,
// Disable metadata caching for installed add-ons by default.
'extensions.getAddons.cache.enabled' : false,
// Disable intalling any distribution add-ons.
'extensions.installDistroAddons' : false,
// Allow installing extensions dropped into the profile folder.
'extensions.autoDisableScopes' : 10,

// Disable app update.
'app.update.enabled' : false,

// Point update checks to a nonexistent local URL for fast failures.
'extensions.update.url': 'http://localhost/extensions-dummy/updateURL',
'extensions.blocklist.url':
'http://localhost/extensions-dummy/blocklistURL',

// Make sure opening about:addons won't hit the network.
'extensions.webservice.discoverURL' :
'http://localhost/extensions-dummy/discoveryURL',

// Allow unsigned add-ons.
'xpinstall.signatures.required' : false,
};

// Prefs specific to Firefox for Android.
prefs.fennec = {
'browser.console.showInPanel': true,
'browser.firstrun.show.uidiscovery': false,
};

// Prefs specific to Firefox for desktop.
prefs.firefox = {
'browser.startup.homepage' : 'about:blank',
'startup.homepage_welcome_url' : 'about:blank',
'startup.homepage_welcome_url.additional' : '',
'devtools.errorconsole.enabled' : true,
'devtools.chrome.enabled' : true,

// From:
// http://hg.mozilla.org/mozilla-central/file/1dd81c324ac7/build/automation.py.in//l388
// Make url-classifier updates so rare that they won't affect tests.
'urlclassifier.updateinterval' : 172800,
// Point the url-classifier to a nonexistent local URL for fast failures.
'browser.safebrowsing.provider.0.gethashURL' :
'http://localhost/safebrowsing-dummy/gethash',
'browser.safebrowsing.provider.0.keyURL' :
'http://localhost/safebrowsing-dummy/newkey',
'browser.safebrowsing.provider.0.updateURL' :
'http://localhost/safebrowsing-dummy/update',
};
1 change: 1 addition & 0 deletions src/util/promised-fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ import {promisify} from './es6-modules';

export const stat = promisify(fs.stat);
export const mkdir = promisify(fs.mkdir);
export const readdir = promisify(fs.readdir);
export const readFile = promisify(fs.readFile);
export const writeFile = promisify(fs.writeFile);
Binary file added tests/fixtures/minimal_extension-1.0.xpi
Binary file not shown.
60 changes: 60 additions & 0 deletions tests/helpers.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import path from 'path';
import sinon from 'sinon';
import {promisify} from '../src/util/es6-modules';
import yauzl from 'yauzl';

Expand Down Expand Up @@ -72,3 +73,62 @@ export function makeSureItFails() {
throw new Error('This test unexpectedly succeeded without an error');
};
}


/*
* Return a fake version of an object for testing.
*
* The fake object will contain stub implementations of
* all original methods. Each method will be wrapped in
* a sinon.spy() for inspection.
*
* You can optionally provide implementations for one or
* more methods.
*
* Unlike similar sinon helpers, this *does not* touch the
* original object so there is no need to tear down any
* patches afterwards.
*
* Usage:
*
* let fakeProcess = fake(process, {
* cwd: () => '/some/directory',
* });
*
* // Use the object in real code:
* fakeProcess.cwd();
*
* // Make assertions about methods that
* // were on the original object:
* assert.equal(fakeProcess.exit.called, true);
*
*/
export function fake(original, methods={}) {
var stub = {};

// Provide stubs for all original members:
Object.keys(original).forEach((key) => {
if (typeof original[key] === 'function') {
stub[key] = () => {
console.warn(
`Running stubbed function ${key} (default implementation)`);
};
}
});

// Provide custom implementations, if necessary.
Object.keys(methods).forEach((key) => {
if (!original[key]) {
throw new Error(
`Cannot define method "${key}"; it does not exist on the original`);
}
stub[key] = methods[key];
});

// Wrap all implementations in spies.
Object.keys(stub).forEach((key) => {
stub[key] = sinon.spy(stub[key]);
});

return stub;
}
Loading

0 comments on commit 373746e

Please sign in to comment.