Skip to content

Commit

Permalink
feat: run watches source, reloads addon in Firefox
Browse files Browse the repository at this point in the history
  • Loading branch information
kumar303 committed May 2, 2016
1 parent 47fb0ac commit 61c5087
Show file tree
Hide file tree
Showing 8 changed files with 513 additions and 78 deletions.
199 changes: 170 additions & 29 deletions src/cmd/run.js
Original file line number Diff line number Diff line change
@@ -1,45 +1,186 @@
/* @flow */
import buildExtension from './build';
import * as defaultFirefox from '../firefox';
import defaultFirefoxConnector from '../firefox/remote';
import {onlyErrorsWithCode} from '../errors';
import {withTempDir} from '../util/temp-dir';
import {createLogger} from '../util/logger';
import getValidatedManifest from '../util/manifest';
import defaultSourceWatcher from '../watcher';

const log = createLogger(__filename);


export function defaultWatcherCreator(
{profile, client, sourceDir, artifactsDir, createRunner,
onSourceChange=defaultSourceWatcher}: Object): Object {
return onSourceChange({
sourceDir, artifactsDir, onChange: () => createRunner(
(runner) => runner.buildExtension()
.then((buildResult) => runner.install(buildResult, {profile}))
.then(() => {
log.debug('Attempting to reload extension');
const addonId = runner.manifestData.applications.gecko.id;
log.debug(`Reloading add-on ID ${addonId}`);
return client.reloadAddon(addonId);
})
.catch((error) => {
log.error(error.stack);
throw error;
})
),
});
}


export function defaultReloadStrategy(
{firefox, profile, sourceDir, artifactsDir, createRunner}: Object,
{connectToFirefox=defaultFirefoxConnector,
maxRetries=25, retryInterval=120,
createWatcher=defaultWatcherCreator}: Object = {}): Promise {
var watcher;
var client;
var retries = 0;

firefox.on('close', () => {
if (client) {
client.disconnect();
}
if (watcher) {
watcher.close();
}
});

function establishConnection() {
return new Promise((resolve, reject) => {
connectToFirefox()
.then((connectedClient) => {
log.debug('Connected to the Firefox debugger');
client = connectedClient;
watcher = createWatcher({
profile, client, sourceDir, artifactsDir, createRunner,
});
resolve();
})
.catch(onlyErrorsWithCode('ECONNREFUSED', (error) => {
if (retries >= maxRetries) {
log.debug('Connect to Firefox debugger: too many retries');
throw error;
} else {
setTimeout(() => {
retries ++;
log.debug(
`Retrying Firefox (${retries}); connection error: ${error}`);
resolve(establishConnection());
}, retryInterval);
}
}))
.catch((error) => {
log.error(error.stack);
reject(error);
});
});
}

return establishConnection();
}


