Merge fx-team to m-c.
authorRyan VanderMeulen <ryanvm@gmail.com>
Tue, 21 May 2013 08:15:23 -0400
changeset 143918 957f5f047a9461982930b0534fe2755d5c44ca41
parent 143908 d502187daa5c02e682c30bd903bfd120da157288 (current diff)
parent 143917 6cf5ac92530bbec37fabe1518ec897dcf512d6c4 (diff)
child 143919 c96b50b62e75aa4814a6672abfb28055ec1484ce
child 143997 057f451940434f9932a0e600acc899b698a0b37b
child 144017 d1cd5199bf45f568ea05bd8b04c9c17bd80380bd
push id2697
push userbbajaj@mozilla.com
push dateMon, 05 Aug 2013 18:49:53 +0000
treeherdermozilla-beta@dfec938c7b63 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone24.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge fx-team to m-c.
browser/devtools/commandline/test/browser_gcli_keyboard.js
--- a/browser/devtools/commandline/BuiltinCommands.jsm
+++ b/browser/devtools/commandline/BuiltinCommands.jsm
@@ -9,17 +9,17 @@ const BRAND_SHORT_NAME = Cc["@mozilla.or
                            .createBundle("chrome://branding/locale/brand.properties")
                            .GetStringFromName("brandShortName");
 
 this.EXPORTED_SYMBOLS = [ "CmdAddonFlags", "CmdCommands", "DEFAULT_DEBUG_PORT", "connect" ];
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
-Cu.import("resource://gre/modules/osfile.jsm")
+Cu.import("resource://gre/modules/osfile.jsm");
 
 Cu.import("resource://gre/modules/devtools/gcli.jsm");
 Cu.import("resource:///modules/devtools/shared/event-emitter.js");
 
 XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
                                   "resource:///modules/devtools/gDevTools.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "devtools",
                                   "resource://gre/modules/devtools/Loader.jsm");
@@ -169,18 +169,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                  + ")") :
                 "",
               toggleActionName: isActiveForToggle(addon) ? "disable": "enable",
               toggleActionMessage: isActiveForToggle(addon) ?
                 gcli.lookup("addonListOutDisable") :
                 gcli.lookup("addonListOutEnable")
             };
           }),
-          onclick: createUpdateHandler(context),
-          ondblclick: createExecuteHandler(context)
+          onclick: context.update,
+          ondblclick: context.updateExec
         }
       });
     }
   });
 
   var addonsListHtml = "" +
         "<table>" +
         " <caption>${header}</caption>" +
@@ -339,66 +339,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
         AddonManager.getAllAddons(disable.bind(deferred, aArgs.name));
         return deferred.promise;
       }
     });
     module.CmdAddonFlags.addonsLoaded = true;
     Services.obs.notifyObservers(null, "gcli_addon_commands_ready", null);
   });
 
-  /**
-   * Helper to find the 'data-command' attribute and call some action on it.
-   * @see |updateCommand()| and |executeCommand()|
-   */
-  function withCommand(element, action) {
-    var command = element.getAttribute("data-command");
-    if (!command) {
-      command = element.querySelector("*[data-command]")
-        .getAttribute("data-command");
-    }
-
-    if (command) {
-      action(command);
-    }
-    else {
-      console.warn("Missing data-command for " + util.findCssSelector(element));
-    }
-  }
-
-  /**
-   * Create a handler to update the requisition to contain the text held in the
-   * first matching data-command attribute under the currentTarget of the event.
-   * @param context Either a Requisition or an ExecutionContext or another object
-   * that contains an |update()| function that follows a similar contract.
-   */
-  function createUpdateHandler(context) {
-    return function(ev) {
-      withCommand(ev.currentTarget, function(command) {
-        context.update(command);
-      });
-    }
-  }
-
-  /**
-   * Create a handler to execute the text held in the data-command attribute
-   * under the currentTarget of the event.
-   * @param context Either a Requisition or an ExecutionContext or another object
-   * that contains an |update()| function that follows a similar contract.
-   */
-  function createExecuteHandler(context) {
-    return function(ev) {
-      withCommand(ev.currentTarget, function(command) {
-        context.exec({
-          visible: true,
-          typed: command
-        });
-      });
-    }
-  }
-
 }(this));
 
 /* CmdCalllog -------------------------------------------------------------- */
 
 (function(module) {
   XPCOMUtils.defineLazyGetter(this, "Debugger", function() {
     let JsDebugger = {};
     Components.utils.import("resource://gre/modules/jsdebugger.jsm", JsDebugger);
@@ -921,18 +871,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                        (noAttrs ? gcli.lookup("cookieListOutNone") : ' ');
       }
 
       return context.createView({
         html: cookieListHtml,
         data: {
           options: { allowEval: true },
           cookies: cookies,
-          onclick: createUpdateHandler(context),
-          ondblclick: createExecuteHandler(context),
+          onclick: context.update,
+          ondblclick: context.updateExec
         }
       });
     }
   });
 
   /**
    * The cookie 'expires' value needs converting into something more readable
    */
