Writing GCLI Commands

  • Revision slug: Tools/GCLI/Writing_GCLI_Commands
  • Revision title: Writing GCLI Commands
  • Revision id: 239020
  • Created:
  • Creator: joewalker
  • Is current revision? No
  • Comment 22 words added

Revision Content

Types

Out of the box we support:

  • The obvious basic types: string, number, boolean
  • setting, settingValue: i.e. prefs (settingValue is string, number, or boolean depending on the underlying type of the setting)
  • node: A single node on the page referenced by CSS expression
  • resource: A CSS or JavaScript file in the page

We're also working on date and file types, but they're not ready yet. For now you should use string.

Further Documentation

The GCLI project contains several pages of relevant documentation:

Access to Firefox

When a command is executed, it is passed 2 parameters, args and context. This are both explained in the GCLI documentation. Firefox provides an environment which contains the following:

  • chromeDocument: Allows access to the browser environment
  • contentDocument: Allows access to the current web page

So for example:

gcli.addCommand({
  name: 'closebrowserwindow',
  exec: function(args, context) {
    context.environment.chromeDocument.defaultView.close();
  }
});

Or:

gcli.addCommand({
  name: 'countdivs',
  exec: function(args, context) {
    return context.environment.contentDocument.querySelectorAll('div').length;
  }
});

Internationalization / Localization

The way GCLI does localization (for the web) doesn't work with commands that are shipped with Firefox.

To add a command that will only ever be used embedded in Firefox, this is the way to go. Your strings should be stored in browser/locales/en-US/chrome/browser/devtools/gclicommands.properties,
And you should access them using gcli.lookup(...) or gcli.lookupFormat().

For examples of existing commands, take a look in browser/devtools/webconsole/GcliCommands.jsm, which contains most of the
current GCLI commands. If you will be adding a number of new commands, then consider starting a new JSM.

Your command will then look something like this:

gcli.addCommand({
  name: 'greet',
  description: gcli.lookup("greetDesc")
  ...
});

Unit Tests

The command line comes with a framework to make it easy to write tests.

Your test() method will look something like this:

function test() {
  DeveloperToolbarTest.test(TEST_URI, function(browser, tab) {
    testThis(browser, tab);
    testThat(browser, tab);
    finish();
  });
}

There are 2 functions to help you testing - checkInputStatus() which checks to see that the command line can understand input properly and know what's allowed and disallowed, and exec() which checks that commands have the correct operation when executed.

checkInputStatus

All calls to checkInputStatus() need an input string in the typed property, and optionally a cursor property to specify where the cursor is when the checks are made. It will be common to leave this out, in which case the cursor is assumed to be at the end of the command line.

DeveloperToolbarTest.checkInputStatus({
  typed:  "echo hi",
  // checks go here
});

There are 3 states that are important to the command line:

  • VALID: Obvious - this is ok and ready to go
  • INCOMPLETE: This is not valid, but it's possible to make it valid just continuing to add characters
  • ERROR: This is wrong, and no amount of adding characters will help

The distinction between INCOMPLETE and ERROR is obvious when you consider the command 'cat README.TX' - assuming that the file to be displayed is README.TXT, as it stands it's not right, but we shoudn't be marking it as an error either.

These states apply to individual characters (and decide the underline state) and to the command line as a whole, which is generally the worst of these statuses, plus other checks.

There are 5 checks that can be made by checkInputStatus():

  • status: One of the strings "VALID", "ERROR", "INCOMPLETE"
  • emptyParameters: And array containing the parameters that still need typing
  • directTabText: What will be added to the command line when TAB is pressed if the completion is a simple extension of what is there already
  • arrowTabText: As above for when the completion text isn't an extension of what's there - e.g. for fuzzy matching
  • markup: One char for char on the input being the first letter of the status of that char. e.g. "VVVIIIEEE"

For example:

DeveloperToolbarTest.checkInputStatus({
  typed:  "edit c",
  markup: "VVVVVI",
  status: "ERROR",
  directTabText: "ss#style2",
  emptyParameters: [ " [line]" ],
});

exec

