Bug 834230 - break del command suffers from an off by 1 error; r=vporof
authorJoe Walker <jwalker@mozilla.com>
Tue, 21 May 2013 10:18:55 +0100
changeset 143911 873bf55af0cdab648bc85945d02e3bbc23304f26
parent 143910 97892f7b402a05d57a47afc48dcb3c06d0abc346
child 143912 231636fc32319c1f4edc9c9d247d3af9212e38e8
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)
reviewersvporof
bugs834230
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
Bug 834230 - break del command suffers from an off by 1 error; r=vporof
browser/devtools/commandline/test/browser_gcli_types.js
browser/devtools/debugger/CmdDebugger.jsm
browser/devtools/debugger/test/Makefile.in
browser/devtools/debugger/test/browser_dbg_cmd_break.js
browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
toolkit/devtools/gcli/gcli.jsm
--- 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/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/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
  *
@@ -325,32 +326,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;
 };
 
@@ -372,66 +373,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
@@ -471,17 +472,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;
 
@@ -619,17 +620,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;
 
 
@@ -2010,17 +2011,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
@@ -2820,17 +2821,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;
@@ -2851,31 +2852,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;
 };
@@ -2883,34 +2884,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
@@ -2926,18 +2927,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) {
@@ -3018,17 +3019,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;
@@ -3036,19 +3037,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);
@@ -3057,33 +3058,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--;
@@ -3660,25 +3661,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
@@ -4670,17 +4662,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';
@@ -6431,17 +6423,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);
@@ -8067,17 +8060,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;
 };
 
 
 /**
@@ -8122,17 +8115,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);
@@ -8174,17 +8167,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) {
@@ -8552,17 +8545,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);
@@ -8595,17 +8588,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
@@ -8731,17 +8724,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;
@@ -8749,17 +8742,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
@@ -9173,17 +9166,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;
@@ -9191,17 +9184,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);
 };
@@ -11103,17 +11096,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);
       }