@@ -970,17 +920,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   /**
    * 'cookie list' command
    */
   gcli.addCommand({
     name: "cookie list",
     description: gcli.lookup("cookieListDesc"),
     manual: gcli.lookup("cookieListManual"),
     returnType: "cookies",
-    exec: function Command_cookieList(args, context) {
+    exec: function(args, context) {
       let host = context.environment.document.location.host;
       if (host == null || host == "") {
         throw new Error(gcli.lookup("cookieListOutNonePage"));
       }
 
       let enm = cookieMgr.getCookiesFromHost(host);
 
       let cookies = [];
@@ -1013,17 +963,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
     manual: gcli.lookup("cookieRemoveManual"),
     params: [
       {
         name: "name",
         type: "string",
         description: gcli.lookup("cookieRemoveKeyDesc"),
       }
     ],
-    exec: function Command_cookieRemove(args, context) {
+    exec: function(args, context) {
       let host = context.environment.document.location.host;
       let enm = cookieMgr.getCookiesFromHost(host);
 
       let cookies = [];
       while (enm.hasMoreElements()) {
         let cookie = enm.getNext().QueryInterface(Ci.nsICookie);
         if (isCookieAtHost(cookie, host)) {
           if (cookie.name == args.name) {
@@ -1052,17 +1002,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
         type: "string",
         description: gcli.lookup("cookieSetValueDesc")
       },
       {
         group: gcli.lookup("cookieSetOptionsDesc"),
         params: [
           {
             name: "path",
-            type: "string",
+            type: { name: "string", allowBlank: true },
             defaultValue: "/",
             description: gcli.lookup("cookieSetPathDesc")
           },
           {
             name: "domain",
             type: "string",
             defaultValue: null,
             description: gcli.lookup("cookieSetDomainDesc")
@@ -1086,80 +1036,30 @@ XPCOMUtils.defineLazyModuleGetter(this, 
             name: "expires",
             type: "string",
             defaultValue: "Jan 17, 2038",
             description: gcli.lookup("cookieSetExpiresDesc")
           },
         ]
       }
     ],
-    exec: function Command_cookieSet(args, context) {
+    exec: function(args, context) {
       let host = context.environment.document.location.host;
       let time = Date.parse(args.expires) / 1000;
 
       cookieMgr.add(args.domain ? "." + args.domain : host,
                     args.path ? args.path : "/",
                     args.name,
                     args.value,
                     args.secure,
                     args.httpOnly,
                     args.session,
                     time);
     }
   });
-
-  /**
-   * Helper to find the 'data-command' attribute and call some action on it.
-   * @see |updateCommand()| and |executeCommand()|
-   */
-  function withCommand(element, action) {
-    let command = element.getAttribute("data-command");
-    if (!command) {
-      command = element.querySelector("*[data-command]")
-              .getAttribute("data-command");
-    }
-
-    if (command) {
-      action(command);
-    }
-    else {
-      console.warn("Missing data-command for " + util.findCssSelector(element));
-    }
-  }
-
-  /**
-   * Create a handler to update the requisition to contain the text held in the
-   * first matching data-command attribute under the currentTarget of the event.
-   * @param context Either a Requisition or an ExecutionContext or another object
-   * that contains an |update()| function that follows a similar contract.
-   */
-  function createUpdateHandler(context) {
-    return function(ev) {
-      withCommand(ev.currentTarget, function(command) {
-        context.update(command);
-      });
-    }
-  }
-
-  /**
-   * Create a handler to execute the text held in the data-command attribute
-   * under the currentTarget of the event.
-   * @param context Either a Requisition or an ExecutionContext or another object
-   * that contains an |update()| function that follows a similar contract.
-   */
-  function createExecuteHandler(context) {
-    return function(ev) {
-      withCommand(ev.currentTarget, function(command) {
-        context.exec({
-          visible: true,
-          typed: command
-        });
-      });
-    }
-  }
 }(this));
 
 /* CmdExport --------------------------------------------------------------- */
 
 (function(module) {
   /**
    * 'export' command
    */
@@ -2202,18 +2102,18 @@ gcli.addCommand({
   gcli.addConverter({
     from: "appcacheentries",
     to: "view",
     exec: function(entries, context) {
       return context.createView({
         html: appcacheListEntries,
         data: {
           entries: entries,
-          onclick: createUpdateHandler(context),
-          ondblclick: createExecuteHandler(context),
+          onclick: context.update,
+          ondblclick: context.updateExec
         }
       });
     }
   });
 
   gcli.addCommand({
     name: 'appcache list',
     description: gcli.lookup('appCacheListDesc'),
@@ -2248,59 +2148,9 @@ gcli.addCommand({
         defaultValue: null,
       }
     ],
     exec: function(args, context) {
       let utils = new AppCacheUtils();
       return utils.viewEntry(args.key);
     }
   });
-
-  /**
-   * Helper to find the 'data-command' attribute and call some action on it.
-   * @see |updateCommand()| and |executeCommand()|
-   */
-  function withCommand(element, action) {
-    let command = element.getAttribute("data-command");
-    if (!command) {
-      command = element.querySelector("*[data-command]")
-              .getAttribute("data-command");
-    }
-
-    if (command) {
-      action(command);
-    }
-    else {
-      console.warn("Missing data-command for " + util.findCssSelector(element));
-    }
-  }
-
-  /**
-   * Create a handler to update the requisition to contain the text held in the
-   * first matching data-command attribute under the currentTarget of the event.
-   * @param context Either a Requisition or an ExecutionContext or another object
-   * that contains an |update()| function that follows a similar contract.
-   */
-  function createUpdateHandler(context) {
-    return function(ev) {
-      withCommand(ev.currentTarget, function(command) {
-        context.update(command);
-      });
-    }
-  }
-
-  /**
-   * Create a handler to execute the text held in the data-command attribute
-   * under the currentTarget of the event.
-   * @param context Either a Requisition or an ExecutionContext or another object
-   * that contains an |update()| function that follows a similar contract.
-   */
-  function createExecuteHandler(context) {
-    return function(ev) {
-      withCommand(ev.currentTarget, function(command) {
-        context.exec({
-          visible: true,
-          typed: command
-        });
-      });
-    }
-  }
 }(this));
--- a/browser/devtools/commandline/test/Makefile.in
+++ b/browser/devtools/commandline/test/Makefile.in
@@ -47,17 +47,19 @@ MOCHITEST_BROWSER_FILES = \
   browser_gcli_completion.js \
   browser_gcli_exec.js \
   browser_gcli_focus.js \
   browser_gcli_history.js \
   browser_gcli_incomplete.js \
   browser_gcli_inputter.js \
   browser_gcli_intro.js \
   browser_gcli_js.js \
-  browser_gcli_keyboard.js \
+  browser_gcli_keyboard1.js \
+  browser_gcli_keyboard2.js \
+  browser_gcli_keyboard3.js \
   browser_gcli_menu.js \
   browser_gcli_node.js \
   browser_gcli_resource.js \
   browser_gcli_scratchpad.js \
   browser_gcli_spell.js \
   browser_gcli_split.js \
   browser_gcli_tokenize.js \
   browser_gcli_tooltip.js \
--- a/browser/devtools/commandline/test/browser_cmd_cookie.js
+++ b/browser/devtools/commandline/test/browser_cmd_cookie.js
@@ -73,16 +73,31 @@ function test() {
           args: {
             name: { value: 'fruit' },
             value: { value: 'ban' },
             secure: { value: false },
           }
         },
       },
       {
+        setup:    'cookie set fruit ban --path ""',
+        check: {
+          input:  'cookie set fruit ban --path ""',
+          hints:                                ' [options]',
+          markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVVV',
+          status: 'VALID',
+          args: {
+            name: { value: 'fruit' },
+            value: { value: 'ban' },
+            path: { value: '' },
+            secure: { value: false },
+          }
+        },
+      },
+      {
         setup: "cookie list",
         exec: {
           output: [ /zap=zep/, /zip=zop/, /Edit/ ]
         }
       },
       {
         setup: "cookie set zup banana",
         check: {
--- a/browser/devtools/commandline/test/browser_gcli_cli.js
+++ b/browser/devtools/commandline/test/browser_gcli_cli.js
@@ -58,48 +58,48 @@ exports.testBlank = function(options) {
         input:  '',
         hints:  '',
         markup: '',
         cursor: 0,
         current: '__command',
         status: 'ERROR'
       },
       post: function() {
-        assert.is(undefined, requisition.commandAssignment.value);
+        assert.is(requisition.commandAssignment.value, undefined);
       }
     },
     {
       setup:    ' ',
       check: {
         input:  ' ',
         hints:   '',
         markup: 'V',
         cursor: 1,
         current: '__command',
         status: 'ERROR'
       },
       post: function() {
-        assert.is(undefined, requisition.commandAssignment.value);
+        assert.is(requisition.commandAssignment.value, undefined);
       }
     },
     {
       name: '| ',
       setup: function() {
         helpers.setInput(options, ' ', 0);
       },
       check: {
         input:  ' ',
         hints:   '',
         markup: 'V',
         cursor: 0,
         current: '__command',
         status: 'ERROR'
       },
       post: function() {
-        assert.is(undefined, requisition.commandAssignment.value);
+        assert.is(requisition.commandAssignment.value, undefined);
       }
     }
   ]);
 };
 
 exports.testIncompleteMultiMatch = function(options) {
   return helpers.audit(options, [
     {
@@ -305,17 +305,17 @@ exports.testTsv = function(options) {
         input:  'tsv option ',
         hints:             '<optionValue>',
         markup: 'VVVVEEEEEEV',
         cursor: 11,
         current: 'optionValue',
         status: 'ERROR',
         predictions: [ ],
         unassigned: [ ],
-        tooltipState: 'true:isError',
+        tooltipState: 'false:default',
         args: {
           command: { name: 'tsv' },
           optionType: {
             value: undefined,
             arg: ' option ',
             status: 'ERROR',
             message: 'Can\'t use \'option\'.'
           },
@@ -526,17 +526,16 @@ exports.testSingleString = function(opti
       setup:    'tsr',
       check: {
         input:  'tsr',
         hints:     ' <text>',
         markup: 'VVV',
         cursor: 3,
         current: '__command',
         status: 'ERROR',
-        predictions: [ ],
         unassigned: [ ],
         args: {
           command: { name: 'tsr' },
           text: {
             value: undefined,
             arg: '',
             status: 'INCOMPLETE',
             message: ''
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/test/browser_gcli_date.js
@@ -0,0 +1,247 @@
+/*
+ * Copyright 2009-2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE.txt or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+
+// define(function(require, exports, module) {
+
+// <INJECTED SOURCE:START>
+
+// THIS FILE IS GENERATED FROM SOURCE IN THE GCLI PROJECT
+// DO NOT EDIT IT DIRECTLY
+
+var exports = {};
+
+const TEST_URI = "data:text/html;charset=utf-8,<p id='gcli-input'>gcli-testDate.js</p>";
+
+function test() {
+  helpers.addTabWithToolbar(TEST_URI, function(options) {
+    return helpers.runTests(options, exports);
+  }).then(finish);
+}
+
+// <INJECTED SOURCE:END>
+
+'use strict';
+
+// var assert = require('test/assert');
+
+var types = require('gcli/types');
+var Argument = require('gcli/argument').Argument;
+var Status = require('gcli/types').Status;
+
+// var helpers = require('gclitest/helpers');
+// var mockCommands = require('gclitest/mockCommands');
+
+exports.setup = function(options) {
+  mockCommands.setup();
+};
+
+exports.shutdown = function(options) {
+  mockCommands.shutdown();
+};
+
+
+exports.testParse = function(options) {
+  var date = types.createType('date');
+  return date.parse(new Argument('now')).then(function(conversion) {
+    // Date comparison - these 2 dates may not be the same, but how close is
+    // close enough? If this test takes more than 30secs to run the it will
+    // probably time out, so we'll assume that these 2 values must be within
+    // 1 min of each other
+    var gap = new Date().getTime() - conversion.value.getTime();
+    assert.ok(gap < 60000, 'now is less than a minute away');
+
+    assert.is(conversion.getStatus(), Status.VALID, 'now parse');
+  });
+};
+
+exports.testMaxMin = function(options) {
+  var max = new Date();
+  var min = new Date();
+  var date = types.createType({ name: 'date', max: max, min: min });
+  assert.is(date.getMax(), max, 'max setup');
+
+  var incremented = date.increment(min);
+  assert.is(incremented, max, 'incremented');
+};
+
+exports.testIncrement = function(options) {
+  var date = types.createType('date');
+  return date.parse(new Argument('now')).then(function(conversion) {
+    var plusOne = date.increment(conversion.value);
+    var minusOne = date.decrement(plusOne);
+
+    // See comments in testParse
+    var gap = new Date().getTime() - minusOne.getTime();
+    assert.ok(gap < 60000, 'now is less than a minute away');
+  });
+};
+
+exports.testInput = function(options) {
+  helpers.audit(options, [
+    {
+      setup:    'tsdate 2001-01-01 1980-01-03',
+      check: {
+        input:  'tsdate 2001-01-01 1980-01-03',
+        hints:                              '',
+        markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVV',
+        status: 'VALID',
+        message: '',
+        args: {
+          command: { name: 'tsdate' },
+          d1: {
+            value: function(d1) {
+              assert.is(d1.getFullYear(), 2001, 'd1 year');
+              assert.is(d1.getMonth(), 0, 'd1 month');
+              assert.is(d1.getDate(), 1, 'd1 date');
+              assert.is(d1.getHours(), 0, 'd1 hours');
+              assert.is(d1.getMinutes(), 0, 'd1 minutes');
+              assert.is(d1.getSeconds(), 0, 'd1 seconds');
+              assert.is(d1.getMilliseconds(), 0, 'd1 millis');
+            },
+            arg: ' 2001-01-01',
+            status: 'VALID',
+            message: ''
+          },
+          d2: {
+            value: function(d2) {
+              assert.is(d2.getFullYear(), 1980, 'd1 year');
+              assert.is(d2.getMonth(), 0, 'd1 month');
+              assert.is(d2.getDate(), 3, 'd1 date');
+              assert.is(d2.getHours(), 0, 'd1 hours');
+              assert.is(d2.getMinutes(), 0, 'd1 minutes');
+              assert.is(d2.getSeconds(), 0, 'd1 seconds');
+              assert.is(d2.getMilliseconds(), 0, 'd1 millis');
+            },
+            arg: ' 1980-01-03',
+            status: 'VALID',
+            message: ''
+          },
+        }
+      },
+      exec: {
+        output: [ /^Exec: tsdate/, /2001/, /1980/ ],
+        completed: true,
+        type: 'string',
+        error: false
+      }
+    }
+  ]);
+};
+
+exports.testIncrDecr = function(options) {
+  helpers.audit(options, [
+    {
+      setup:    'tsdate 2001-01-01<UP>',
+      check: {
+        input:  'tsdate 2001-01-02',
+        hints:                    ' <d2>',
+        markup: 'VVVVVVVVVVVVVVVVV',
+        status: 'ERROR',
+        message: '',
+        args: {
+          command: { name: 'tsdate' },
+          d1: {
+            value: function(d1) {
+              assert.is(d1.getFullYear(), 2001, 'd1 year');
+              assert.is(d1.getMonth(), 0, 'd1 month');
+              assert.is(d1.getDate(), 2, 'd1 date');
+              assert.is(d1.getHours(), 0, 'd1 hours');
+              assert.is(d1.getMinutes(), 0, 'd1 minutes');
+              assert.is(d1.getSeconds(), 0, 'd1 seconds');
+              assert.is(d1.getMilliseconds(), 0, 'd1 millis');
+            },
+            arg: ' 2001-01-02',
+            status: 'VALID',
+            message: ''
+          },
+          d2: {
+            value: undefined,
+            status: 'INCOMPLETE',
+            message: ''
+          },
+        }
+      }
+    },
+    {
+      // Check wrapping on decrement
+      setup:    'tsdate 2001-02-01<DOWN>',
+      check: {
+        input:  'tsdate 2001-01-31',
+        hints:                    ' <d2>',
+        markup: 'VVVVVVVVVVVVVVVVV',
+        status: 'ERROR',
+        message: '',
+        args: {
+          command: { name: 'tsdate' },
+          d1: {
+            value: function(d1) {
+              assert.is(d1.getFullYear(), 2001, 'd1 year');
+              assert.is(d1.getMonth(), 0, 'd1 month');
+              assert.is(d1.getDate(), 31, 'd1 date');
+              assert.is(d1.getHours(), 0, 'd1 hours');
+              assert.is(d1.getMinutes(), 0, 'd1 minutes');
+              assert.is(d1.getSeconds(), 0, 'd1 seconds');
+              assert.is(d1.getMilliseconds(), 0, 'd1 millis');
+            },
+            arg: ' 2001-01-31',
+            status: 'VALID',
+            message: ''
+          },
+          d2: {
+            value: undefined,
+            status: 'INCOMPLETE',
+            message: ''
+          },
+        }
+      }
+    },
+    {
+      // Check 'max' value capping on increment
+      setup:    'tsdate 2001-02-01 "27 feb 2000"<UP>',
+      check: {
+        input:  'tsdate 2001-02-01 "2000-02-28"',
+        hints:                                '',
+        markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVVV',
+        status: 'VALID',
+        message: '',
+        args: {
+          command: { name: 'tsdate' },
+          d1: {
+            value: function(d1) {
+              assert.is(d1.getFullYear(), 2001, 'd1 year');
+              assert.is(d1.getMonth(), 1, 'd1 month');
+              assert.is(d1.getDate(), 1, 'd1 date');
+              assert.is(d1.getHours(), 0, 'd1 hours');
+              assert.is(d1.getMinutes(), 0, 'd1 minutes');
+              assert.is(d1.getSeconds(), 0, 'd1 seconds');
+              assert.is(d1.getMilliseconds(), 0, 'd1 millis');
+            },
+            arg: ' 2001-02-01',
+            status: 'VALID',
+            message: ''
+          },
+          d2: {
+            value: function(d1) {
+              assert.is(d1.getFullYear(), 2000, 'd1 year');
+              assert.is(d1.getMonth(), 1, 'd1 month');
+              assert.is(d1.getDate(), 28, 'd1 date');
+              assert.is(d1.getHours(), 0, 'd1 hours');
+              assert.is(d1.getMinutes(), 0, 'd1 minutes');
+              assert.is(d1.getSeconds(), 0, 'd1 seconds');
+              assert.is(d1.getMilliseconds(), 0, 'd1 millis');
+            },
+            arg: ' "2000-02-28"',
+            status: 'VALID',
+            message: ''
+          },
+        }
+      }
+    }
+  ]);
+};
+
+
+// });
--- a/browser/devtools/commandline/test/browser_gcli_history.js
+++ b/browser/devtools/commandline/test/browser_gcli_history.js
@@ -37,48 +37,48 @@ function test() {
 
 // var assert = require('test/assert');
 var History = require('gcli/history').History;
 
 exports.testSimpleHistory = function (options) {
   var history = new History({});
   history.add('foo');
   history.add('bar');
-  assert.is('bar', history.backward());
-  assert.is('foo', history.backward());
+  assert.is(history.backward(), 'bar');
+  assert.is(history.backward(), 'foo');
 
   // Adding to the history again moves us back to the start of the history.
   history.add('quux');
-  assert.is('quux', history.backward());
-  assert.is('bar', history.backward());
-  assert.is('foo', history.backward());
+  assert.is(history.backward(), 'quux');
+  assert.is(history.backward(), 'bar');
+  assert.is(history.backward(), 'foo');
 };
 
 exports.testBackwardsPastIndex = function (options) {
   var history = new History({});
   history.add('foo');
   history.add('bar');
-  assert.is('bar', history.backward());
-  assert.is('foo', history.backward());
+  assert.is(history.backward(), 'bar');
+  assert.is(history.backward(), 'foo');
 
   // Moving backwards past recorded history just keeps giving you the last
   // item.
-  assert.is('foo', history.backward());
+  assert.is(history.backward(), 'foo');
 };
 
 exports.testForwardsPastIndex = function (options) {
   var history = new History({});
   history.add('foo');
   history.add('bar');
-  assert.is('bar', history.backward());
-  assert.is('foo', history.backward());
+  assert.is(history.backward(), 'bar');
+  assert.is(history.backward(), 'foo');
 
   // Going forward through the history again.
-  assert.is('bar', history.forward());
+  assert.is(history.forward(), 'bar');
 
   // 'Present' time.
-  assert.is('', history.forward());
+  assert.is(history.forward(), '');
 
   // Going to the 'future' just keeps giving us the empty string.
-  assert.is('', history.forward());
+  assert.is(history.forward(), '');
 };
 
 // });
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/test/browser_gcli_keyboard1.js
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// define(function(require, exports, module) {
+
+// <INJECTED SOURCE:START>
+
+// THIS FILE IS GENERATED FROM SOURCE IN THE GCLI PROJECT
+// DO NOT EDIT IT DIRECTLY
+
+var exports = {};
+
+const TEST_URI = "data:text/html;charset=utf-8,<p id='gcli-input'>gcli-testKeyboard1.js</p>";
+
+function test() {
+  helpers.addTabWithToolbar(TEST_URI, function(options) {
+    return helpers.runTests(options, exports);
+  }).then(finish);
+}
+
+// <INJECTED SOURCE:END>
+
+'use strict';
+
+// var helpers = require('gclitest/helpers');
+// var mockCommands = require('gclitest/mockCommands');
+
+exports.setup = function(options) {
+  mockCommands.setup();
+};
+
+exports.shutdown = function(options) {
+  mockCommands.shutdown();
+};
+
+exports.testComplete = function(options) {
+  return helpers.audit(options, [
+    {
+      setup: 'tsn e<DOWN><DOWN><DOWN><DOWN><DOWN><TAB>',
+      check: { input: 'tsn exte ' }
+    },
+    {
+      setup: 'tsn e<DOWN><DOWN><DOWN><DOWN><TAB>',
+      check: { input: 'tsn ext ' }
+    },
+    {
+      setup: 'tsn e<DOWN><DOWN><DOWN><TAB>',
+      check: { input: 'tsn extend ' }
+    },
+    {
+      setup: 'tsn e<DOWN><DOWN><TAB>',
+      check: { input: 'tsn exten ' }
+    },
+    {
+      setup: 'tsn e<DOWN><TAB>',
+      check: { input: 'tsn exte ' }
+    },
+    {
+      setup: 'tsn e<TAB>',
+      check: { input: 'tsn ext ' }
+    },
+    {
+      setup: 'tsn e<UP><TAB>',
+      check: { input: 'tsn extend ' }
+    },
+    {
+      setup: 'tsn e<UP><UP><TAB>',
+      check: { input: 'tsn exten ' }
+    },
+    {
+      setup: 'tsn e<UP><UP><UP><TAB>',
+      check: { input: 'tsn exte ' }
+    },
+    {
+      setup: 'tsn e<UP><UP><UP><UP><TAB>',
+      check: { input: 'tsn ext ' }
+    },
+    {
+      setup: 'tsn e<UP><UP><UP><UP><UP><TAB>',
+      check: { input: 'tsn extend ' }
+    },
+    {
+      setup: 'tsn e<UP><UP><UP><UP><UP><UP><TAB>',
+      check: { input: 'tsn exten ' }
+    },
+    {
+      setup: 'tsn e<UP><UP><UP><UP><UP><UP><UP><TAB>',
+      check: { input: 'tsn exte ' }
+    },
+    {
+      setup: 'tsn e<UP><UP><UP><UP><UP><UP><UP><UP><TAB>',
+      check: { input: 'tsn ext ' }
+    }
+  ]);
+};
+
+
+// });
rename from browser/devtools/commandline/test/browser_gcli_keyboard.js
rename to browser/devtools/commandline/test/browser_gcli_keyboard2.js
--- a/browser/devtools/commandline/test/browser_gcli_keyboard.js
+++ b/browser/devtools/commandline/test/browser_gcli_keyboard2.js
@@ -18,46 +18,36 @@
 
 // <INJECTED SOURCE:START>
 
 // THIS FILE IS GENERATED FROM SOURCE IN THE GCLI PROJECT
 // DO NOT EDIT IT DIRECTLY
 
 var exports = {};
 
-const TEST_URI = "data:text/html;charset=utf-8,<p id='gcli-input'>gcli-testKeyboard.js</p>";
+const TEST_URI = "data:text/html;charset=utf-8,<p id='gcli-input'>gcli-testKeyboard2.js</p>";
 
 function test() {
   helpers.addTabWithToolbar(TEST_URI, function(options) {
     return helpers.runTests(options, exports);
   }).then(finish);
 }
 
 // <INJECTED SOURCE:END>
 
 'use strict';
 
-var javascript = require('gcli/types/javascript');
 // var helpers = require('gclitest/helpers');
 // var mockCommands = require('gclitest/mockCommands');
-var canon = require('gcli/canon');
-
-var tempWindow = undefined;
 
 exports.setup = function(options) {
   mockCommands.setup();
-
-  tempWindow = javascript.getGlobalObject();
-  javascript.setGlobalObject(options.window);
 };
 
 exports.shutdown = function(options) {
-  javascript.setGlobalObject(tempWindow);
-  tempWindow = undefined;
-
   mockCommands.shutdown();
 };
 
 // Bug 664377: Add tests for internal completion. i.e. "tsela<TAB> 1"
 
 exports.testSimple = function(options) {
   return helpers.audit(options, [
     {
@@ -70,108 +60,16 @@ exports.testSimple = function(options) {
     },
     {
       setup: 'tsg a<TAB>',
       check: { input: 'tsg aaa ', cursor: 8 }
     }
   ]);
 };
 
-exports.testComplete = function(options) {
-  return helpers.audit(options, [
-    {
-      setup: 'tsn e<DOWN><DOWN><DOWN><DOWN><DOWN><TAB>',
-      check: { input: 'tsn exte ' }
-    },
-    {
-      setup: 'tsn e<DOWN><DOWN><DOWN><DOWN><TAB>',
-      check: { input: 'tsn ext ' }
-    },
-    {
-      setup: 'tsn e<DOWN><DOWN><DOWN><TAB>',
-      check: { input: 'tsn extend ' }
-    },
-    {
-      setup: 'tsn e<DOWN><DOWN><TAB>',
-      check: { input: 'tsn exten ' }
-    },
-    {
-      setup: 'tsn e<DOWN><TAB>',
-      check: { input: 'tsn exte ' }
-    },
-    {
-      setup: 'tsn e<TAB>',
-      check: { input: 'tsn ext ' }
-    },
-    {
-      setup: 'tsn e<UP><TAB>',
-      check: { input: 'tsn extend ' }
-    },
-    {
-      setup: 'tsn e<UP><UP><TAB>',
-      check: { input: 'tsn exten ' }
-    },
-    {
-      setup: 'tsn e<UP><UP><UP><TAB>',
-      check: { input: 'tsn exte ' }
-    },
-    {
-      setup: 'tsn e<UP><UP><UP><UP><TAB>',
-      check: { input: 'tsn ext ' }
-    },
-    {
-      setup: 'tsn e<UP><UP><UP><UP><UP><TAB>',
-      check: { input: 'tsn extend ' }
-    },
-    {
-      setup: 'tsn e<UP><UP><UP><UP><UP><UP><TAB>',
-      check: { input: 'tsn exten ' }
-    },
-    {
-      setup: 'tsn e<UP><UP><UP><UP><UP><UP><UP><TAB>',
-      check: { input: 'tsn exte ' }
-    },
-    {
-      setup: 'tsn e<UP><UP><UP><UP><UP><UP><UP><UP><TAB>',
-      check: { input: 'tsn ext ' }
-    }
-  ]);
-};
-
-exports.testScript = function(options) {
-  return helpers.audit(options, [
-    {
-      skipIf: function commandJsMissing() {
-        return canon.getCommand('{') == null;
-      },
-      setup: '{ wind<TAB>',
-      check: { input: '{ window' }
-    },
-    {
-      skipIf: function commandJsMissing() {
-        return canon.getCommand('{') == null;
-      },
-      setup: '{ window.docum<TAB>',
-      check: { input: '{ window.document' }
-    }
-  ]);
-};
-
-exports.testJsdom = function(options) {
-  return helpers.audit(options, [
-    {
-      skipIf: function jsDomOrCommandJsMissing() {
-        return options.isJsdom || canon.getCommand('{') == null;
-      },
-      setup: '{ window.document.titl<TAB>',
-      check: { input: '{ window.document.title ' }
-    }
-  ]);
-};
-
 exports.testIncr = function(options) {
   return helpers.audit(options, [
     /*
     // We currently refuse to increment/decrement things with a non-valid
     // status which makes sense for many cases, and is a decent default.
     // However in theory we could do better, these tests are there for then
     {
       setup: 'tsu -70<UP>',
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/test/browser_gcli_keyboard3.js
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// define(function(require, exports, module) {
+
+// <INJECTED SOURCE:START>
+
+// THIS FILE IS GENERATED FROM SOURCE IN THE GCLI PROJECT
+// DO NOT EDIT IT DIRECTLY
+
+var exports = {};
+
+const TEST_URI = "data:text/html;charset=utf-8,<p id='gcli-input'>gcli-testKeyboard3.js</p>";
+
+function test() {
+  helpers.addTabWithToolbar(TEST_URI, function(options) {
+    return helpers.runTests(options, exports);
+  }).then(finish);
+}
+
+// <INJECTED SOURCE:END>
+
+'use strict';
+
+var javascript = require('gcli/types/javascript');
+// var helpers = require('gclitest/helpers');
+// var mockCommands = require('gclitest/mockCommands');
+var canon = require('gcli/canon');
+
+var tempWindow = undefined;
+
+exports.setup = function(options) {
+  mockCommands.setup();
+
+  tempWindow = javascript.getGlobalObject();
+  javascript.setGlobalObject(options.window);
+};
+
+exports.shutdown = function(options) {
+  javascript.setGlobalObject(tempWindow);
+  tempWindow = undefined;
+
+  mockCommands.shutdown();
+};
+
+exports.testScript = function(options) {
+  return helpers.audit(options, [
+    {
+      skipIf: function commandJsMissing() {
+        return canon.getCommand('{') == null;
+      },
+      setup: '{ wind<TAB>',
+      check: { input: '{ window' }
+    },
+    {
+      skipIf: function commandJsMissing() {
+        return canon.getCommand('{') == null;
+      },
+      setup: '{ window.docum<TAB>',
+      check: { input: '{ window.document' }
+    }
+  ]);
+};
+
+exports.testJsdom = function(options) {
+  return helpers.audit(options, [
+    {
+      skipIf: function jsDomOrCommandJsMissing() {
+        return options.isJsdom || canon.getCommand('{') == null;
+      },
+      setup: '{ window.document.titl<TAB>',
+      check: { input: '{ window.document.title ' }
+    }
+  ]);
+};
+
+
+// });
--- a/browser/devtools/commandline/test/browser_gcli_scratchpad.js
+++ b/browser/devtools/commandline/test/browser_gcli_scratchpad.js
@@ -60,13 +60,13 @@ stubScratchpad.activate = function(value
   return true;
 };
 
 
 exports.testActivate = function(options) {
   var ev = {};
   stubScratchpad.activatedCount = 0;
   options.display.inputter.handleKeyUp(ev);
-  assert.is(1, stubScratchpad.activatedCount, 'scratchpad is activated');
+  assert.is(stubScratchpad.activatedCount, 1, 'scratchpad is activated');
 };
 
 
 // });
--- a/browser/devtools/commandline/test/browser_gcli_split.js
+++ b/browser/devtools/commandline/test/browser_gcli_split.js
@@ -51,49 +51,49 @@ exports.shutdown = function(options) {
 
 
 exports.testSplitSimple = function(options) {
   var args;
   var requisition = new Requisition();
 
   args = cli.tokenize('s');
   requisition._split(args);
-  assert.is(0, args.length);
-  assert.is('s', requisition.commandAssignment.arg.text);
+  assert.is(args.length, 0);
+  assert.is(requisition.commandAssignment.arg.text, 's');
 };
 
 exports.testFlatCommand = function(options) {
   var args;
   var requisition = new Requisition();
 
   args = cli.tokenize('tsv');
   requisition._split(args);
-  assert.is(0, args.length);
-  assert.is('tsv', requisition.commandAssignment.value.name);
+  assert.is(args.length, 0);
+  assert.is(requisition.commandAssignment.value.name, 'tsv');
 
   args = cli.tokenize('tsv a b');
   requisition._split(args);
-  assert.is('tsv', requisition.commandAssignment.value.name);
-  assert.is(2, args.length);
-  assert.is('a', args[0].text);
-  assert.is('b', args[1].text);
+  assert.is(requisition.commandAssignment.value.name, 'tsv');
+  assert.is(args.length, 2);
+  assert.is(args[0].text, 'a');
+  assert.is(args[1].text, 'b');
 };
 
 exports.testJavascript = function(options) {
   if (!canon.getCommand('{')) {
     assert.log('Skipping testJavascript because { is not registered');
     return;
   }
 
   var args;
   var requisition = new Requisition();
 
   args = cli.tokenize('{');
   requisition._split(args);
-  assert.is(1, args.length);
-  assert.is('', args[0].text);
-  assert.is('', requisition.commandAssignment.arg.text);
-  assert.is('{', requisition.commandAssignment.value.name);
+  assert.is(args.length, 1);
+  assert.is(args[0].text, '');
+  assert.is(requisition.commandAssignment.arg.text, '');
+  assert.is(requisition.commandAssignment.value.name, '{');
 };
 
 // BUG 663081 - add tests for sub commands
 
 // });
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/test/browser_gcli_string.js
@@ -0,0 +1,293 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// define(function(require, exports, module) {
+
+// <INJECTED SOURCE:START>
+
+// THIS FILE IS GENERATED FROM SOURCE IN THE GCLI PROJECT
+// DO NOT EDIT IT DIRECTLY
+
+var exports = {};
+
+const TEST_URI = "data:text/html;charset=utf-8,<p id='gcli-input'>gcli-testString.js</p>";
+
+function test() {
+  helpers.addTabWithToolbar(TEST_URI, function(options) {
+    return helpers.runTests(options, exports);
+  }).then(finish);
+}
+
+// <INJECTED SOURCE:END>
+
+'use strict';
+
+// var helpers = require('gclitest/helpers');
+// var mockCommands = require('gclitest/mockCommands');
+
+exports.setup = function(options) {
+  mockCommands.setup();
+};
+
+exports.shutdown = function(options) {
+  mockCommands.shutdown();
+};
+
+exports.testNewLine = function(options) {
+  helpers.audit(options, [
+    {
+      setup:    'echo a\\nb',
+      check: {
+        input:  'echo a\\nb',
+        hints:            '',
+        markup: 'VVVVVVVVV',
+        cursor: 9,
+        current: 'message',
+        status: 'VALID',
+        args: {
+          command: { name: 'echo' },
+          message: {
+            value: 'a\nb',
+            arg: ' a\\nb',
+            status: 'VALID',
+            message: ''
+          }
+        }
+      }
+    }
+  ]);
+};
+
+exports.testTab = function(options) {
+  helpers.audit(options, [
+    {
+      setup:    'echo a\\tb',
+      check: {
+        input:  'echo a\\tb',
+        hints:            '',
+        markup: 'VVVVVVVVV',
+        cursor: 9,
+        current: 'message',
+        status: 'VALID',
+        args: {
+          command: { name: 'echo' },
+          message: {
+            value: 'a\tb',
+            arg: ' a\\tb',
+            status: 'VALID',
+            message: ''
+          }
+        }
+      }
+    }
+  ]);
+};
+
+exports.testEscape = function(options) {
+  helpers.audit(options, [
+    {
+      // What's typed is actually:
+      //         tsrsrsr a\\ b c
+      setup:    'tsrsrsr a\\\\ b c',
+      check: {
+        input:  'tsrsrsr a\\\\ b c',
+        hints:                 '',
+        markup: 'VVVVVVVVVVVVVVV',
+        status: 'VALID',
+        message: '',
+        args: {
+          command: { name: 'tsrsrsr' },
+          p1: { value: 'a\\', arg: ' a\\\\', status: 'VALID', message: '' },
+          p2: { value: 'b', arg: ' b', status: 'VALID', message: '' },
+          p3: { value: 'c', arg: ' c', status: 'VALID', message: '' },
+        }
+      }
+    },
+    {
+      // What's typed is actually:
+      //         tsrsrsr abc\\ndef asd asd
+      setup:    'tsrsrsr abc\\\\ndef asd asd',
+      check: {
+        input:  'tsrsrsr abc\\\\ndef asd asd',
+        hints:                           '',
+        markup: 'VVVVVVVVVVVVVVVVVVVVVVVVV',
+        status: 'VALID',
+        message: '',
+        args: {
+          command: { name: 'tsrsrsr' },
+          p1: {
+            value: 'abc\\ndef',
+            arg: ' abc\\\\ndef',
+            status: 'VALID',
+            message: ''
+          },
+          p2: { value: 'asd', arg: ' asd', status: 'VALID', message: '' },
+          p3: { value: 'asd', arg: ' asd', status: 'VALID', message: '' },
+        }
+      }
+    }
+  ]);
+};
+
+exports.testBlank = function(options) {
+  helpers.audit(options, [
+    {
+      setup:    'tsrsrsr a "" c',
+      check: {
+        input:  'tsrsrsr a "" c',
+        hints:                '',
+        markup: 'VVVVVVVVVVVVVV',
+        cursor: 14,
+        current: 'p3',
+        status: 'ERROR',
+        message: '',
+        args: {
+          command: { name: 'tsrsrsr' },
+          p1: {
+            value: 'a',
+            arg: ' a',
+            status: 'VALID',
+            message: ''
+          },
+          p2: {
+            value: undefined,
+            arg: ' ""',
+            status: 'INCOMPLETE',
+            message: ''
+          },
+          p3: {
+            value: 'c',
+            arg: ' c',
+            status: 'VALID',
+            message: ''
+          }
+        }
+      }
+    },
+    {
+      setup:    'tsrsrsr a b ""',
+      check: {
+        input:  'tsrsrsr a b ""',
+        hints:                '',
+        markup: 'VVVVVVVVVVVVVV',
+        cursor: 14,
+        current: 'p3',
+        status: 'VALID',
+        message: '',
+        args: {
+          command: { name: 'tsrsrsr' },
+          p1: {
+            value: 'a',
+            arg: ' a',
+            status:'VALID',
+            message: '' },
+          p2: {
+            value: 'b',
+            arg: ' b',
+            status: 'VALID',
+            message: ''
+          },
+          p3: {
+            value: '',
+            arg: ' ""',
+            status: 'VALID',
+            message: ''
+          }
+        }
+      }
+    }
+  ]);
+};
+
+exports.testBlankWithParam = function(options) {
+  helpers.audit(options, [
+    {
+      setup:    'tsrsrsr  a --p3',
+      check: {
+        input:  'tsrsrsr  a --p3',
+        hints:                 ' <string> <p2>',
+        markup: 'VVVVVVVVVVVVVVV',
+        cursor: 15,
+        current: 'p3',
+        status: 'ERROR',
+        message: '',
+        args: {
+          command: { name: 'tsrsrsr' },
+          p1: { value: 'a', arg: '  a', status: 'VALID', message: '' },
+          p2: { value: undefined, arg: '', status: 'INCOMPLETE', message: '' },
+          p3: { value: '', arg: ' --p3', status: 'VALID', message: '' },
+        }
+      }
+    },
+    {
+      setup:    'tsrsrsr  a --p3 ',
+      check: {
+        input:  'tsrsrsr  a --p3 ',
+        hints:                  '<string> <p2>',
+        markup: 'VVVVVVVVVVVVVVVV',
+        cursor: 16,
+        current: 'p3',
+        status: 'ERROR',
+        message: '',
+        args: {
+          command: { name: 'tsrsrsr' },
+          p1: { value: 'a', arg: '  a', status: 'VALID', message: '' },
+          p2: { value: undefined, arg: '', status: 'INCOMPLETE', message: '' },
+          p3: { value: '', arg: ' --p3 ', status: 'VALID', message: '' },
+        }
+      }
+    },
+    {
+      setup:    'tsrsrsr  a --p3 "',
+      check: {
+        input:  'tsrsrsr  a --p3 "',
+        hints:                   ' <p2>',
+        markup: 'VVVVVVVVVVVVVVVVV',
+        cursor: 17,
+        current: 'p3',
+        status: 'ERROR',
+        message: '',
+        args: {
+          command: { name: 'tsrsrsr' },
+          p1: { value: 'a', arg: '  a', status: 'VALID', message: '' },
+          p2: { value: undefined, arg: '', status: 'INCOMPLETE', message: '' },
+          p3: { value: '', arg: ' --p3 "', status: 'VALID', message: '' },
+        }
+      }
+    },
+    {
+      setup:    'tsrsrsr  a --p3 ""',
+      check: {
+        input:  'tsrsrsr  a --p3 ""',
+        hints:                    ' <p2>',
+        markup: 'VVVVVVVVVVVVVVVVVV',
+        cursor: 18,
+        current: 'p3',
+        status: 'ERROR',
+        message: '',
+        args: {
+          command: { name: 'tsrsrsr' },
+          p1: { value: 'a', arg: '  a', status: 'VALID', message: '' },
+          p2: { value: undefined, arg: '', status: 'INCOMPLETE', message: '' },
+          p3: { value: '', arg: ' --p3 ""', status: 'VALID', message: '' },
+        }
+      }
+    }
+  ]);
+};
+
+
+// });
--- a/browser/devtools/commandline/test/browser_gcli_tokenize.js
+++ b/browser/devtools/commandline/test/browser_gcli_tokenize.js
@@ -37,267 +37,267 @@ function test() {
 
 // var assert = require('test/assert');
 var cli = require('gcli/cli');
 
 exports.testBlanks = function(options) {
   var args;
 
   args = cli.tokenize('');
-  assert.is(1, args.length);
-  assert.is('', args[0].text);
-  assert.is('', args[0].prefix);
-  assert.is('', args[0].suffix);
+  assert.is(args.length, 1);
+  assert.is(args[0].text, '');
+  assert.is(args[0].prefix, '');
+  assert.is(args[0].suffix, '');
 
   args = cli.tokenize(' ');
-  assert.is(1, args.length);
-  assert.is('', args[0].text);
-  assert.is(' ', args[0].prefix);
-  assert.is('', args[0].suffix);
+  assert.is(args.length, 1);
+  assert.is(args[0].text, '');
+  assert.is(args[0].prefix, ' ');
+  assert.is(args[0].suffix, '');
 };
 
 exports.testTokSimple = function(options) {
   var args;
 
   args = cli.tokenize('s');
-  assert.is(1, args.length);
-  assert.is('s', args[0].text);
-  assert.is('', args[0].prefix);
-  assert.is('', args[0].suffix);
-  assert.is('Argument', args[0].type);
+  assert.is(args.length, 1);
+  assert.is(args[0].text, 's');
+  assert.is(args[0].prefix, '');
+  assert.is(args[0].suffix, '');
+  assert.is(args[0].type, 'Argument');
 
   args = cli.tokenize('s s');
-  assert.is(2, args.length);
-  assert.is('s', args[0].text);
-  assert.is('', args[0].prefix);
-  assert.is('', args[0].suffix);
-  assert.is('Argument', args[0].type);
-  assert.is('s', args[1].text);
-  assert.is(' ', args[1].prefix);
-  assert.is('', args[1].suffix);
-  assert.is('Argument', args[1].type);
+  assert.is(args.length, 2);
+  assert.is(args[0].text, 's');
+  assert.is(args[0].prefix, '');
+  assert.is(args[0].suffix, '');
+  assert.is(args[0].type, 'Argument');
+  assert.is(args[1].text, 's');
+  assert.is(args[1].prefix, ' ');
+  assert.is(args[1].suffix, '');
+  assert.is(args[1].type, 'Argument');
 };
 
 exports.testJavascript = function(options) {
   var args;
 
   args = cli.tokenize('{x}');
-  assert.is(1, args.length);
-  assert.is('x', args[0].text);
-  assert.is('{', args[0].prefix);
-  assert.is('}', args[0].suffix);
-  assert.is('ScriptArgument', args[0].type);
+  assert.is(args.length, 1);
+  assert.is(args[0].text, 'x');
+  assert.is(args[0].prefix, '{');
+  assert.is(args[0].suffix, '}');
+  assert.is(args[0].type, 'ScriptArgument');
 
   args = cli.tokenize('{ x }');
-  assert.is(1, args.length);
-  assert.is('x', args[0].text);
-  assert.is('{ ', args[0].prefix);
-  assert.is(' }', args[0].suffix);
-  assert.is('ScriptArgument', args[0].type);
+  assert.is(args.length, 1);
+  assert.is(args[0].text, 'x');
+  assert.is(args[0].prefix, '{ ');
+  assert.is(args[0].suffix, ' }');
+  assert.is(args[0].type, 'ScriptArgument');
 
   args = cli.tokenize('{x} {y}');
-  assert.is(2, args.length);
-  assert.is('x', args[0].text);
-  assert.is('{', args[0].prefix);
-  assert.is('}', args[0].suffix);
-  assert.is('ScriptArgument', args[0].type);
-  assert.is('y', args[1].text);
-  assert.is(' {', args[1].prefix);
-  assert.is('}', args[1].suffix);
-  assert.is('ScriptArgument', args[1].type);
+  assert.is(args.length, 2);
+  assert.is(args[0].text, 'x');
+  assert.is(args[0].prefix, '{');
+  assert.is(args[0].suffix, '}');
+  assert.is(args[0].type, 'ScriptArgument');
+  assert.is(args[1].text, 'y');
+  assert.is(args[1].prefix, ' {');
+  assert.is(args[1].suffix, '}');
+  assert.is(args[1].type, 'ScriptArgument');
 
   args = cli.tokenize('{x}{y}');
-  assert.is(2, args.length);
-  assert.is('x', args[0].text);
-  assert.is('{', args[0].prefix);
-  assert.is('}', args[0].suffix);
-  assert.is('ScriptArgument', args[0].type);
-  assert.is('y', args[1].text);
-  assert.is('{', args[1].prefix);
-  assert.is('}', args[1].suffix);
-  assert.is('ScriptArgument', args[1].type);
+  assert.is(args.length, 2);
+  assert.is(args[0].text, 'x');
+  assert.is(args[0].prefix, '{');
+  assert.is(args[0].suffix, '}');
+  assert.is(args[0].type, 'ScriptArgument');
+  assert.is(args[1].text, 'y');
+  assert.is(args[1].prefix, '{');
+  assert.is(args[1].suffix, '}');
+  assert.is(args[1].type, 'ScriptArgument');
 
   args = cli.tokenize('{');
-  assert.is(1, args.length);
-  assert.is('', args[0].text);
-  assert.is('{', args[0].prefix);
-  assert.is('', args[0].suffix);
-  assert.is('ScriptArgument', args[0].type);
+  assert.is(args.length, 1);
+  assert.is(args[0].text, '');
+  assert.is(args[0].prefix, '{');
+  assert.is(args[0].suffix, '');
+  assert.is(args[0].type, 'ScriptArgument');
 
   args = cli.tokenize('{ ');
-  assert.is(1, args.length);
-  assert.is('', args[0].text);
-  assert.is('{ ', args[0].prefix);
-  assert.is('', args[0].suffix);
-  assert.is('ScriptArgument', args[0].type);
+  assert.is(args.length, 1);
+  assert.is(args[0].text, '');
+  assert.is(args[0].prefix, '{ ');
+  assert.is(args[0].suffix, '');
+  assert.is(args[0].type, 'ScriptArgument');
 
   args = cli.tokenize('{x');
-  assert.is(1, args.length);
-  assert.is('x', args[0].text);
-  assert.is('{', args[0].prefix);
-  assert.is('', args[0].suffix);
-  assert.is('ScriptArgument', args[0].type);
+  assert.is(args.length, 1);
+  assert.is(args[0].text, 'x');
+  assert.is(args[0].prefix, '{');
+  assert.is(args[0].suffix, '');
+  assert.is(args[0].type, 'ScriptArgument');
 };
 
 exports.testRegularNesting = function(options) {
   var args;
 
   args = cli.tokenize('{"x"}');
-  assert.is(1, args.length);
-  assert.is('"x"', args[0].text);
-  assert.is('{', args[0].prefix);
-  assert.is('}', args[0].suffix);
-  assert.is('ScriptArgument', args[0].type);
+  assert.is(args.length, 1);
+  assert.is(args[0].text, '"x"');
+  assert.is(args[0].prefix, '{');
+  assert.is(args[0].suffix, '}');
+  assert.is(args[0].type, 'ScriptArgument');
 
   args = cli.tokenize('{\'x\'}');
-  assert.is(1, args.length);
-  assert.is('\'x\'', args[0].text);
-  assert.is('{', args[0].prefix);
-  assert.is('}', args[0].suffix);
-  assert.is('ScriptArgument', args[0].type);
+  assert.is(args.length, 1);
+  assert.is(args[0].text, '\'x\'');
+  assert.is(args[0].prefix, '{');
+  assert.is(args[0].suffix, '}');
+  assert.is(args[0].type, 'ScriptArgument');
 
   args = cli.tokenize('"{x}"');
-  assert.is(1, args.length);
-  assert.is('{x}', args[0].text);
-  assert.is('"', args[0].prefix);
-  assert.is('"', args[0].suffix);
-  assert.is('Argument', args[0].type);
+  assert.is(args.length, 1);
+  assert.is(args[0].text, '{x}');
+  assert.is(args[0].prefix, '"');
+  assert.is(args[0].suffix, '"');
+  assert.is(args[0].type, 'Argument');
 
   args = cli.tokenize('\'{x}\'');
-  assert.is(1, args.length);
-  assert.is('{x}', args[0].text);
-  assert.is('\'', args[0].prefix);
-  assert.is('\'', args[0].suffix);
-  assert.is('Argument', args[0].type);
+  assert.is(args.length, 1);
+  assert.is(args[0].text, '{x}');
+  assert.is(args[0].prefix, '\'');
+  assert.is(args[0].suffix, '\'');
+  assert.is(args[0].type, 'Argument');
 };
 
 exports.testDeepNesting = function(options) {
   var args;
 
   args = cli.tokenize('{{}}');
-  assert.is(1, args.length);
-  assert.is('{}', args[0].text);
-  assert.is('{', args[0].prefix);
-  assert.is('}', args[0].suffix);
-  assert.is('ScriptArgument', args[0].type);
+  assert.is(args.length, 1);
+  assert.is(args[0].text, '{}');
+  assert.is(args[0].prefix, '{');
+  assert.is(args[0].suffix, '}');
+  assert.is(args[0].type, 'ScriptArgument');
 
   args = cli.tokenize('{{x} {y}}');
-  assert.is(1, args.length);
-  assert.is('{x} {y}', args[0].text);
-  assert.is('{', args[0].prefix);
-  assert.is('}', args[0].suffix);
-  assert.is('ScriptArgument', args[0].type);
+  assert.is(args.length, 1);
+  assert.is(args[0].text, '{x} {y}');
+  assert.is(args[0].prefix, '{');
+  assert.is(args[0].suffix, '}');
+  assert.is(args[0].type, 'ScriptArgument');
 
   args = cli.tokenize('{{w} {{{x}}}} {y} {{{z}}}');
 
-  assert.is(3, args.length);
+  assert.is(args.length, 3);
 
-  assert.is('{w} {{{x}}}', args[0].text);
-  assert.is('{', args[0].prefix);
-  assert.is('}', args[0].suffix);
-  assert.is('ScriptArgument', args[0].type);
+  assert.is(args[0].text, '{w} {{{x}}}');
+  assert.is(args[0].prefix, '{');
+  assert.is(args[0].suffix, '}');
+  assert.is(args[0].type, 'ScriptArgument');
 
-  assert.is('y', args[1].text);
-  assert.is(' {', args[1].prefix);
-  assert.is('}', args[1].suffix);
-  assert.is('ScriptArgument', args[1].type);
+  assert.is(args[1].text, 'y');
+  assert.is(args[1].prefix, ' {');
+  assert.is(args[1].suffix, '}');
+  assert.is(args[1].type, 'ScriptArgument');
 
-  assert.is('{{z}}', args[2].text);
-  assert.is(' {', args[2].prefix);
-  assert.is('}', args[2].suffix);
-  assert.is('ScriptArgument', args[2].type);
+  assert.is(args[2].text, '{{z}}');
+  assert.is(args[2].prefix, ' {');
+  assert.is(args[2].suffix, '}');
+  assert.is(args[2].type, 'ScriptArgument');
 
   args = cli.tokenize('{{w} {{{x}}} {y} {{{z}}}');
 
-  assert.is(1, args.length);
+  assert.is(args.length, 1);
 
-  assert.is('{w} {{{x}}} {y} {{{z}}}', args[0].text);
-  assert.is('{', args[0].prefix);
-  assert.is('', args[0].suffix);
-  assert.is('ScriptArgument', args[0].type);
+  assert.is(args[0].text, '{w} {{{x}}} {y} {{{z}}}');
+  assert.is(args[0].prefix, '{');
+  assert.is(args[0].suffix, '');
+  assert.is(args[0].type, 'ScriptArgument');
 };
 
 exports.testStrangeNesting = function(options) {
   var args;
 
   // Note: When we get real JS parsing this should break
   args = cli.tokenize('{"x}"}');
 
-  assert.is(2, args.length);
+  assert.is(args.length, 2);
 
-  assert.is('"x', args[0].text);
-  assert.is('{', args[0].prefix);
-  assert.is('}', args[0].suffix);
-  assert.is('ScriptArgument', args[0].type);
+  assert.is(args[0].text, '"x');
+  assert.is(args[0].prefix, '{');
+  assert.is(args[0].suffix, '}');
+  assert.is(args[0].type, 'ScriptArgument');
 
-  assert.is('}', args[1].text);
-  assert.is('"', args[1].prefix);
-  assert.is('', args[1].suffix);
-  assert.is('Argument', args[1].type);
+  assert.is(args[1].text, '}');
+  assert.is(args[1].prefix, '"');
+  assert.is(args[1].suffix, '');
+  assert.is(args[1].type, 'Argument');
 };
 
 exports.testComplex = function(options) {
   var args;
 
   args = cli.tokenize(' 1234  \'12 34\'');
 
-  assert.is(2, args.length);
+  assert.is(args.length, 2);
 
-  assert.is('1234', args[0].text);
-  assert.is(' ', args[0].prefix);
-  assert.is('', args[0].suffix);
-  assert.is('Argument', args[0].type);
+  assert.is(args[0].text, '1234');
+  assert.is(args[0].prefix, ' ');
+  assert.is(args[0].suffix, '');
+  assert.is(args[0].type, 'Argument');
 
-  assert.is('12 34', args[1].text);
-  assert.is('  \'', args[1].prefix);
-  assert.is('\'', args[1].suffix);
-  assert.is('Argument', args[1].type);
+  assert.is(args[1].text, '12 34');
+  assert.is(args[1].prefix, '  \'');
+  assert.is(args[1].suffix, '\'');
+  assert.is(args[1].type, 'Argument');
 
   args = cli.tokenize('12\'34 "12 34" \\'); // 12'34 "12 34" \
 
-  assert.is(3, args.length);
+  assert.is(args.length, 3);
 
-  assert.is('12\'34', args[0].text);
-  assert.is('', args[0].prefix);
-  assert.is('', args[0].suffix);
-  assert.is('Argument', args[0].type);
+  assert.is(args[0].text, '12\'34');
+  assert.is(args[0].prefix, '');
+  assert.is(args[0].suffix, '');
+  assert.is(args[0].type, 'Argument');
 
-  assert.is('12 34', args[1].text);
-  assert.is(' "', args[1].prefix);
-  assert.is('"', args[1].suffix);
-  assert.is('Argument', args[1].type);
+  assert.is(args[1].text, '12 34');
+  assert.is(args[1].prefix, ' "');
+  assert.is(args[1].suffix, '"');
+  assert.is(args[1].type, 'Argument');
 
-  assert.is('\\', args[2].text);
-  assert.is(' ', args[2].prefix);
-  assert.is('', args[2].suffix);
-  assert.is('Argument', args[2].type);
+  assert.is(args[2].text, '\\');
+  assert.is(args[2].prefix, ' ');
+  assert.is(args[2].suffix, '');
+  assert.is(args[2].type, 'Argument');
 };
 
 exports.testPathological = function(options) {
   var args;
 
   args = cli.tokenize('a\\ b \\t\\n\\r \\\'x\\\" \'d'); // a_b \t\n\r \'x\" 'd
 
-  assert.is(4, args.length);
+  assert.is(args.length, 4);
 
-  assert.is('a b', args[0].text);
-  assert.is('', args[0].prefix);
-  assert.is('', args[0].suffix);
-  assert.is('Argument', args[0].type);
+  assert.is(args[0].text, 'a\\ b');
+  assert.is(args[0].prefix, '');
+  assert.is(args[0].suffix, '');
+  assert.is(args[0].type, 'Argument');
 
-  assert.is('\t\n\r', args[1].text);
-  assert.is(' ', args[1].prefix);
-  assert.is('', args[1].suffix);
-  assert.is('Argument', args[1].type);
+  assert.is(args[1].text, '\\t\\n\\r');
+  assert.is(args[1].prefix, ' ');
+  assert.is(args[1].suffix, '');
+  assert.is(args[1].type, 'Argument');
 
-  assert.is('\'x"', args[2].text);
-  assert.is(' ', args[2].prefix);
-  assert.is('', args[2].suffix);
-  assert.is('Argument', args[2].type);
+  assert.is(args[2].text, '\\\'x\\"');
+  assert.is(args[2].prefix, ' ');
+  assert.is(args[2].suffix, '');
+  assert.is(args[2].type, 'Argument');
 
-  assert.is('d', args[3].text);
-  assert.is(' \'', args[3].prefix);
-  assert.is('', args[3].suffix);
-  assert.is('Argument', args[3].type);
+  assert.is(args[3].text, 'd');
+  assert.is(args[3].prefix, ' \'');
+  assert.is(args[3].suffix, '');
+  assert.is(args[3].type, 'Argument');
 };
 
 
 // });
--- a/browser/devtools/commandline/test/browser_gcli_types.js
+++ b/browser/devtools/commandline/test/browser_gcli_types.js
@@ -70,17 +70,18 @@ function forEachType(options, typeSpec, 
 
 exports.testDefault = function(options) {
   if (options.isJsdom) {
     assert.log('Skipping tests due to issues with resource type.');
     return;
   }
 
   forEachType(options, {}, function(type) {
-    var blank = type.getBlank().value;
+    var context = options.display.requisition.executionContext;
+    var blank = type.getBlank(context).value;
 
     // boolean and array types are exempt from needing undefined blank values
     if (type.name === 'boolean') {
       assert.is(blank, false, 'blank boolean is false');
     }
     else if (type.name === 'array') {
       assert.ok(Array.isArray(blank), 'blank array is array');
       assert.is(blank.length, 0, 'blank array is empty');
--- a/browser/devtools/commandline/test/mockCommands.js
+++ b/browser/devtools/commandline/test/mockCommands.js
@@ -92,16 +92,26 @@ var tsv = {
 };
 
 var tsr = {
   name: 'tsr',
   params: [ { name: 'text', type: 'string' } ],
   exec: createExec('tsr')
 };
 
+var tsrsrsr = {
+  name: 'tsrsrsr',
+  params: [
+    { name: 'p1', type: 'string' },
+    { name: 'p2', type: 'string' },
+    { name: 'p3', type: { name: 'string', allowBlank: true} },
+  ],
+  exec: createExec('tsrsrsr')
+};
+
 var tso = {
   name: 'tso',
   params: [ { name: 'text', type: 'string', defaultValue: null } ],
   exec: createExec('tso')
 };
 
 var tse = {
   name: 'tse',
@@ -390,16 +400,37 @@ var tslong = {
           defaultValue: "collapse"
         }
       ]
     }
   ],
   exec: createExec('tslong')
 };
 
+var tsdate = {
+  name: 'tsdate',
+  description: 'long param tests to catch problems with the jsb command',
+  params: [
+    {
+      name: 'd1',
+      type: 'date',
+    },
+    {
+      name: 'd2',
+      type: {
+        name: 'date',
+        min: '1 jan 2000',
+        max: '28 feb 2000',
+        step: 2
+      }
+    },
+  ],
+  exec: createExec('tsdate')
+};
+
 var tsfail = {
   name: 'tsfail',
   description: 'test errors',
   params: [
     {
       name: 'method',
       type: {
         name: 'selection',
@@ -471,16 +502,17 @@ mockCommands.setup = function(opts) {
     ]
   });
 
   types.addType(mockCommands.optionType);
   types.addType(mockCommands.optionValue);
 
   mockCommands.commands.tsv = canon.addCommand(tsv);
   mockCommands.commands.tsr = canon.addCommand(tsr);
+  mockCommands.commands.tsrsrsr = canon.addCommand(tsrsrsr);
   mockCommands.commands.tso = canon.addCommand(tso);
   mockCommands.commands.tse = canon.addCommand(tse);
   mockCommands.commands.tsj = canon.addCommand(tsj);
   mockCommands.commands.tsb = canon.addCommand(tsb);
   mockCommands.commands.tss = canon.addCommand(tss);
   mockCommands.commands.tsu = canon.addCommand(tsu);
   mockCommands.commands.tsf = canon.addCommand(tsf);
   mockCommands.commands.tsn = canon.addCommand(tsn);
@@ -494,22 +526,24 @@ mockCommands.setup = function(opts) {
   mockCommands.commands.tsnDeepDownNested = canon.addCommand(tsnDeepDownNested);
   mockCommands.commands.tsnDeepDownNestedCmd = canon.addCommand(tsnDeepDownNestedCmd);
   mockCommands.commands.tselarr = canon.addCommand(tselarr);
   mockCommands.commands.tsm = canon.addCommand(tsm);
   mockCommands.commands.tsg = canon.addCommand(tsg);
   mockCommands.commands.tshidden = canon.addCommand(tshidden);
   mockCommands.commands.tscook = canon.addCommand(tscook);
   mockCommands.commands.tslong = canon.addCommand(tslong);
+  mockCommands.commands.tsdate = canon.addCommand(tsdate);
   mockCommands.commands.tsfail = canon.addCommand(tsfail);
 };
 
 mockCommands.shutdown = function(opts) {
   canon.removeCommand(tsv);
   canon.removeCommand(tsr);
+  canon.removeCommand(tsrsrsr);
   canon.removeCommand(tso);
   canon.removeCommand(tse);
   canon.removeCommand(tsj);
   canon.removeCommand(tsb);
   canon.removeCommand(tss);
   canon.removeCommand(tsu);
   canon.removeCommand(tsf);
   canon.removeCommand(tsn);
@@ -523,16 +557,17 @@ mockCommands.shutdown = function(opts) {
   canon.removeCommand(tsnDeepDownNested);
   canon.removeCommand(tsnDeepDownNestedCmd);
   canon.removeCommand(tselarr);
   canon.removeCommand(tsm);
   canon.removeCommand(tsg);
   canon.removeCommand(tshidden);
   canon.removeCommand(tscook);
   canon.removeCommand(tslong);
+  canon.removeCommand(tsdate);
   canon.removeCommand(tsfail);
 
   types.removeType(mockCommands.optionType);
   types.removeType(mockCommands.optionValue);
 
   mockCommands.commands = {};
 };
 
--- a/browser/devtools/debugger/CmdDebugger.jsm
+++ b/browser/devtools/debugger/CmdDebugger.jsm
@@ -14,16 +14,51 @@ Cu.import('resource://gre/modules/XPCOMU
 
 XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
   "resource:///modules/devtools/gDevTools.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "console",
   "resource://gre/modules/devtools/Console.jsm");
 
 /**
+ * Utility to get access to the current breakpoint list
+ * @param dbg The debugger panel
+ * @returns an array of object, one for each breakpoint, where each breakpoint
+ * object has the following properties:
+ * - id: A unique identifier for the breakpoint. This is not designed to be
+ *       shown to the user.
+ * - label: A unique string identifier designed to be user visible. In theory
+ *          the label of a breakpoint could change
+ * - url: The URL of the source file
+ * - lineNumber: The line number of the breakpoint in the source file
+ * - lineText: The text of the line at the breakpoint
+ * - truncatedLineText: lineText truncated to MAX_LINE_TEXT_LENGTH
+ */
+function getAllBreakpoints(dbg) {
+  let breakpoints = [];
+  let SourceUtils = dbg.panelWin.SourceUtils;
+
+  for (let source in dbg.panelWin.DebuggerView.Sources) {
+    for (let { attachment: breakpoint } in source) {
+      breakpoints.push({
+        id: source.value + ":" + breakpoint.lineNumber,
+        label: source.label + ":" + breakpoint.lineNumber,
+        url: source.value,
+        lineNumber: breakpoint.lineNumber,
+        lineText: breakpoint.lineText,
+        truncatedLineText: SourceUtils.trimUrlLength(breakpoint.lineText,
+                                                  MAX_LINE_TEXT_LENGTH, "end")
+      });
+    }
+  }
+
+  return breakpoints;
+}
+
+/**
  * 'break' command
  */
 gcli.addCommand({
   name: "break",
   description: gcli.lookup("breakDesc"),
   manual: gcli.lookup("breakManual")
 });
 
@@ -32,60 +67,33 @@ gcli.addCommand({
  */
 gcli.addCommand({
   name: "break list",
   description: gcli.lookup("breaklistDesc"),
   returnType: "breakpoints",
   exec: function(args, context) {
     let dbg = getPanel(context, "jsdebugger", { ensure_opened: true });
     return dbg.then(function(dbg) {
-      let breakpoints = [];
-      for (let source in dbg.panelWin.DebuggerView.Sources) {
-        for (let { attachment: breakpoint } in source) {
-          breakpoints.push({
-            url: source.value,
-            label: source.label,
-            lineNumber: breakpoint.lineNumber,
-            lineText: breakpoint.lineText
-          });
-        }
-      }
-      return breakpoints;
+      return getAllBreakpoints(dbg);
     });
   }
 });
 
 gcli.addConverter({
   from: "breakpoints",
   to: "view",
   exec: function(breakpoints, context) {
     let dbg = getPanel(context, "jsdebugger");
     if (dbg && breakpoints.length) {
-      let SourceUtils = dbg.panelWin.SourceUtils;
-      let index = 0;
       return context.createView({
         html: breakListHtml,
         data: {
-          breakpoints: breakpoints.map(function(breakpoint) {
-            return {
-              index: index++,
-              url: breakpoint.url,
-              label: SourceUtils.trimUrlLength(
-                breakpoint.label + ":" + breakpoint.lineNumber,
-                MAX_LABEL_LENGTH,
-                "start"),
-              lineText: breakpoint.lineText,
-              truncatedLineText: SourceUtils.trimUrlLength(
-                breakpoint.lineText,
-                MAX_LINE_TEXT_LENGTH,
-                "end")
-            };
-          }),
-          onclick: createUpdateHandler(context),
-          ondblclick: createExecuteHandler(context)
+          breakpoints: breakpoints,
+          onclick: context.update,
+          ondblclick: context.updateExec
         }
       });
     } else {
       return context.createView({
         html: "<p>${message}</p>",
         data: { message: gcli.lookup("breaklistNone") }
       });
     }
@@ -102,17 +110,17 @@ var breakListHtml = "" +
       " <tbody>" +
       "  <tr foreach='breakpoint in ${breakpoints}'>" +
       "    <td class='gcli-breakpoint-label'>${breakpoint.label}</td>" +
       "    <td class='gcli-breakpoint-lineText'>" +
       "      ${breakpoint.truncatedLineText}" +
       "    </td>" +
       "    <td>" +
       "      <span class='gcli-out-shortcut'" +
-      "            data-command='break del ${breakpoint.index}'" +
+      "            data-command='break del ${breakpoint.label}'" +
       "            onclick='${onclick}'" +
       "            ondblclick='${ondblclick}'>" +
       "        " + gcli.lookup("breaklistOutRemove") + "</span>" +
       "    </td>" +
       "  </tr>" +
       " </tbody>" +
       "</table>" +
       "";
@@ -135,17 +143,17 @@ gcli.addCommand({
 gcli.addCommand({
   name: "break add line",
   description: gcli.lookup("breakaddlineDesc"),
   params: [
     {
       name: "file",
       type: {
         name: "selection",
-        data: function(args, context) {
+        data: function(context) {
           let dbg = getPanel(context, "jsdebugger");
           if (dbg) {
             return dbg.panelWin.DebuggerView.Sources.values;
           }
           return [];
         }
       },
       description: gcli.lookup("breakaddlineFileDesc")
@@ -181,47 +189,59 @@ gcli.addCommand({
 /**
  * 'break del' command
  */
 gcli.addCommand({
   name: "break del",
   description: gcli.lookup("breakdelDesc"),
   params: [
     {
-      name: "breakIndex",
+      name: "breakpoint",
       type: {
-        name: "number",
-        min: 0,
-        max: function(args, context) {
+        name: "selection",
+        lookup: function(context) {
           let dbg = getPanel(context, "jsdebugger");
-          return dbg == null ? 0 : Object.keys(dbg.getAllBreakpoints()).length - 1;
-        },
+
+          if (dbg == null) {
+            return [];
+          }
+
+          return getAllBreakpoints(dbg).map(breakpoint => {
+            return {
+              name: breakpoint.label,
+              value: breakpoint,
+              description: breakpoint.truncatedLineText
+            };
+          });
+        }
       },
       description: gcli.lookup("breakdelBreakidDesc")
     }
   ],
   returnType: "string",
   exec: function(args, context) {
     let dbg = getPanel(context, "jsdebugger");
     if (!dbg) {
       return gcli.lookup("debuggerStopped");
     }
 
-    let breakpoints = dbg.getAllBreakpoints();
-    let id = Object.keys(breakpoints)[args.breakIndex];
-    if (!id || !(id in breakpoints)) {
+    let breakpoint = dbg.getBreakpoint(args.breakpoint.url,
+                                       args.breakpoint.lineNumber);
+
+    if (breakpoint == null) {
       return gcli.lookup("breakNotFound");
     }
 
     let deferred = context.defer();
     try {
-      dbg.removeBreakpoint(breakpoints[id], function() {
+      dbg.removeBreakpoint(breakpoint, function() {
         deferred.resolve(gcli.lookup("breakdelRemoved"));
       });
     } catch (ex) {
+      console.error('Error removing breakpoint, already removed?', ex);
       // If the debugger has been closed already, don't scare the user.
       deferred.resolve(gcli.lookup("breakdelRemoved"));
     }
     return deferred.promise;
   }
 });
 
 /**
@@ -236,29 +256,31 @@ gcli.addCommand({
 /**
  * 'dbg open' command
  */
 gcli.addCommand({
   name: "dbg open",
   description: gcli.lookup("dbgOpen"),
   params: [],
   exec: function(args, context) {
-    return gDevTools.showToolbox(context.environment.target, "jsdebugger").then(() => null);
+    return gDevTools.showToolbox(context.environment.target, "jsdebugger")
+                    .then(() => null);
   }
 });
 
 /**
  * 'dbg close' command
  */
 gcli.addCommand({
   name: "dbg close",
   description: gcli.lookup("dbgClose"),
   params: [],
   exec: function(args, context) {
-    return gDevTools.closeToolbox(context.environment.target).then(() => null);
+    return gDevTools.closeToolbox(context.environment.target)
+                    .then(() => null);
   }
 });
 
 /**
  * 'dbg interrupt' command
  */
 gcli.addCommand({
   name: "dbg interrupt",
@@ -403,64 +425,16 @@ gcli.addCommand({
 /**
  * A helper to create xhtml namespaced elements
  */
 function createXHTMLElement(document, tagname) {
   return document.createElementNS("http://www.w3.org/1999/xhtml", tagname);
 }
 
 /**
- * Helper to find the 'data-command' attribute and call some action on it.
- * @see |updateCommand()| and |executeCommand()|
- */
-function withCommand(element, action) {
-  let command = element.getAttribute("data-command");
-  if (!command) {
-    command = element.querySelector("*[data-command]").getAttribute("data-command");
-  }
-
-  if (command) {
-    action(command);
-  } else {
-    console.warn("Missing data-command for " + util.findCssSelector(element));
-  }
-}
-
-/**
- * Create a handler to update the requisition to contain the text held in the
- * first matching data-command attribute under the currentTarget of the event.
- * @param context Either a Requisition or an ExecutionContext or another object
- * that contains an |update()| function that follows a similar contract.
- */
-function createUpdateHandler(context) {
-  return function(ev) {
-    withCommand(ev.currentTarget, function(command) {
-      context.update(command);
-    });
-  }
-}
-
-/**
- * Create a handler to execute the text held in the data-command attribute
- * under the currentTarget of the event.
- * @param context Either a Requisition or an ExecutionContext or another object
- * that contains an |update()| function that follows a similar contract.
- */
-function createExecuteHandler(context) {
-  return function(ev) {
-    withCommand(ev.currentTarget, function(command) {
-      context.exec({
-        visible: true,
-        typed: command
-      });
-    });
-  }
-}
-
-/**
  * A helper to go from a command context to a debugger panel
  */
 function getPanel(context, id, options = {}) {
   if (context == null) {
     return undefined;
   }
 
   let target = context.environment.target;
--- a/browser/devtools/debugger/test/Makefile.in
+++ b/browser/devtools/debugger/test/Makefile.in
@@ -9,17 +9,17 @@ VPATH           = @srcdir@
 relativesrcdir  = @relativesrcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MOCHITEST_BROWSER_TESTS = \
 	browser_dbg_aaa_run_first_leaktest.js \
 	browser_dbg_clean-exit.js \
 	browser_dbg_cmd.js \
-	$(browser_dbg_cmd_break.js disabled until bug 722727 is fixed) \
+	browser_dbg_cmd_break.js \
 	$(browser_dbg_createRemote.js disabled for intermittent failures, bug 753225) \
 	browser_dbg_debuggerstatement.js \
 	browser_dbg_listtabs.js \
 	browser_dbg_tabactor-01.js \
 	browser_dbg_tabactor-02.js \
 	browser_dbg_globalactor-01.js \
 	browser_dbg_nav-01.js \
 	browser_dbg_propertyview-01.js \
--- a/browser/devtools/debugger/test/browser_dbg_cmd_break.js
+++ b/browser/devtools/debugger/test/browser_dbg_cmd_break.js
@@ -79,73 +79,142 @@ function test() {
           return helpers.setInput(options, line);
         },
         check: {
           hints: '',
           status: 'VALID',
           message: '',
           args: {
             file: { value: TEST_URI, message: '' },
-            line: { value: 10 }, // would like to use line0, but see above
-                                 // if this proves to be too fragile, disable
+            line: { value: 10 }
           }
         },
         exec: {
-          output: '',
+          output: 'Added breakpoint',
+          completed: false
+        },
+      },
+      {
+        setup: 'break add line http://example.com/browser/browser/devtools/debugger/test/browser_dbg_cmd_break.html 13',
+        check: {
+          hints: '',
+          status: 'VALID',
+          message: '',
+          args: {
+            file: { value: TEST_URI, message: '' },
+            line: { value: 13 }
+          }
+        },
+        exec: {
+          output: 'Added breakpoint',
           completed: false
         },
       },
       {
         setup: 'break list',
         check: {
           input:  'break list',
           hints:            '',
           markup: 'VVVVVVVVVV',
           status: 'VALID'
         },
         exec: {
-          output: ''
+          output: [
+            /Source/, /Remove/,
+            /cmd_break\.html:10/,
+            /cmd_break\.html:13/
+          ]
         },
       },
       {
         name: 'cleanup',
         setup: function() {
+          // a.k.a "return client.activeThread.resume();"
           var deferred = Promise.defer();
           client.activeThread.resume(function() {
             deferred.resolve();
           });
           return deferred.promise;
         },
       },
       {
-        setup: 'break del 9',
+        setup: 'break del 0',
         check: {
-          input:  'break del 9',
-          hints:             '',
-          markup: 'VVVVVVVVVVE',
+          input:  'break del 0',
+          hints:             ' -> browser_dbg_cmd_break.html:10',
+          markup: 'VVVVVVVVVVI',
           status: 'ERROR',
           args: {
-            breakid: {
-              status: 'ERROR',
-              message: '9 is greater than maximum allowed: 0.'
+            breakpoint: {
+              status: 'INCOMPLETE',
+              message: ''
             },
           }
         },
       },
       {
-        setup: 'break del 0',
+        setup: 'break del browser_dbg_cmd_break.html:10',
         check: {
-          input:  'break del 0',
-          hints:             '',
-          markup: 'VVVVVVVVVVV',
+          input:  'break del browser_dbg_cmd_break.html:10',
+          hints:                                         '',
+          markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV',
           status: 'VALID',
           args: {
-            breakid: { value: 0 },
+            breakpoint: { arg: ' browser_dbg_cmd_break.html:10' },
           }
         },
         exec: {
-          output: '',
+          output: 'Breakpoint removed',
           completed: false
         },
       },
+      {
+        setup: 'break list',
+        check: {
+          input:  'break list',
+          hints:            '',
+          markup: 'VVVVVVVVVV',
+          status: 'VALID'
+        },
+        exec: {
+          output: [
+            /Source/, /Remove/,
+            /browser_dbg_cmd_break\.html:13/
+          ]
+        },
+      },
+      {
+        setup: 'break del browser_dbg_cmd_break.html:13',
+        check: {
+          input:  'break del browser_dbg_cmd_break.html:13',
+          hints:                                         '',
+          markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV',
+          status: 'VALID',
+          args: {
+            breakpoint: { arg: ' browser_dbg_cmd_break.html:13' },
+          }
+        },
+        exec: {
+          output: 'Breakpoint removed',
+          completed: false
+        },
+      },
+      {
+        setup: 'break list',
+        check: {
+          input:  'break list',
+          hints:            '',
+          markup: 'VVVVVVVVVV',
+          status: 'VALID'
+        },
+        exec: {
+          output: 'No breakpoints set'
+        },
+        post: function() {
+          client = undefined;
+
+          let toolbox = gDevTools.getToolbox(options.target);
+          return toolbox.destroy();
+        }
+      },
     ]);
   }).then(finish);
 }
--- a/browser/devtools/framework/toolbox.js
+++ b/browser/devtools/framework/toolbox.js
@@ -315,18 +315,19 @@ Toolbox.prototype = {
    * Add buttons to the UI as specified in the devtools.window.toolbarSpec pref
    */
   _buildButtons: function TBOX_buildButtons() {
     if (!this.target.isLocalTab) {
       return;
     }
 
     let toolbarSpec = CommandUtils.getCommandbarSpec("devtools.toolbox.toolbarSpec");
-    let environment = { chromeDocument: this.target.tab.ownerDocument };
-    let requisition = new Requisition(environment);
+    let env = CommandUtils.createEnvironment(this.target.tab.ownerDocument,
+                                             this.target.window.document);
+    let requisition = new Requisition(env);
 
     let buttons = CommandUtils.createButtons(toolbarSpec, this._target, this.doc, requisition);
 
     let container = this.doc.getElementById("toolbox-buttons");
     buttons.forEach(function(button) {
       container.appendChild(button);
     }.bind(this));
   },
--- a/browser/devtools/shared/DeveloperToolbar.jsm
+++ b/browser/devtools/shared/DeveloperToolbar.jsm
@@ -151,17 +151,17 @@ let CommandUtils = {
    * GCLI commands.
    */
   createEnvironment: function(chromeDocument, contentDocument) {
     let environment = {
       chromeDocument: chromeDocument,
       chromeWindow: chromeDocument.defaultView,
 
       document: contentDocument,
-      window: contentDocument.defaultView
+      window: contentDocument != null ? contentDocument.defaultView : undefined
     };
 
     Object.defineProperty(environment, "target", {
       get: function() {
         let tab = chromeDocument.defaultView.getBrowser().selectedTab;
         return devtools.TargetFactory.forTab(tab);
       },
       enumerable: true
--- a/browser/locales/en-US/chrome/browser/devtools/gcli.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/gcli.properties
@@ -109,16 +109,31 @@ typesNumberMax=%1$S is greater than maxi
 # number, but the number is lower than the smallest allowed number, this error
 # message is displayed.
 typesNumberMin=%1$S is smaller than minimum allowed: %2$S.
 
 # LOCALIZATION NOTE (typesNumberNotInt2): When the command line is passed a
 # number, but the number has a decimal part and floats are not allowed.
 typesNumberNotInt2=Can't convert "%S" to an integer.
 
+# LOCALIZATION NOTE (typesDateNan): When the command line is passed a date,
+# however the input string is not a valid date, this error message is
+# displayed.
+typesDateNan=Can't convert "%S" to a date.
+
+# LOCALIZATION NOTE (typesDateMax): When the command line is passed a date,
+# but the number is later than the latest allowed date, this error message is
+# displayed.
+typesDateMax=%1$S is later than maximum allowed: %2$S.
+
+# LOCALIZATION NOTE (typesDateMin): When the command line is passed a date,
+# but the date is earlier than the earliest allowed number, this error message
+# is displayed.
+typesDateMin=%1$S is earlier than minimum allowed: %2$S.
+
 # LOCALIZATION NOTE (typesSelectionNomatch): When the command line is passed
 # an option with a limited number of correct values, but the passed value is
 # not one of them, this error message is displayed.
 typesSelectionNomatch=Can't use '%S'.
 
 # LOCALIZATION NOTE (nodeParseSyntax): When the command line is expecting a
 # CSS query string, however the passed string is not valid, this error message
 # is displayed.
@@ -183,22 +198,27 @@ helpListPrefix=Commands starting with '%
 
 # LOCALIZATION NOTE (helpListNone): The heading shown in response to the 'help
 # <search>' command (i.e. with a search string), when there are no matching
 # commands.
 helpListNone=No commands starting with '%1$S'
 
 # LOCALIZATION NOTE (helpManRequired): When the 'help x' command wants to show
 # the manual for the 'x' command it needs to be able to describe the
-# parameters as either required or optional. See also 'helpManOptional'.
+# parameters as either required or optional, or if they have a default value.
+# See also 'helpManOptional' and 'helpManDefault'.
 helpManRequired=required
 
 # LOCALIZATION NOTE (helpManOptional): See description of 'helpManRequired'
 helpManOptional=optional
 
+# LOCALIZATION NOTE (helpManDefault): See description of 'helpManRequired'. %1$
+# S is the default value
+helpManDefault=optional, default=%1$S
+
 # LOCALIZATION NOTE (subCommands): Text shown as part of the output of the
 # 'help' command when the command in question has sub-commands, before a list
 # of the matching sub-commands
 subCommands=Sub-Commands
 
 # LOCALIZATION NOTE (subCommandsNone): Text shown as part of the output of the
 # 'help' command when the command in question should have sub-commands but in
 # fact has none
@@ -467,17 +487,19 @@ introTextF1Escape=F1/Escape
 # introTextOpening2. The text on the button that dismisses the intro text.
 introTextGo=Got it!
 
 # LOCALIZATION NOTE (hideIntroDesc): Short description of the 'hideIntro'
 # setting. Displayed when the user asks for help on the settings.
 hideIntroDesc=Show the initial welcome message
 
 # LOCALIZATION NOTE (eagerHelperDesc): Short description of the 'eagerHelper'
-# setting. Displayed when the user asks for help on the settings.
+# setting. Displayed when the user asks for help on the settings. eagerHelper
+# allows users to select between showing no tooltips, permanent tooltips, and
+# only important tooltips
 eagerHelperDesc=How eager are the tooltips
 
 # LOCALIZATION NOTE (allowSetDesc): Short description of the 'allowSetDesc'
 # setting. Displayed when the user asks for help on the settings.
 allowSetDesc=Has the user enabled the 'pref set' command?
 
 # LOCALIZATION NOTE (introBody): The text displayed at the top of the output
 # for the help command, just before the list of commands. This text is wrapped
--- a/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
@@ -362,20 +362,16 @@ breakdelDesc=Remove a breakpoint
 # LOCALIZATION NOTE (breakdelBreakidDesc) A very short string used to describe
 # the function of the index parameter in the 'break del' command.
 breakdelBreakidDesc=Index of breakpoint
 
 # LOCALIZATION NOTE (breakdelRemoved) Used in the output of the 'break del'
 # command to explain that a breakpoint was removed.
 breakdelRemoved=Breakpoint removed
 
-# LOCALIZATION NOTE (breakNotFound) Used in the output of the 'break del'
-# command to explain that the breakpoint was not found.
-breakNotFound=Breakpoint was not found
-
 # LOCALIZATION NOTE (dbgDesc) A very short string used to describe the
 # function of the dbg command.
 dbgDesc=Manage debugger
 
 # LOCALIZATION NOTE (dbgManual) A longer description describing the
 # set of commands that control the debugger.
 dbgManual=Commands to interrupt or resume the main thread, step in, out and over lines of code
 
--- a/toolkit/devtools/gcli/gcli.jsm
+++ b/toolkit/devtools/gcli/gcli.jsm
@@ -28,16 +28,17 @@ this.EXPORTED_SYMBOLS = [ "gcli" ];
 
 var define = Components.utils.import("resource://gre/modules/devtools/Require.jsm", {}).define;
 var require = Components.utils.import("resource://gre/modules/devtools/Require.jsm", {}).require;
 var console = Components.utils.import("resource://gre/modules/devtools/Console.jsm", {}).console;
 var setTimeout = Components.utils.import("resource://gre/modules/Timer.jsm", {}).setTimeout;
 var clearTimeout = Components.utils.import("resource://gre/modules/Timer.jsm", {}).clearTimeout;
 var Node = Components.interfaces.nsIDOMNode;
 var HTMLElement = Components.interfaces.nsIDOMHTMLElement;
+var Event = Components.interfaces.nsIDOMEvent;
 
 /*
  * Copyright 2012, Mozilla Foundation and contributors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -98,26 +99,27 @@ var mozl10n = {};
     }
     catch (ex) {
       throw new Error("Failure in lookupFormat('" + name + "')");
     }
   };
 
 })(mozl10n);
 
-define('gcli/index', ['require', 'exports', 'module' , 'gcli/types/basic', 'gcli/types/selection', 'gcli/types/command', 'gcli/types/javascript', 'gcli/types/node', 'gcli/types/resource', 'gcli/types/setting', 'gcli/settings', 'gcli/ui/intro', 'gcli/ui/focus', 'gcli/ui/fields/basic', 'gcli/ui/fields/javascript', 'gcli/ui/fields/selection', 'gcli/commands/connect', 'gcli/commands/context', 'gcli/commands/help', 'gcli/commands/pref', 'gcli/canon', 'gcli/converters', 'gcli/ui/ffdisplay'], function(require, exports, module) {
+define('gcli/index', ['require', 'exports', 'module' , 'gcli/types/basic', 'gcli/types/selection', 'gcli/types/command', 'gcli/types/date', 'gcli/types/javascript', 'gcli/types/node', 'gcli/types/resource', 'gcli/types/setting', 'gcli/settings', 'gcli/ui/intro', 'gcli/ui/focus', 'gcli/ui/fields/basic', 'gcli/ui/fields/javascript', 'gcli/ui/fields/selection', 'gcli/commands/connect', 'gcli/commands/context', 'gcli/commands/help', 'gcli/commands/pref', 'gcli/canon', 'gcli/converters', 'gcli/ui/ffdisplay'], function(require, exports, module) {
 
   'use strict';
 
   // Internal startup process. Not exported
   // The basic/selection are depended on by others so they must come first
   require('gcli/types/basic').startup();
   require('gcli/types/selection').startup();
 
   require('gcli/types/command').startup();
+  require('gcli/types/date').startup();
   require('gcli/types/javascript').startup();
   require('gcli/types/node').startup();
   require('gcli/types/resource').startup();
   require('gcli/types/setting').startup();
 
   require('gcli/settings').startup();
   require('gcli/ui/intro').startup();
   require('gcli/ui/focus').startup();
@@ -220,35 +222,81 @@ exports.shutdown = function() {
   types.removeType(BooleanType);
   types.removeType(BlankType);
   types.removeType(DelegateType);
   types.removeType(ArrayType);
 };
 
 
 /**
- * 'string' the most basic string type that doesn't need to convert
+ * 'string' the most basic string type where all we need to do is to take care
+ * of converting escaped characters like \t, \n, etc. For the full list see
+ * https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Values,_variables,_and_literals
+ *
+ * The exception is that we ignore \b because replacing '\b' characters in
+ * stringify() with their escaped version injects '\\b' all over the place and
+ * the need to support \b seems low)
+ *
+ * @param typeSpec Options object, currently obeys only one parameter:
+ * - allowBlank: Allow a blank string to be counted as valid
  */
 function StringType(typeSpec) {
+  this._allowBlank = !!typeSpec.allowBlank;
 }
 
 StringType.prototype = Object.create(Type.prototype);
 
 StringType.prototype.stringify = function(value, context) {
   if (value == null) {
     return '';
   }
-  return value.toString();
+
+  return value
+       .replace(/\\/g, '\\\\')
+       .replace(/\f/g, '\\f')
+       .replace(/\n/g, '\\n')
+       .replace(/\r/g, '\\r')
+       .replace(/\t/g, '\\t')
+       .replace(/\v/g, '\\v')
+       .replace(/\n/g, '\\n')
+       .replace(/\r/g, '\\r')
+       .replace(/ /g, '\\ ')
+       .replace(/'/g, '\\\'')
+       .replace(/"/g, '\\"')
+       .replace(/{/g, '\\{')
+       .replace(/}/g, '\\}');
 };
 
 StringType.prototype.parse = function(arg, context) {
-  if (arg.text == null || arg.text === '') {
+  if (!this._allowBlank && (arg.text == null || arg.text === '')) {
     return Promise.resolve(new Conversion(undefined, arg, Status.INCOMPLETE, ''));
   }
-  return Promise.resolve(new Conversion(arg.text, arg));
+
+  // The string '\\' (i.e. an escaped \ (represented here as '\\\\' because it
+  // is double escaped)) is first converted to a private unicode character and
+  // then at the end from \uF000 to a single '\' to avoid the string \\n being
+  // converted first to \n and then to a <LF>
+
+  var value = arg.text
+       .replace(/\\\\/g, '\uF000')
+       .replace(/\\f/g, '\f')
+       .replace(/\\n/g, '\n')
+       .replace(/\\r/g, '\r')
+       .replace(/\\t/g, '\t')
+       .replace(/\\v/g, '\v')
+       .replace(/\\n/g, '\n')
+       .replace(/\\r/g, '\r')
+       .replace(/\\ /g, ' ')
+       .replace(/\\'/g, '\'')
+       .replace(/\\"/g, '"')
+       .replace(/\\{/g, '{')
+       .replace(/\\}/g, '}')
+       .replace(/\uF000/g, '\\');
+
+  return Promise.resolve(new Conversion(value, arg));
 };
 
 StringType.prototype.name = 'string';
 
 exports.StringType = StringType;
 
 
 /**
@@ -279,32 +327,32 @@ NumberType.prototype = Object.create(Typ
 
 NumberType.prototype.stringify = function(value, context) {
   if (value == null) {
     return '';
   }
   return '' + value;
 };
 
-NumberType.prototype.getMin = function() {
+NumberType.prototype.getMin = function(context) {
   if (this._min) {
     if (typeof this._min === 'function') {
-      return this._min();
+      return this._min(context);
     }
     if (typeof this._min === 'number') {
       return this._min;
     }
   }
   return undefined;
 };
 
-NumberType.prototype.getMax = function() {
+NumberType.prototype.getMax = function(context) {
   if (this._max) {
     if (typeof this._max === 'function') {
-      return this._max();
+      return this._max(context);
     }
     if (typeof this._max === 'number') {
       return this._max;
     }
   }
   return undefined;
 };
 
@@ -326,66 +374,66 @@ NumberType.prototype.parse = function(ar
     value = parseInt(arg.text, 10);
   }
 
   if (isNaN(value)) {
     var message = l10n.lookupFormat('typesNumberNan', [ arg.text ]);
     return Promise.resolve(new Conversion(undefined, arg, Status.ERROR, message));
   }
 
-  var max = this.getMax();
+  var max = this.getMax(context);
   if (max != null && value > max) {
     var message = l10n.lookupFormat('typesNumberMax', [ value, max ]);
     return Promise.resolve(new Conversion(undefined, arg, Status.ERROR, message));
   }
 
-  var min = this.getMin();
+  var min = this.getMin(context);
   if (min != null && value < min) {
     var message = l10n.lookupFormat('typesNumberMin', [ value, min ]);
     return Promise.resolve(new Conversion(undefined, arg, Status.ERROR, message));
   }
 
   return Promise.resolve(new Conversion(value, arg));
 };
 
 NumberType.prototype.decrement = function(value, context) {
   if (typeof value !== 'number' || isNaN(value)) {
-    return this.getMax() || 1;
+    return this.getMax(context) || 1;
   }
   var newValue = value - this._step;
   // Snap to the nearest incremental of the step
   newValue = Math.ceil(newValue / this._step) * this._step;
-  return this._boundsCheck(newValue);
+  return this._boundsCheck(newValue, context);
 };
 
 NumberType.prototype.increment = function(value, context) {
   if (typeof value !== 'number' || isNaN(value)) {
-    var min = this.getMin();
+    var min = this.getMin(context);
     return min != null ? min : 0;
   }
   var newValue = value + this._step;
   // Snap to the nearest incremental of the step
   newValue = Math.floor(newValue / this._step) * this._step;
-  if (this.getMax() == null) {
+  if (this.getMax(context) == null) {
     return newValue;
   }
-  return this._boundsCheck(newValue);
+  return this._boundsCheck(newValue, context);
 };
 
 /**
  * Return the input value so long as it is within the max/min bounds. If it is
  * lower than the minimum, return the minimum. If it is bigger than the maximum
  * then return the maximum.
  */
-NumberType.prototype._boundsCheck = function(value) {
-  var min = this.getMin();
+NumberType.prototype._boundsCheck = function(value, context) {
+  var min = this.getMin(context);
   if (min != null && value < min) {
     return min;
   }
-  var max = this.getMax();
+  var max = this.getMax(context);
   if (max != null && value > max) {
     return max;
   }
   return value;
 };
 
 /**
  * Return true if the given value is a finite number and not an integer, else
@@ -425,17 +473,17 @@ BooleanType.prototype.parse = function(a
 
 BooleanType.prototype.stringify = function(value, context) {
   if (value == null) {
     return '';
   }
   return '' + value;
 };
 
-BooleanType.prototype.getBlank = function() {
+BooleanType.prototype.getBlank = function(context) {
   return new Conversion(false, new BlankArgument(), Status.VALID, '',
                         Promise.resolve(this.lookup));
 };
 
 BooleanType.prototype.name = 'boolean';
 
 exports.BooleanType = BooleanType;
 
@@ -573,17 +621,17 @@ ArrayType.prototype.parse = function(arg
   }.bind(this);
 
   var conversionPromises = arg.getArguments().map(subArgParse);
   return util.all(conversionPromises).then(function(conversions) {
     return new ArrayConversion(conversions, arg);
   });
 };
 
-ArrayType.prototype.getBlank = function(values) {
+ArrayType.prototype.getBlank = function(context) {
   return new ArrayConversion([], new ArrayArgument());
 };
 
 ArrayType.prototype.name = 'array';
 
 exports.ArrayType = ArrayType;
 
 
@@ -1964,17 +2012,17 @@ Type.prototype.decrement = function(valu
 };
 
 /**
  * The 'blank value' of most types is 'undefined', but there are exceptions;
  * This allows types to specify a better conversion from empty string than
  * 'undefined'.
  * 2 known examples of this are boolean -> false and array -> []
  */
-Type.prototype.getBlank = function() {
+Type.prototype.getBlank = function(context) {
   return new Conversion(undefined, new BlankArgument(), Status.INCOMPLETE, '');
 };
 
 /**
  * This is something of a hack for the benefit of DelegateType which needs to
  * be able to lie about it's type for fields to accept it as one of their own.
  * Sub-types can ignore this unless they're DelegateType.
  * @param context An ExecutionContext to allow basic Requisition access
@@ -2774,17 +2822,17 @@ SelectionType.prototype.stringify = func
     return '';
   }
   if (this.stringifyProperty != null) {
     return value[this.stringifyProperty];
   }
 
   try {
     var name = null;
-    var lookup = util.synchronize(this.getLookup());
+    var lookup = util.synchronize(this.getLookup(context));
     lookup.some(function(item) {
       if (item.value === value) {
         name = item.name;
         return true;
       }
       return false;
     }, this);
     return name;
@@ -2805,31 +2853,31 @@ SelectionType.prototype.clearCache = fun
   delete this._cachedLookup;
 };
 
 /**
  * There are several ways to get selection data. This unifies them into one
  * single function.
  * @return An array of objects with name and value properties.
  */
-SelectionType.prototype.getLookup = function() {
+SelectionType.prototype.getLookup = function(context) {
   if (this._cachedLookup != null) {
     return this._cachedLookup;
   }
 
   var reply;
   if (this.lookup == null) {
-    reply = resolve(this.data, this.neverForceAsync).then(dataToLookup);
+    reply = resolve(this.data, context, this.neverForceAsync).then(dataToLookup);
   }
   else {
     var lookup = (typeof this.lookup === 'function') ?
             this.lookup.bind(this) :
             this.lookup;
 
-    reply = resolve(lookup, this.neverForceAsync);
+    reply = resolve(lookup, context, this.neverForceAsync);
   }
 
   if (this.cacheable && !forceAsync) {
     this._cachedLookup = reply;
   }
 
   return reply;
 };
@@ -2837,34 +2885,34 @@ SelectionType.prototype.getLookup = func
 var forceAsync = false;
 
 /**
  * Both 'lookup' and 'data' properties (see docs on SelectionType constructor)
  * in addition to being real data can be a function or a promise, or even a
  * function which returns a promise of real data, etc. This takes a thing and
  * returns a promise of actual values.
  */
-function resolve(thing, neverForceAsync) {
+function resolve(thing, context, neverForceAsync) {
   if (forceAsync && !neverForceAsync) {
     var deferred = Promise.defer();
     setTimeout(function() {
       Promise.resolve(thing).then(function(resolved) {
         if (typeof resolved === 'function') {
           resolved = resolve(resolved(), neverForceAsync);
         }
 
         deferred.resolve(resolved);
       });
     }, 500);
     return deferred.promise;
   }
 
   return Promise.resolve(thing).then(function(resolved) {
     if (typeof resolved === 'function') {
-      return resolve(resolved(), neverForceAsync);
+      return resolve(resolved(context), context, neverForceAsync);
     }
     return resolved;
   });
 }
 
 /**
  * Selection can be provided with either a lookup object (in the 'lookup'
  * property) or an array of strings (in the 'data' property). Internally we
@@ -2880,18 +2928,18 @@ function dataToLookup(data) {
   }, this);
 };
 
 /**
  * Return a list of possible completions for the given arg.
  * @param arg The initial input to match
  * @return A trimmed array of string:value pairs
  */
-SelectionType.prototype._findPredictions = function(arg) {
-  return Promise.resolve(this.getLookup()).then(function(lookup) {
+SelectionType.prototype._findPredictions = function(arg, context) {
+  return Promise.resolve(this.getLookup(context)).then(function(lookup) {
     var predictions = [];
     var i, option;
     var maxPredictions = Conversion.maxPredictions;
     var match = arg.text.toLowerCase();
 
     // If the arg has a suffix then we're kind of 'done'. Only an exact match
     // will do.
     if (arg.suffix.length > 0) {
@@ -2972,17 +3020,17 @@ SelectionType.prototype._findPredictions
  * to make an extra check before actually adding which SelectionType does not
  * need to make.
  */
 SelectionType.prototype._addToPredictions = function(predictions, option, arg) {
   predictions.push(option);
 };
 
 SelectionType.prototype.parse = function(arg, context) {
-  return this._findPredictions(arg).then(function(predictions) {
+  return this._findPredictions(arg, context).then(function(predictions) {
     if (predictions.length === 0) {
       var msg = l10n.lookupFormat('typesSelectionNomatch', [ arg.text ]);
       return new Conversion(undefined, arg, Status.ERROR, msg,
                             Promise.resolve(predictions));
     }
 
     if (predictions[0].name === arg.text) {
       var value = predictions[0].value;
@@ -2990,19 +3038,19 @@ SelectionType.prototype.parse = function
                             Promise.resolve(predictions));
     }
 
     return new Conversion(undefined, arg, Status.INCOMPLETE, '',
                           Promise.resolve(predictions));
   }.bind(this));
 };
 
-SelectionType.prototype.getBlank = function() {
+SelectionType.prototype.getBlank = function(context) {
   var predictFunc = function() {
-    return Promise.resolve(this.getLookup()).then(function(lookup) {
+    return Promise.resolve(this.getLookup(context)).then(function(lookup) {
       return lookup.filter(function(option) {
         return !option.value.hidden;
       }).slice(0, Conversion.maxPredictions - 1);
     });
   }.bind(this);
 
   return new Conversion(undefined, new BlankArgument(), Status.INCOMPLETE, '',
                         predictFunc);
@@ -3011,33 +3059,33 @@ SelectionType.prototype.getBlank = funct
 /**
  * For selections, up is down and black is white. It's like this, given a list
  * [ a, b, c, d ], it's natural to think that it starts at the top and that
  * going up the list, moves towards 'a'. However 'a' has the lowest index, so
  * for SelectionType, up is down and down is up.
  * Sorry.
  */
 SelectionType.prototype.decrement = function(value, context) {
-  var lookup = util.synchronize(this.getLookup());
+  var lookup = util.synchronize(this.getLookup(context));
   var index = this._findValue(lookup, value);
   if (index === -1) {
     index = 0;
   }
   index++;
   if (index >= lookup.length) {
     index = 0;
   }
   return lookup[index].value;
 };
 
 /**
  * See note on SelectionType.decrement()
  */
 SelectionType.prototype.increment = function(value, context) {
-  var lookup = util.synchronize(this.getLookup());
+  var lookup = util.synchronize(this.getLookup(context));
   var index = this._findValue(lookup, value);
   if (index === -1) {
     // For an increment operation when there is nothing to start from, we
     // want to start from the top, i.e. index 0, so the value before we
     // 'increment' (see note above) must be 1.
     index = 1;
   }
   index--;
@@ -3614,25 +3662,16 @@ Object.defineProperty(Parameter.prototyp
 Parameter.prototype.isKnownAs = function(name) {
   if (name === '--' + this.name) {
     return true;
   }
   return false;
 };
 
 /**
- * Read the default value for this parameter either from the parameter itself
- * (if this function has been over-ridden) or from the type, or from calling
- * parseString on an empty string
- */
-Parameter.prototype.getBlank = function() {
-  return this.type.getBlank();
-};
-
-/**
  * Resolve the manual for this parameter, by looking in the paramSpec
  * and doing a l10n lookup
  */
 Object.defineProperty(Parameter.prototype, 'manual', {
   get: function() {
     return lookup(this.paramSpec.manual || undefined);
   },
   enumerable: true
@@ -3899,16 +3938,247 @@ function CommandOutputManager() {
   this.onOutput = util.createEvent('CommandOutputManager.onOutput');
 }
 
 exports.CommandOutputManager = CommandOutputManager;
 
 
 });
 /*
+ * Copyright 2009-2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE.txt or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+
+define('gcli/types/date', ['require', 'exports', 'module' , 'util/promise', 'util/l10n', 'gcli/types'], function(require, exports, module) {
+
+'use strict';
+
+var Promise = require('util/promise');
+var l10n = require('util/l10n');
+
+var types = require('gcli/types');
+var Type = require('gcli/types').Type;
+var Status = require('gcli/types').Status;
+var Conversion = require('gcli/types').Conversion;
+
+
+function DateType(typeSpec) {
+  // ECMA 5.1 ยง15.9.1.1
+  // @see http://stackoverflow.com/questions/11526504/minimum-and-maximum-date
+  typeSpec = typeSpec || {};
+
+  this._step = typeSpec.step || 1;
+  this._min = new Date(-8640000000000000);
+  this._max = new Date(8640000000000000);
+
+  if (typeSpec.min != null) {
+    if (typeof typeSpec.min === 'string') {
+      this._min = toDate(typeSpec.min);
+    }
+    else if (isDate(typeSpec.min) || typeof typeSpec.min === 'function') {
+      this._min = typeSpec.min;
+    }
+    else {
+      throw new Error('date min value must be a string a date or a function');
+    }
+  }
+
+  if (typeSpec.max != null) {
+    if (typeof typeSpec.max === 'string') {
+      this._max = toDate(typeSpec.max);
+    }
+    else if (isDate(typeSpec.max) || typeof typeSpec.max === 'function') {
+      this._max = typeSpec.max;
+    }
+    else {
+      throw new Error('date max value must be a string a date or a function');
+    }
+  }
+}
+
+DateType.prototype = Object.create(Type.prototype);
+
+/**
+ * Helper for stringify() to left pad a single digit number with a single '0'
+ * so 1 -> '01', 42 -> '42', etc.
+ */
+function pad(number) {
+  var r = String(number);
+  return r.length === 1 ? '0' + r : r;
+}
+
+DateType.prototype.stringify = function(value) {
+  if (!isDate(value)) {
+    return '';
+  }
+
+  var str = pad(value.getFullYear()) + '-' +
+            pad(value.getMonth() + 1) + '-' +
+            pad(value.getDate());
+
+  // Only add in the time if it's not midnight
+  if (value.getHours() !== 0 || value.getMinutes() !== 0 ||
+      value.getSeconds() !== 0 || value.getMilliseconds() !== 0) {
+
+    // What string should we use to separate the date from the time?
+    // There are 3 options:
+    // 'T': This is the standard from ISO8601. i.e. 2013-05-20T11:05
+    //      The good news - it's a standard. The bad news - it's weird and
+    //      alien to many if not most users
+    // ' ': This looks nicest, but needs escaping (which GCLI will do
+    //      automatically) so it would look like: '2013-05-20 11:05'
+    //      Good news: looks best, bad news: on completion we place the cursor
+    //      after the final ', so repeated increment/decrement doesn't work
+    // '\ ': It's possible that we could find a way to use a \ to escape the
+    //      space, so the output would look like: 2013-05-20\ 11:05
+    //      This would involve changes to a number of parts, and is probably
+    //      too complex a solution for this problem for now
+    // In the short term I'm going for ' ', and raising the priority of cursor
+    // positioning on actions like increment/decrement/tab.
+
+    str += ' ' + pad(value.getHours());
+    str += ':' + pad(value.getMinutes());
+
+    // Only add in seconds/milliseconds if there is anything to report
+    if (value.getSeconds() !== 0 || value.getMilliseconds() !== 0) {
+      str += ':' + pad(value.getSeconds());
+      if (value.getMilliseconds() !== 0) {
+        str += '.' + String((value.getUTCMilliseconds()/1000).toFixed(3)).slice(2, 5);
+      }
+    }
+  }
+
+  return str;
+};
+
+DateType.prototype.getMin = function(context) {
+  if (typeof this._min === 'function') {
+    return this._min(context);
+  }
+  if (isDate(this._min)) {
+    return this._min;
+  }
+  return undefined;
+};
+
+DateType.prototype.getMax = function(context) {
+  if (typeof this._max === 'function') {
+    return this._max(context);
+  }
+  if (isDate(this._max)) {
+    return this._max;
+  }
+  return undefined;
+};
+
+DateType.prototype.parse = function(arg, context) {
+  var value;
+
+  if (arg.text.replace(/\s/g, '').length === 0) {
+    return Promise.resolve(new Conversion(undefined, arg, Status.INCOMPLETE, ''));
+  }
+
+  // Lots of room for improvement here: 1h ago, in two days, etc.
+  // Should "1h ago" dynamically update the step?
+  if (arg.text === 'now') {
+    value = new Date();
+  }
+  else if (arg.text === 'yesterday') {
+    value = new Date().setDate(new Date().getDate() - 1);
+  }
+  else if (arg.text === 'tomorrow') {
+    value = new Date().setDate(new Date().getDate() + 1);
+  }
+  else {
+    var millis = Date.parse(arg.text);
+
+    if (isNaN(millis)) {
+      var msg = l10n.lookupFormat('typesDateNan', [ arg.text ]);
+      return Promise.resolve(new Conversion(undefined, arg, Status.ERROR, msg));
+    }
+
+    value = new Date(millis);
+  }
+
+  return Promise.resolve(new Conversion(value, arg));
+};
+
+DateType.prototype.decrement = function(value, context) {
+  if (!isDate(value)) {
+    return new Date();
+  }
+
+  var newValue = new Date(value);
+  newValue.setDate(value.getDate() - this._step);
+
+  if (newValue >= this.getMin(context)) {
+    return newValue;
+  }
+  else {
+    return this.getMin(context);
+  }
+};
+
+DateType.prototype.increment = function(value, context) {
+  if (!isDate(value)) {
+    return new Date();
+  }
+
+  var newValue = new Date(value);
+  newValue.setDate(value.getDate() + this._step);
+
+  if (newValue <= this.getMax(context)) {
+    return newValue;
+  }
+  else {
+    return this.getMax();
+  }
+};
+
+DateType.prototype.name = 'date';
+
+
+/**
+ * Utility to convert a string to a date, throwing if the date can't be
+ * parsed rather than having an invalid date
+ */
+function toDate(str) {
+  var millis = Date.parse(str);
+  if (isNaN(millis)) {
+    throw new Error(l10n.lookupFormat('typesDateNan', [ str ]));
+  }
+  return new Date(millis);
+}
+
+/**
+ * Is |thing| a valid date?
+ * @see http://stackoverflow.com/questions/1353684/detecting-an-invalid-date-date-instance-in-javascript
+ */
+function isDate(thing) {
+  return Object.prototype.toString.call(thing) === '[object Date]'
+          && !isNaN(thing.getTime());
+};
+
+
+/**
+ * Registration and de-registration.
+ */
+exports.startup = function() {
+  types.addType(DateType);
+};
+
+exports.shutdown = function() {
+  types.removeType(DateType);
+};
+
+
+
+});
+/*
  * Copyright 2012, Mozilla Foundation and contributors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  * http://www.apache.org/licenses/LICENSE-2.0
  *
@@ -4624,17 +4894,17 @@ function NodeListType(typeSpec) {
     throw new Error('Legal values for allowEmpty are [true|false]');
   }
 
   this.allowEmpty = typeSpec.allowEmpty;
 }
 
 NodeListType.prototype = Object.create(Type.prototype);
 
-NodeListType.prototype.getBlank = function() {
+NodeListType.prototype.getBlank = function(context) {
   return new Conversion(exports._empty, new BlankArgument(), Status.VALID);
 };
 
 NodeListType.prototype.stringify = function(value, context) {
   if (value == null) {
     return '';
   }
   return value.__gcliQuery || 'Error';
@@ -6385,17 +6655,18 @@ Requisition.prototype.setAssignment = fu
     assignment.onAssignmentChange({
       assignment: assignment,
       conversion: assignment.conversion,
       oldConversion: oldConversion
     });
   }
 
   if (arg == null) {
-    setAssignmentInternal(assignment.param.type.getBlank());
+    var blank = assignment.param.type.getBlank(this.executionContext);
+    setAssignmentInternal(blank);
   }
   else if (typeof arg.getStatus === 'function') {
     setAssignmentInternal(arg);
   }
   else {
     var parsed = assignment.param.type.parse(arg, this.executionContext);
     return parsed.then(function(conversion) {
       setAssignmentInternal(conversion);
@@ -7045,45 +7316,35 @@ exports.tokenize = function(typed) {
   }
 
   if (isSimple(typed)) {
     return [ new Argument(typed, '', '') ];
   }
 
   var mode = In.WHITESPACE;
 
-  // First we un-escape. This list was taken from:
-  // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Core_Language_Features#Unicode
-  // We are generally converting to their real values except for the strings
-  // '\'', '\"', '\ ', '{' and '}' which we are converting to unicode private
-  // characters so we can distinguish them from '"', ' ', '{', '}' and ''',
-  // which are special. They need swapping back post-split - see unescape2()
+  // First we swap out escaped characters that are special to the tokenizer.
+  // So a backslash followed by any of ['"{} ] is turned into a unicode private
+  // char so we can swap back later
   typed = typed
-      .replace(/\\\\/g, '\\')
-      .replace(/\\b/g, '\b')
-      .replace(/\\f/g, '\f')
-      .replace(/\\n/g, '\n')
-      .replace(/\\r/g, '\r')
-      .replace(/\\t/g, '\t')
-      .replace(/\\v/g, '\v')
-      .replace(/\\n/g, '\n')
-      .replace(/\\r/g, '\r')
-      .replace(/\\ /g, '\uF000')
-      .replace(/\\'/g, '\uF001')
-      .replace(/\\"/g, '\uF002')
-      .replace(/\\{/g, '\uF003')
-      .replace(/\\}/g, '\uF004');
+      .replace(/\\\\/g, '\uF000')
+      .replace(/\\ /g, '\uF001')
+      .replace(/\\'/g, '\uF002')
+      .replace(/\\"/g, '\uF003')
+      .replace(/\\{/g, '\uF004')
+      .replace(/\\}/g, '\uF005');
 
   function unescape2(escaped) {
     return escaped
-        .replace(/\uF000/g, ' ')
-        .replace(/\uF001/g, '\'')
-        .replace(/\uF002/g, '"')
-        .replace(/\uF003/g, '{')
-        .replace(/\uF004/g, '}');
+        .replace(/\uF000/g, '\\\\')
+        .replace(/\uF001/g, '\\ ')
+        .replace(/\uF002/g, '\\\'')
+        .replace(/\uF003/g, '\\\"')
+        .replace(/\uF004/g, '\\\{')
+        .replace(/\uF005/g, '\\\}');
   }
 
   var i = 0;          // The index of the current character
   var start = 0;      // Where did this section start?
   var prefix = '';    // Stuff that comes before the current argument
   var args = [];      // The array that we're creating
   var blockDepth = 0; // For JS with nested {}
 
@@ -7278,23 +7539,26 @@ Requisition.prototype._split = function(
     // Previously we needed a way to hide commands depending context.
     // We have not resurrected that feature yet, but if we do we should
     // insert code here to ignore certain commands depending on the
     // context/environment
 
     argsUsed++;
   }
 
+  // This could probably be re-written to consume args as we go
   for (var i = 0; i < argsUsed; i++) {
     args.shift();
   }
 
+  // Warning: we're returning a promise (from setAssignment) which tells us
+  // when we're done setting the current command, but mutating the args array
+  // as we go, so we're conflicted on when we're done
+
   return this.setAssignment(this.commandAssignment, conversion, noArgUp);
-
-  // This could probably be re-written to consume args as we go
 };
 
 /**
  * Add all the passed args to the list of unassigned assignments.
  */
 Requisition.prototype._addUnassignedArgs = function(args) {
   args.forEach(function(arg) {
     this._unassigned.push(new UnassignedAssignment(this, arg));
@@ -8028,17 +8292,17 @@ StringField.prototype.setConversion = fu
   this.arg = conversion.arg;
   this.element.value = conversion.arg.text;
   this.setMessage(conversion.message);
 };
 
 StringField.prototype.getConversion = function() {
   // This tweaks the prefix/suffix of the argument to fit
   this.arg = this.arg.beget({ text: this.element.value, prefixSpace: true });
-  return this.type.parse(this.arg, this.requisition.context);
+  return this.type.parse(this.arg, this.requisition.executionContext);
 };
 
 StringField.claim = function(type, context) {
   return type.name === 'string' ? Field.MATCH : Field.BASIC;
 };
 
 
 /**
@@ -8083,17 +8347,17 @@ NumberField.prototype.destroy = function
 NumberField.prototype.setConversion = function(conversion) {
   this.arg = conversion.arg;
   this.element.value = conversion.arg.text;
   this.setMessage(conversion.message);
 };
 
 NumberField.prototype.getConversion = function() {
   this.arg = this.arg.beget({ text: this.element.value, prefixSpace: true });
-  return this.type.parse(this.arg, this.requisition.context);
+  return this.type.parse(this.arg, this.requisition.executionContext);
 };
 
 
 /**
  * A field that uses a checkbox to toggle a boolean field
  */
 function BooleanField(type, options) {
   Field.call(this, type, options);
@@ -8135,17 +8399,17 @@ BooleanField.prototype.getConversion = f
   if (this.named) {
     arg = this.element.checked ?
             new TrueNamedArgument(new Argument(' --' + this.name)) :
             new FalseNamedArgument();
   }
   else {
     arg = new Argument(' ' + this.element.checked);
   }
-  return this.type.parse(arg, this.requisition.context);
+  return this.type.parse(arg, this.requisition.executionContext);
 };
 
 
 /**
  * A field that works with delegate types by delaying resolution until that
  * last possible time
  */
 function DelegateField(type, options) {
@@ -8513,17 +8777,17 @@ exports.removeField = function(field) {
  * - required: Is this a required parameter (i.e. param.isDataRequired)
  * - named: Is this a named parameters (i.e. !param.isPositionalAllowed)
  * @return A newly constructed field that best matches the input options
  */
 exports.getField = function(type, options) {
   var ctor;
   var highestClaim = -1;
   fieldCtors.forEach(function(fieldCtor) {
-    var claim = fieldCtor.claim(type, options.requisition.context);
+    var claim = fieldCtor.claim(type, options.requisition.executionContext);
     if (claim > highestClaim) {
       highestClaim = claim;
       ctor = fieldCtor;
     }
   });
 
   if (!ctor) {
     console.error('Unknown field type ', type, ' in ', fieldCtors);
@@ -8556,17 +8820,17 @@ BlankField.claim = function(type, contex
   return type.name === 'blank' ? Field.MATCH : Field.NO_MATCH;
 };
 
 BlankField.prototype.setConversion = function(conversion) {
   this.setMessage(conversion.message);
 };
 
 BlankField.prototype.getConversion = function() {
-  return this.type.parse(new Argument(), this.requisition.context);
+  return this.type.parse(new Argument(), this.requisition.executionContext);
 };
 
 exports.addField(BlankField);
 
 
 });
 /*
  * Copyright 2012, Mozilla Foundation and contributors
@@ -8692,17 +8956,17 @@ JavascriptField.prototype.setConversion 
         });
       }
     }, this);
     this.menu.show(items);
   }.bind(this), util.errorHandler);
 };
 
 JavascriptField.prototype.itemClicked = function(ev) {
-  var parsed = this.type.parse(ev.arg, this.requisition.context);
+  var parsed = this.type.parse(ev.arg, this.requisition.executionContext);
   Promise.resolve(parsed).then(function(conversion) {
     this.onFieldChange({ conversion: conversion });
     this.setMessage(conversion.message);
   }.bind(this), util.errorHandler);
 };
 
 JavascriptField.prototype.onInputChange = function(ev) {
   this.item = ev.currentTarget.item;
@@ -8710,17 +8974,17 @@ JavascriptField.prototype.onInputChange 
     this.onFieldChange({ conversion: conversion });
     this.setMessage(conversion.message);
   }.bind(this), util.errorHandler);
 };
 
 JavascriptField.prototype.getConversion = function() {
   // This tweaks the prefix/suffix of the argument to fit
   this.arg = new ScriptArgument(this.input.value, '{ ', ' }');
-  return this.type.parse(this.arg, this.requisition.context);
+  return this.type.parse(this.arg, this.requisition.executionContext);
 };
 
 JavascriptField.DEFAULT_VALUE = '__JavascriptField.DEFAULT_VALUE';
 
 
 });
 /*
  * Copyright 2012, Mozilla Foundation and contributors
@@ -9134,17 +9398,17 @@ SelectionTooltipField.prototype.setConve
       // at least that has a name.
       return prediction.value.description ? prediction.value : prediction;
     }, this);
     this.menu.show(items, conversion.arg.text);
   }.bind(this), util.errorHandler);
 };
 
 SelectionTooltipField.prototype.itemClicked = function(ev) {
-  var parsed = this.type.parse(ev.arg, this.requisition.context);
+  var parsed = this.type.parse(ev.arg, this.requisition.executionContext);
   Promise.resolve(parsed).then(function(conversion) {
     this.onFieldChange({ conversion: conversion });
     this.setMessage(conversion.message);
   }.bind(this), util.errorHandler);
 };
 
 SelectionTooltipField.prototype.onInputChange = function(ev) {
   this.item = ev.currentTarget.item;
@@ -9152,17 +9416,17 @@ SelectionTooltipField.prototype.onInputC
     this.onFieldChange({ conversion: conversion });
     this.setMessage(conversion.message);
   }.bind(this), util.errorHandler);
 };
 
 SelectionTooltipField.prototype.getConversion = function() {
   // This tweaks the prefix/suffix of the argument to fit
   this.arg = this.arg.beget({ text: this.input.value });
-  return this.type.parse(this.arg, this.requisition.context);
+  return this.type.parse(this.arg, this.requisition.executionContext);
 };
 
 /**
  * Allow the menu to highlight the correct prediction choice
  */
 SelectionTooltipField.prototype.setChoiceIndex = function(choice) {
   this.menu.setChoiceIndex(choice);
 };
@@ -9660,20 +9924,33 @@ var commandConverterSpec = {
           var input = '';
           if (param.defaultValue === undefined) {
             input = l10n.lookup('helpManRequired');
           }
           else if (param.defaultValue === null) {
             input = l10n.lookup('helpManOptional');
           }
           else {
-            input = param.defaultValue;
+            var defaultValue = param.type.stringify(param.defaultValue);
+            input = l10n.lookupFormat('helpManDefault', [ defaultValue ]);
           }
           return '(' + param.type.name + ', ' + input + ')';
         },
+        getSynopsis: function(param) {
+          if (param.isPositionalAllowed) {
+            return param.defaultValue !== undefined ?
+                '[' + param.name + ']' :
+                '<' + param.name + '>';
+          }
+          else {
+            return param.type.name === 'boolean' ?
+                '[--' + param.name + ']' :
+                '[--' + param.name + '=...]';
+          }
+        },
         command: commandData.command,
         subcommands: commandData.subcommands
       },
       css: helpCss,
       cssId: 'gcli-help'
     });
   }
 };
@@ -10110,19 +10387,17 @@ exports.addConverter(errorDomConverter);
 define("text!gcli/commands/help_man.html", [], "\n" +
   "<div>\n" +
   "  <h3>${command.name}</h3>\n" +
   "\n" +
   "  <h4 class=\"gcli-help-header\">\n" +
   "    ${l10n.helpManSynopsis}:\n" +
   "    <span class=\"gcli-out-shortcut\" onclick=\"${onclick}\" data-command=\"${command.name}\">\n" +
   "      ${command.name}\n" +
-  "      <span foreach=\"param in ${command.params}\">\n" +
-  "        ${param.defaultValue !== undefined ? '[' + param.name + ']' : param.name}\n" +
-  "      </span>\n" +
+  "      <span foreach=\"param in ${command.params}\">${getSynopsis(param)} </span>\n" +
   "    </span>\n" +
   "  </h4>\n" +
   "\n" +
   "  <h4 class=\"gcli-help-header\">${l10n.helpManDescription}:</h4>\n" +
   "\n" +
   "  <p class=\"gcli-help-description\">${describe(command)}</p>\n" +
   "\n" +
   "  <div if=\"${command.exec}\">\n" +
@@ -10941,16 +11216,17 @@ Inputter.prototype._checkAssignment = fu
  * This function updates the data model. It sets the caret to the end of the
  * input. It does not make any similarity checks so calling this function with
  * it's current value resets the cursor position.
  * It does not execute the input or affect the history.
  * This function should not be called internally, by Inputter and never as a
  * result of a keyboard event on this.element or bug 676520 could be triggered.
  */
 Inputter.prototype.setInput = function(str) {
+  this._caretChange = Caret.TO_END;
   return this.requisition.update(str);
 };
 
 /**
  * Counterpart to |setInput| for moving the cursor.
  * @param cursor An object shaped like { start: x, end: y }
  */
 Inputter.prototype.setCursor = function(cursor) {
@@ -11064,17 +11340,18 @@ Inputter.prototype.handleKeyUp = functio
     }
     else if (this.element.value === '' || this._scrollingThroughHistory) {
       this._scrollingThroughHistory = true;
       return this.requisition.update(this.history.forward());
     }
     else {
       // See notes above for the UP key
       if (this.assignment.getStatus() === Status.VALID) {
-        this.requisition.decrement(this.assignment, this.requisition.context);
+        this.requisition.decrement(this.assignment,
+                                   this.requisition.executionContext);
         // See notes on focusManager.onInputChange in onKeyDown
         if (this.focusManager) {
           this.focusManager.onInputChange();
         }
       }
       else {
         this.changeChoice(+1);
       }
@@ -11476,17 +11753,17 @@ Completer.prototype._getCompleterTemplat
             arrowTabText = '\u21E5 ' + tabText;
           }
         }
       }
       else {
         // There's no prediction, but if this is a named argument that needs a
         // value (that is without any) then we need to show that one is needed
         // For example 'git commit --message ', clearly needs some more text
-        if (cArg.type === 'NamedArgument' && cArg.text === '') {
+        if (cArg.type === 'NamedArgument' && cArg.valueArg == null) {
           emptyParameters.push('<' + current.param.type.name + '>\u00a0');
         }
       }
     }
 
     // Add a space between the typed text (+ directTabText) and the hints,
     // making sure we don't add 2 sets of padding
     if (directTabText !== '') {