export default function run(
{sourceDir, firefoxBinary, firefoxProfile}: Object,
{firefox=defaultFirefox}: Object = {}): Promise {
{sourceDir, artifactsDir, firefoxBinary, firefoxProfile, noReload}: Object,
{firefox=defaultFirefox, reloadStrategy=defaultReloadStrategy}
: Object = {}): Promise {

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

return getValidatedManifest(sourceDir)
.then((manifestData) => withTempDir(
(tmpDir) =>
Promise.all([
buildExtension({sourceDir, artifactsDir: tmpDir.path()},
{manifestData}),
new Promise((resolve) => {
if (firefoxProfile) {
log.debug(`Copying Firefox profile from ${firefoxProfile}`);
resolve(firefox.copyProfile(firefoxProfile));
} else {
log.debug('Creating new Firefox profile');
resolve(firefox.createProfile());
}
}),
])
.then((result) => {
let [buildResult, profile] = result;
return firefox.installExtension(
{
manifestData,
extensionPath: buildResult.extensionPath,
profile,
})
.then(() => profile);
function createRunner(callback) {
return getValidatedManifest(sourceDir)
.then((manifestData) => withTempDir(
(tmpDir) => {
const runner = new ExtensionRunner({
sourceDir,
firefox,
firefoxBinary,
tmpDirPath: tmpDir.path(),
manifestData,
firefoxProfile,
});
return callback(runner);
}
));
}

return createRunner(
(runner) => runner.buildExtension()
.then((buildResult) => runner.install(buildResult))
.then((profile) => runner.run(profile).then((firefox) => {
return {firefox, profile};
}))
.then(({firefox, profile}) => {
if (noReload) {
log.debug('Extension auto-reloading has been disabled');
} else {
log.debug('Reloading extension when the source changes');
reloadStrategy(
{firefox, profile, sourceDir, artifactsDir, createRunner});
}
return firefox;
})
);
}


export class ExtensionRunner {
sourceDir: string;
tmpDirPath: string;
manifestData: Object;
firefoxProfile: Object;
firefox: Object;
firefoxBinary: string;

constructor({firefox, sourceDir, tmpDirPath, manifestData,
firefoxProfile, firefoxBinary}: Object) {
this.sourceDir = sourceDir;
this.tmpDirPath = tmpDirPath;
this.manifestData = manifestData;
this.firefoxProfile = firefoxProfile;
this.firefox = firefox;
this.firefoxBinary = firefoxBinary;
}

buildExtension(): Promise {
const {sourceDir, tmpDirPath, manifestData} = this;
return buildExtension({sourceDir, artifactsDir: tmpDirPath},
{manifestData});
}

getProfile(): Promise {
const {firefox, firefoxProfile} = this;
return new Promise((resolve) => {
if (firefoxProfile) {
log.debug(`Copying Firefox profile from ${firefoxProfile}`);
resolve(firefox.copyProfile(firefoxProfile));
} else {
log.debug('Creating new Firefox profile');
resolve(firefox.createProfile());
}
});
}

install(buildResult: Object, {profile}: Object = {}): Promise {
const {firefox, manifestData} = this;
return Promise.resolve(profile ? profile : this.getProfile())
.then((profile) => firefox.installExtension(
{
manifestData,
extensionPath: buildResult.extensionPath,
profile,
})
.then((profile) => firefox.run(profile, {firefoxBinary}))
));
.then(() => profile));
}

run(profile: Object): Promise {
const {firefox, firefoxBinary} = this;
return firefox.run(profile, {firefoxBinary});
}
}
13 changes: 8 additions & 5 deletions src/firefox/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,17 @@ export const defaultFirefoxEnv = {
*/
export function run(
profile: FirefoxProfile,
{fxRunner=defaultFxRunner, firefoxBinary}: Object = {}): Promise {
{fxRunner=defaultFxRunner, firefoxBinary, binaryArgs}
: Object = {}): Promise {

log.info(`Running Firefox with profile at ${profile.path()}`);
return fxRunner(
{
// if this is falsey, fxRunner tries to find the default one.
'binary': firefoxBinary,
'binary-args': null,
'no-remote': true,
'binary-args': binaryArgs,
'no-remote': false,
'listen': '6000',
'foreground': true,
'profile': profile.path(),
'env': {
Expand All @@ -48,7 +50,7 @@ export function run(
let firefox = results.process;

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

firefox.on('error', (error) => {
// TODO: show a nice error when it can't find Firefox.
Expand All @@ -67,8 +69,9 @@ export function run(

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

resolve(firefox);
});
});
}
Expand Down
2 changes: 2 additions & 0 deletions src/firefox/preferences.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ prefs.common = {

// Allow remote connections to the debugger.
'devtools.debugger.remote-enabled' : true,
// Disable the prompt for allowing connections.
'devtools.debugger.prompt-connection' : false,

// Turn off platform logging because it is a lot of info.
'extensions.logging.enabled': false,
Expand Down
6 changes: 5 additions & 1 deletion src/program.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export class Program {
}

command(name: string, description: string, executor: Function,
commandOptions: ?Object): Program {
commandOptions: Object = {}): Program {
this.yargs.command(name, description, (yargs) => {
if (!commandOptions) {
return;
Expand Down Expand Up @@ -184,6 +184,10 @@ Example: $0 --help run.
demand: false,
type: 'string',
},
'no-reload': {
describe: 'Do not reload the extension as the source changes',
type: 'boolean',
},
});

return program.run(runOptions);
Expand Down
12 changes: 8 additions & 4 deletions src/watcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Watchpack from 'watchpack';
import debounce from 'debounce';

import {createLogger} from './util/logger';
import {FileFilter} from './cmd/build';

const log = createLogger(__filename);

Expand All @@ -21,16 +22,19 @@ export default function onSourceChange(
log.debug(`Watching for file changes in ${sourceDir}`);
watcher.watch([], [sourceDir], Date.now());

// TODO: support windows See:
// http://stackoverflow.com/questions/10021373/what-is-the-windows-equivalent-of-process-onsigint-in-node-js
// TODO: support interrupting the watcher on Windows.
// https://github.com/mozilla/web-ext/issues/225
process.on('SIGINT', () => watcher.close());
return watcher;
}


export function proxyFileChanges(
{artifactsDir, onChange, filePath, shouldWatchFile=() => true}
: Object) {
{artifactsDir, onChange, filePath, shouldWatchFile}: Object) {
if (!shouldWatchFile) {
const fileFilter = new FileFilter();
shouldWatchFile = (...args) => fileFilter.wantFile(...args);
}
if (filePath.indexOf(artifactsDir) === 0 || !shouldWatchFile(filePath)) {
log.debug(`Ignoring change to: ${filePath}`);
} else {
Expand Down
Loading

0 comments on commit 61c5087

Please sign in to comment.