The exec() test is similar to checkInputStatus() except that it's more about checking the output and effect of running the command. The typed property is the same, however the checks are different:

  • args: an object that matches the args object passed to exec
  • outputMatch: A RegExp or Array of Regexps which should all match the textual content of the output
  • blankOutput: true if the command should produce no output
  • completed: false if the command should execute asynchronously

First example:

DeveloperToolbarTest.exec({
  typed: "console close",
  args: {},
  blankOutput: true,
});

ok(!(hud.hudId in imported.HUDService.hudReferences), "console closed");

Second Example:

DeveloperToolbarTest.exec({
  typed: "pref set devtools.editor.tabsize 9",
  args: {
    setting: imports.settings.getSetting("devtools.editor.tabsize"),
    value: 9
  },
  completed: true,
  outputMatch: [ /void your warranty/, /I promise/ ],
});

More Info

Some additional links which could be useful:

Revision Source

<h3>Types</h3>
<p>Out of the box we support:</p>
<ul> <li>The obvious basic types: <code>string</code>, <code>number</code>, <code>boolean</code></li> <li><code>setting</code>, <code>settingValue</code>: i.e. prefs (settingValue is string, number, or boolean depending on the underlying type of the setting)</li> <li><code>node</code>: A single node on the page referenced by CSS expression</li> <li><code>resource</code>: A CSS or JavaScript file in the page</li>
</ul>
<p>We're also working on date and file types, but they're not ready yet. For now you should use <code>string</code>.</p>
<h3>Further Documentation</h3>
<p>The GCLI project contains several pages of relevant documentation:</p>
<ul> <li><a class="link-https" href="https://github.com/joewalker/gcli/blob/master/docs/index.md" title="https://github.com/joewalker/gcli/blob/master/docs/index.md">Documentation Index</a></li> <li><a class="link-https" href="https://github.com/joewalker/gcli/blob/master/docs/writing-commands.md" title="https://github.com/joewalker/gcli/blob/master/docs/writing-commands.md">Writing Commands</a></li> <li><a class="link-https" href="https://github.com/joewalker/gcli/blob/master/docs/writing-types.md" title="https://github.com/joewalker/gcli/blob/master/docs/writing-types.md">Writing Types</a></li>
</ul>
<h2>Access to Firefox</h2>
<p>When a command is executed, it is passed 2 parameters, <code>args</code> and <code>context</code>. This are both <a class="link-https" href="https://github.com/joewalker/gcli/blob/master/docs/writing-commands.md#the-command-function-exec" title="https://github.com/joewalker/gcli/blob/master/docs/writing-commands.md#the-command-function-exec">explained in the GCLI documentation</a>. Firefox provides an environment which contains the following:</p>
<ul> <li><code>chromeDocument</code>: Allows access to the browser environment</li> <li><code>contentDocument</code>: Allows access to the current web page</li>
</ul>
<p>So for example:</p>
<pre>gcli.addCommand({
  name: 'closebrowserwindow',
  exec: function(args, context) {
    context.environment.chromeDocument.defaultView.close();
  }
});
</pre>
<p>Or:</p>
<pre>gcli.addCommand({
  name: 'countdivs',
  exec: function(args, context) {
    return context.environment.contentDocument.querySelectorAll('div').length;
  }
});
</pre>
<h2>Internationalization / Localization</h2>
<p>The way GCLI does localization (for the web) doesn't work with commands that are shipped with Firefox.</p>
<p>To add a command that will only ever be used embedded in Firefox, this is the way to go. Your strings should be stored in <code>browser/locales/en-US/chrome/browser/devtools/gclicommands.properties</code>,<br> And you should access them using <code>gcli.lookup(...)</code> or <code>gcli.lookupFormat()</code>.</p>
<p>For examples of existing commands, take a look in <code>browser/devtools/webconsole/GcliCommands.jsm</code>, which contains most of the<br> current GCLI commands. If you will be adding a number of new commands, then consider starting a new JSM.</p>
<p>Your command will then look something like this:</p>
<pre>gcli.addCommand({
  name: 'greet',
  description: gcli.lookup("greetDesc")
  ...
});
</pre>
<h2>Unit Tests</h2>
<p>The command line comes with a framework to make it easy to write tests.</p>
<p>Your test() method will look something like this:</p>
<pre>function test() {
  DeveloperToolbarTest.test(TEST_URI, function(browser, tab) {
    testThis(browser, tab);
    testThat(browser, tab);
    finish();
  });
}
</pre>
<p>There are 2 functions to help you testing - <code>checkInputStatus()</code> which checks to see that the command line can understand input properly and know what's allowed and disallowed, and <code>exec()</code> which checks that commands have the correct operation when executed.</p>
<h3>checkInputStatus</h3>
<p>All calls to <code>checkInputStatus()</code> need an input string in the <code>typed</code> property, and optionally a <code>cursor </code>property to specify where the cursor is when the checks are made. It will be common to leave this out, in which case the cursor is assumed to be at the end of the command line.</p>
<pre>DeveloperToolbarTest.checkInputStatus({
  typed:  "echo hi",
  // checks go here
});
</pre>
<p>There are 3 states that are important to the command line:</p>
<ul> <li><code>VALID</code>: Obvious - this is ok and ready to go</li> <li><code>INCOMPLETE</code>: This is not valid, but it's possible to make it valid just continuing to add characters</li> <li><code>ERROR</code>: This is wrong, and no amount of adding characters will help</li>
</ul>
<p>The distinction between <code>INCOMPLETE </code>and <code>ERROR </code>is obvious when you consider the command '<code>cat README.TX</code>' - assuming that the file to be displayed is <code>README.TXT</code>, as it stands it's not right, but we shoudn't be marking it as an error either.</p>
<p>These states apply to individual characters (and decide the underline state) and to the command line as a whole, which is generally the worst of these statuses, plus other checks.</p>
<p>There are 5 checks that can be made by <code>checkInputStatus()</code>:</p>
<ul> <li><code>status</code>: One of the strings "VALID", "ERROR", "INCOMPLETE"</li> <li><code>emptyParameters</code>: And array containing the parameters that still need typing</li> <li><code>directTabText</code>: What will be added to the command line when TAB is pressed if the completion is a simple extension of what is there already</li> <li><code>arrowTabText</code>: As above for when the completion text isn't an extension of what's there - e.g. for fuzzy matching</li> <li><code>markup</code>: One char for char on the input being the first letter of the status of that char. e.g. "VVVIIIEEE"</li>
</ul>
<p>For example:</p>
<pre>DeveloperToolbarTest.checkInputStatus({
  typed:  "edit c",
  markup: "VVVVVI",
  status: "ERROR",
  directTabText: "ss#style2",
  emptyParameters: [ " [line]" ],
});
</pre>
<h3>exec</h3>
<p>The <code>exec()</code> test is similar to <code>checkInputStatus()</code> except that it's more about checking the output and effect of running the command. The <code>typed </code>property is the same, however the checks are different:</p>
<ul> <li><code>args</code>: an object that matches the args object passed to exec</li> <li><code>outputMatch</code>: A RegExp or Array of Regexps which should all match the textual content of the output</li> <li><code>blankOutput</code>: true if the command should produce no output</li> <li><code>completed</code>: false if the command should execute asynchronously</li>
</ul>
<p>First example:</p>
<pre>DeveloperToolbarTest.exec({
  typed: "console close",
  args: {},
  blankOutput: true,
});

ok(!(hud.hudId in imported.HUDService.hudReferences), "console closed");
</pre>
<p>Second Example:</p>
<pre>DeveloperToolbarTest.exec({
  typed: "pref set devtools.editor.tabsize 9",
  args: {
    setting: imports.settings.getSetting("devtools.editor.tabsize"),
    value: 9
  },
  completed: true,
  outputMatch: [ /void your warranty/, /I promise/ ],
});
</pre>
<h3>More Info</h3>
<p>Some additional links which could be useful:</p>
<ul> <li>The <a class="link-https" href="https://hg.mozilla.org/mozilla-central/file/tip/browser/devtools/commandline/test/head.js" title="https://hg.mozilla.org/mozilla-central/file/tip/browser/devtools/commandline/test/head.js">test harness itself</a>, which could be useful to see how the tests are run</li> <li><a class="link-https" href="https://hg.mozilla.org/mozilla-central/file/tip/browser/devtools/commandline/test/" title="https://hg.mozilla.org/mozilla-central/file/tip/browser/devtools/commandline/test/">Examples of existing tests</a></li>
</ul>
Revert to this revision