Bug 984365 - Refactor and split out BuiltinCommands.jsm; r=mratcliffe,robcee,panos
authorJoe Walker <jwalker@mozilla.com>
Sun, 13 Apr 2014 07:47:27 +0100
changeset 196653 7b43671588648971a7c7db2dae6f336f25fed0f8
parent 196652 15503d9a400a3ca680e6dd8ecf98a3a82435dd9c
child 196654 c17fcb94177b3ed68fa7b66dabc7ede015ad8f15
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmratcliffe, robcee, panos
bugs984365
milestone31.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 984365 - Refactor and split out BuiltinCommands.jsm; r=mratcliffe,robcee,panos BuiltinCommands.jsm was huge to avoid slowing things down by having many modules loading. To avoid splitting it up from slowing things down we want to delay loading commands. Create [add|remove]ItemsByModule to allow us to lazily add modules, and convert all command modules to use this. Then break up BuiltinCommands into a set of files, for each command, and do some refactoring to use JS files rather than JSMs and use "use strict".
browser/devtools/commandline/BuiltinCommands.jsm
browser/devtools/commandline/Commands.jsm
browser/devtools/commandline/Makefile.in
browser/devtools/commandline/commands-index.js
browser/devtools/commandline/gcli.jsm
browser/devtools/commandline/moz.build
browser/devtools/commandline/test/browser_cmd_addon.js
browser/devtools/commandline/test/browser_cmd_pagemod_export.js
browser/devtools/debugger/CmdDebugger.jsm
browser/devtools/debugger/Makefile.in
browser/devtools/debugger/debugger-commands.js
browser/devtools/debugger/moz.build
browser/devtools/framework/toolbox.js
browser/devtools/inspector/CmdInspect.jsm
browser/devtools/inspector/Makefile.in
browser/devtools/inspector/inspector-commands.js
browser/devtools/inspector/moz.build
browser/devtools/jar.mn
browser/devtools/main.js
browser/devtools/profiler/commands.js
browser/devtools/responsivedesign/CmdResize.jsm
browser/devtools/responsivedesign/moz.build
browser/devtools/responsivedesign/resize-commands.js
browser/devtools/scratchpad/CmdScratchpad.jsm
browser/devtools/scratchpad/moz.build
browser/devtools/scratchpad/scratchpad-commands.js
browser/devtools/shared/DeveloperToolbar.jsm
browser/devtools/styleeditor/CmdEdit.jsm
browser/devtools/styleeditor/styleeditor-commands.js
browser/devtools/tilt/CmdTilt.jsm
browser/devtools/tilt/Makefile.in
browser/devtools/tilt/moz.build
browser/devtools/tilt/tilt-commands.js
browser/devtools/webconsole/console-commands.js
browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
toolkit/devtools/gcli/Makefile.in
toolkit/devtools/gcli/commands/addon.js
toolkit/devtools/gcli/commands/appcache.js
toolkit/devtools/gcli/commands/calllog.js
toolkit/devtools/gcli/commands/cmd.js
toolkit/devtools/gcli/commands/cookie.js
toolkit/devtools/gcli/commands/jsb.js
toolkit/devtools/gcli/commands/listen.js
toolkit/devtools/gcli/commands/media.js
toolkit/devtools/gcli/commands/pagemod.js
toolkit/devtools/gcli/commands/paintflashing.js
toolkit/devtools/gcli/commands/restart.js
toolkit/devtools/gcli/commands/screenshot.js
toolkit/devtools/gcli/commands/tools.js
toolkit/devtools/gcli/source/lib/gcli/api.js
toolkit/devtools/gcli/source/lib/gcli/util/promise.js
deleted file mode 100644
--- a/browser/devtools/commandline/BuiltinCommands.jsm
+++ /dev/null
@@ -1,2335 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
-
-const BRAND_SHORT_NAME = Cc["@mozilla.org/intl/stringbundle;1"]
-                           .getService(Ci.nsIStringBundleService)
-                           .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");
-let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js").Promise;
-Cu.import("resource://gre/modules/osfile.jsm");
-
-Cu.import("resource://gre/modules/devtools/event-emitter.js");
-
-let devtools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
-let gcli = devtools.require("gcli/index");
-let Telemetry = devtools.require("devtools/shared/telemetry");
-let telemetry = new Telemetry();
-
-XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
-                                  "resource:///modules/devtools/gDevTools.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "AppCacheUtils",
-                                  "resource:///modules/devtools/AppCacheUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
-                                  "resource://gre/modules/Downloads.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Task",
-                                  "resource://gre/modules/Task.jsm");
-
-/* CmdAddon ---------------------------------------------------------------- */
-
-(function(module) {
-  XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
-                                    "resource://gre/modules/AddonManager.jsm");
-
-  // We need to use an object in which to store any flags because a primitive
-  // would remain undefined.
-  module.CmdAddonFlags = {
-    addonsLoaded: false
-  };
-
-  /**
-   * 'addon' command.
-   */
-  gcli.addCommand({
-    name: "addon",
-    description: gcli.lookup("addonDesc")
-  });
-
-  /**
-   * 'addon list' command.
-   */
-  gcli.addCommand({
-    name: "addon list",
-    description: gcli.lookup("addonListDesc"),
-    returnType: "addonsInfo",
-    params: [{
-      name: 'type',
-      type: {
-        name: 'selection',
-        data: ["dictionary", "extension", "locale", "plugin", "theme", "all"]
-      },
-      defaultValue: 'all',
-      description: gcli.lookup("addonListTypeDesc")
-    }],
-    exec: function(aArgs, context) {
-      let deferred = context.defer();
-      function pendingOperations(aAddon) {
-        let allOperations = ["PENDING_ENABLE",
-                             "PENDING_DISABLE",
-                             "PENDING_UNINSTALL",
-                             "PENDING_INSTALL",
-                             "PENDING_UPGRADE"];
-        return allOperations.reduce(function(operations, opName) {
-          return aAddon.pendingOperations & AddonManager[opName] ?
-            operations.concat(opName) :
-            operations;
-        }, []);
-      }
-      let types = aArgs.type === "all" ? null : [aArgs.type];
-      AddonManager.getAddonsByTypes(types, function(addons) {
-        deferred.resolve({
-          addons: addons.map(function(addon) {
-            return {
-              name: addon.name,
-              version: addon.version,
-              isActive: addon.isActive,
-              pendingOperations: pendingOperations(addon)
-            };
-          }),
-          type: aArgs.type
-        });
-      });
-      return deferred.promise;
-    }
-  });
-
-  gcli.addConverter({
-    from: "addonsInfo",
-    to: "view",
-    exec: function(addonsInfo, context) {
-      if (!addonsInfo.addons.length) {
-        return context.createView({
-          html: "<p>${message}</p>",
-          data: { message: gcli.lookup("addonNoneOfType") }
-        });
-      }
-
-      let headerLookups = {
-        "dictionary": "addonListDictionaryHeading",
-        "extension": "addonListExtensionHeading",
-        "locale": "addonListLocaleHeading",
-        "plugin": "addonListPluginHeading",
-        "theme": "addonListThemeHeading",
-        "all": "addonListAllHeading"
-      };
-      let header = gcli.lookup(headerLookups[addonsInfo.type] ||
-                               "addonListUnknownHeading");
-
-      let operationLookups = {
-        "PENDING_ENABLE": "addonPendingEnable",
-        "PENDING_DISABLE": "addonPendingDisable",
-        "PENDING_UNINSTALL": "addonPendingUninstall",
-        "PENDING_INSTALL": "addonPendingInstall",
-        "PENDING_UPGRADE": "addonPendingUpgrade"
-      };
-      function lookupOperation(opName) {
-        let lookupName = operationLookups[opName];
-        return lookupName ? gcli.lookup(lookupName) : opName;
-      }
-
-      function arrangeAddons(addons) {
-        let enabledAddons = [];
-        let disabledAddons = [];
-        addons.forEach(function(aAddon) {
-          if (aAddon.isActive) {
-            enabledAddons.push(aAddon);
-          } else {
-            disabledAddons.push(aAddon);
-          }
-        });
-
-        function compareAddonNames(aNameA, aNameB) {
-          return String.localeCompare(aNameA.name, aNameB.name);
-        }
-        enabledAddons.sort(compareAddonNames);
-        disabledAddons.sort(compareAddonNames);
-
-        return enabledAddons.concat(disabledAddons);
-      }
-
-      function isActiveForToggle(addon) {
-        return (addon.isActive && ~~addon.pendingOperations.indexOf("PENDING_DISABLE"));
-      }
-
-      return context.createView({
-        html: addonsListHtml,
-        data: {
-          header: header,
-          addons: arrangeAddons(addonsInfo.addons).map(function(addon) {
-            return {
-              name: addon.name,
-              label: addon.name.replace(/\s/g, "_") +
-                    (addon.version ? "_" + addon.version : ""),
-              status: addon.isActive ? "enabled" : "disabled",
-              version: addon.version,
-              pendingOperations: addon.pendingOperations.length ?
-                (" (" + gcli.lookup("addonPending") + ": "
-                 + addon.pendingOperations.map(lookupOperation).join(", ")
-                 + ")") :
-                "",
-              toggleActionName: isActiveForToggle(addon) ? "disable": "enable",
-              toggleActionMessage: isActiveForToggle(addon) ?
-                gcli.lookup("addonListOutDisable") :
-                gcli.lookup("addonListOutEnable")
-            };
-          }),
-          onclick: context.update,
-          ondblclick: context.updateExec
-        }
-      });
-    }
-  });
-
-  var addonsListHtml = "" +
-        "<table>" +
-        " <caption>${header}</caption>" +
-        " <tbody>" +
-        "  <tr foreach='addon in ${addons}'" +
-        "      class=\"gcli-addon-${addon.status}\">" +
-        "    <td>${addon.name} ${addon.version}</td>" +
-        "    <td>${addon.pendingOperations}</td>" +
-        "    <td>" +
-        "      <span class='gcli-out-shortcut'" +
-        "            data-command='addon ${addon.toggleActionName} ${addon.label}'" +
-        "       onclick='${onclick}'" +
-        "       ondblclick='${ondblclick}'" +
-        "      >${addon.toggleActionMessage}</span>" +
-        "    </td>" +
-        "  </tr>" +
-        " </tbody>" +
-        "</table>" +
-        "";
-
-  // We need a list of addon names for the enable and disable commands. Because
-  // getting the name list is async we do not add the commands until we have the
-  // list.
-  AddonManager.getAllAddons(function addonAsync(aAddons) {
-    // We listen for installs to keep our addon list up to date. There is no need
-    // to listen for uninstalls because uninstalled addons are simply disabled
-    // until restart (to enable undo functionality).
-    AddonManager.addAddonListener({
-      onInstalled: function(aAddon) {
-        addonNameCache.push({
-          name: representAddon(aAddon).replace(/\s/g, "_"),
-          value: aAddon.name
-        });
-      },
-      onUninstalled: function(aAddon) {
-        let name = representAddon(aAddon).replace(/\s/g, "_");
-
-        for (let i = 0; i < addonNameCache.length; i++) {
-          if(addonNameCache[i].name == name) {
-            addonNameCache.splice(i, 1);
-            break;
-          }
-        }
-      },
-    });
-
-    /**
-    * Returns a string that represents the passed add-on.
-    */
-    function representAddon(aAddon) {
-      let name = aAddon.name + " " + aAddon.version;
-      return name.trim();
-    }
-
-    let addonNameCache = [];
-
-    // The name parameter, used in "addon enable" and "addon disable."
-    let nameParameter = {
-      name: "name",
-      type: {
-        name: "selection",
-        lookup: addonNameCache
-      },
-      description: gcli.lookup("addonNameDesc")
-    };
-
-    for (let addon of aAddons) {
-      addonNameCache.push({
-        name: representAddon(addon).replace(/\s/g, "_"),
-        value: addon.name
-      });
-    }
-
-    /**
-    * 'addon enable' command.
-    */
-    gcli.addCommand({
-      name: "addon enable",
-      description: gcli.lookup("addonEnableDesc"),
-      params: [nameParameter],
-      exec: function(aArgs, context) {
-        /**
-         * Enables the addon in the passed list which has a name that matches
-         * according to the passed name comparer, and resolves the promise which
-         * is the scope (this) of this function to display the result of this
-         * enable attempt.
-         */
-        function enable(aName, addons) {
-          // Find the add-on.
-          let addon = null;
-          addons.some(function(candidate) {
-            if (candidate.name == aName) {
-              addon = candidate;
-              return true;
-            } else {
-              return false;
-            }
-          });
-
-          let name = representAddon(addon);
-          let message = "";
-
-          if (!addon.userDisabled) {
-            message = gcli.lookupFormat("addonAlreadyEnabled", [name]);
-          } else {
-            addon.userDisabled = false;
-            message = gcli.lookupFormat("addonEnabled", [name]);
-          }
-          this.resolve(message);
-        }
-
-        let deferred = context.defer();
-        // List the installed add-ons, enable one when done listing.
-        AddonManager.getAllAddons(enable.bind(deferred, aArgs.name));
-        return deferred.promise;
-      }
-    });
-
-    /**
-     * 'addon disable' command.
-     */
-    gcli.addCommand({
-      name: "addon disable",
-      description: gcli.lookup("addonDisableDesc"),
-      params: [nameParameter],
-      exec: function(aArgs, context) {
-        /**
-        * Like enable, but ... you know ... the exact opposite.
-        */
-        function disable(aName, addons) {
-          // Find the add-on.
-          let addon = null;
-          addons.some(function(candidate) {
-            if (candidate.name == aName) {
-              addon = candidate;
-              return true;
-            } else {
-              return false;
-            }
-          });
-
-          let name = representAddon(addon);
-          let message = "";
-
-          // If the addon is not disabled or is set to "click to play" then
-          // disable it. Otherwise display the message "Add-on is already
-          // disabled."
-          if (!addon.userDisabled ||
-              addon.userDisabled === AddonManager.STATE_ASK_TO_ACTIVATE) {
-            addon.userDisabled = true;
-            message = gcli.lookupFormat("addonDisabled", [name]);
-          } else {
-            message = gcli.lookupFormat("addonAlreadyDisabled", [name]);
-          }
-          this.resolve(message);
-        }
-
-        let deferred = context.defer();
-        // List the installed add-ons, disable one when done listing.
-        AddonManager.getAllAddons(disable.bind(deferred, aArgs.name));
-        return deferred.promise;
-      }
-    });
-    module.CmdAddonFlags.addonsLoaded = true;
-    Services.obs.notifyObservers(null, "gcli_addon_commands_ready", null);
-  });
-
-}(this));
-
-/* CmdCalllog -------------------------------------------------------------- */
-
-(function(module) {
-  XPCOMUtils.defineLazyGetter(this, "Debugger", function() {
-    let JsDebugger = {};
-    Components.utils.import("resource://gre/modules/jsdebugger.jsm", JsDebugger);
-
-    let global = Components.utils.getGlobalForObject({});
-    JsDebugger.addDebuggerToGlobal(global);
-
-    return global.Debugger;
-  });
-
-  let debuggers = [];
-
-  /**
-   * 'calllog' command
-   */
-  gcli.addCommand({
-    name: "calllog",
-    description: gcli.lookup("calllogDesc")
-  })
-
-  /**
-   * 'calllog start' command
-   */
-  gcli.addCommand({
-    name: "calllog start",
-    description: gcli.lookup("calllogStartDesc"),
-
-    exec: function(args, context) {
-      let contentWindow = context.environment.window;
-
-      let dbg = new Debugger(contentWindow);
-      dbg.onEnterFrame = function(frame) {
-        // BUG 773652 -  Make the output from the GCLI calllog command nicer
-        contentWindow.console.log("Method call: " + this.callDescription(frame));
-      }.bind(this);
-
-      debuggers.push(dbg);
-
-      let gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
-      let target = devtools.TargetFactory.forTab(gBrowser.selectedTab);
-      gDevTools.showToolbox(target, "webconsole");
-
-      return gcli.lookup("calllogStartReply");
-    },
-
-    callDescription: function(frame) {
-      let name = "<anonymous>";
-      if (frame.callee.name) {
-        name = frame.callee.name;
-      }
-      else {
-        let desc = frame.callee.getOwnPropertyDescriptor("displayName");
-        if (desc && desc.value && typeof desc.value == "string") {
-          name = desc.value;
-        }
-      }
-
-      let args = frame.arguments.map(this.valueToString).join(", ");
-      return name + "(" + args + ")";
-    },
-
-    valueToString: function(value) {
-      if (typeof value !== "object" || value === null) {
-        return uneval(value);
-      }
-      return "[object " + value.class + "]";
-    }
-  });
-
-  /**
-   * 'calllog stop' command
-   */
-  gcli.addCommand({
-    name: "calllog stop",
-    description: gcli.lookup("calllogStopDesc"),
-
-    exec: function(args, context) {
-      let numDebuggers = debuggers.length;
-      if (numDebuggers == 0) {
-        return gcli.lookup("calllogStopNoLogging");
-      }
-
-      for (let dbg of debuggers) {
-        dbg.onEnterFrame = undefined;
-      }
-      debuggers = [];
-
-      return gcli.lookupFormat("calllogStopReply", [ numDebuggers ]);
-    }
-  });
-}(this));
-
-/* CmdCalllogChrome -------------------------------------------------------- */
-
-(function(module) {
-  XPCOMUtils.defineLazyGetter(this, "Debugger", function() {
-    let JsDebugger = {};
-    Cu.import("resource://gre/modules/jsdebugger.jsm", JsDebugger);
-
-    let global = Components.utils.getGlobalForObject({});
-    JsDebugger.addDebuggerToGlobal(global);
-
-    return global.Debugger;
-  });
-
-  let debuggers = [];
-  let sandboxes = [];
-
-  /**
-   * 'calllog chromestart' command
-   */
-  gcli.addCommand({
-    name: "calllog chromestart",
-    description: gcli.lookup("calllogChromeStartDesc"),
-    get hidden() gcli.hiddenByChromePref(),
-    params: [
-      {
-        name: "sourceType",
-        type: {
-          name: "selection",
-          data: ["content-variable", "chrome-variable", "jsm", "javascript"]
-        }
-      },
-      {
-        name: "source",
-        type: "string",
-        description: gcli.lookup("calllogChromeSourceTypeDesc"),
-        manual: gcli.lookup("calllogChromeSourceTypeManual"),
-      }
-    ],
-    exec: function(args, context) {
-      let globalObj;
-      let contentWindow = context.environment.window;
-
-      if (args.sourceType == "jsm") {
-        try {
-          globalObj = Cu.import(args.source);
-        }
-        catch (e) {
-          return gcli.lookup("callLogChromeInvalidJSM");
-        }
-      } else if (args.sourceType == "content-variable") {
-        if (args.source in contentWindow) {
-          globalObj = Cu.getGlobalForObject(contentWindow[args.source]);
-        } else {
-          throw new Error(gcli.lookup("callLogChromeVarNotFoundContent"));
-        }
-      } else if (args.sourceType == "chrome-variable") {
-        let chromeWin = context.environment.chromeDocument.defaultView;
-        if (args.source in chromeWin) {
-          globalObj = Cu.getGlobalForObject(chromeWin[args.source]);
-        } else {
-          return gcli.lookup("callLogChromeVarNotFoundChrome");
-        }
-      } else {
-        let chromeWin = context.environment.chromeDocument.defaultView;
-        let sandbox = new Cu.Sandbox(chromeWin,
-                                    {
-                                      sandboxPrototype: chromeWin,
-                                      wantXrays: false,
-                                      sandboxName: "gcli-cmd-calllog-chrome"
-                                    });
-        let returnVal;
-        try {
-          returnVal = Cu.evalInSandbox(args.source, sandbox, "ECMAv5");
-          sandboxes.push(sandbox);
-        } catch(e) {
-          // We need to save the message before cleaning up else e contains a dead
-          // object.
-          let msg = gcli.lookup("callLogChromeEvalException") + ": " + e;
-          Cu.nukeSandbox(sandbox);
-          return msg;
-        }
-
-        if (typeof returnVal == "undefined") {
-          return gcli.lookup("callLogChromeEvalNeedsObject");
-        }
-
-        globalObj = Cu.getGlobalForObject(returnVal);
-      }
-
-      let dbg = new Debugger(globalObj);
-      debuggers.push(dbg);
-
-      dbg.onEnterFrame = function(frame) {
-        // BUG 773652 -  Make the output from the GCLI calllog command nicer
-        contentWindow.console.log(gcli.lookup("callLogChromeMethodCall") +
-                                  ": " + this.callDescription(frame));
-      }.bind(this);
-
-      let gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
-      let target = devtools.TargetFactory.forTab(gBrowser.selectedTab);
-      gDevTools.showToolbox(target, "webconsole");
-
-      return gcli.lookup("calllogChromeStartReply");
-    },
-
-    valueToString: function(value) {
-      if (typeof value !== "object" || value === null)
-        return uneval(value);
-      return "[object " + value.class + "]";
-    },
-
-    callDescription: function(frame) {
-      let name = frame.callee.name || gcli.lookup("callLogChromeAnonFunction");
-      let args = frame.arguments.map(this.valueToString).join(", ");
-      return name + "(" + args + ")";
-    }
-  });
-
-  /**
-   * 'calllog chromestop' command
-   */
-  gcli.addCommand({
-    name: "calllog chromestop",
-    description: gcli.lookup("calllogChromeStopDesc"),
-    get hidden() gcli.hiddenByChromePref(),
-    exec: function(args, context) {
-      let numDebuggers = debuggers.length;
-      if (numDebuggers == 0) {
-        return gcli.lookup("calllogChromeStopNoLogging");
-      }
-
-      for (let dbg of debuggers) {
-        dbg.onEnterFrame = undefined;
-        dbg.enabled = false;
-      }
-      for (let sandbox of sandboxes) {
-        Cu.nukeSandbox(sandbox);
-      }
-      debuggers = [];
-      sandboxes = [];
-
-      return gcli.lookupFormat("calllogChromeStopReply", [ numDebuggers ]);
-    }
-  });
-}(this));
-
-/* CmdCmd ------------------------------------------------------------------ */
-
-(function(module) {
-  let prefSvc = "@mozilla.org/preferences-service;1";
-  XPCOMUtils.defineLazyGetter(this, "prefBranch", function() {
-    let prefService = Cc[prefSvc].getService(Ci.nsIPrefService);
-    return prefService.getBranch(null).QueryInterface(Ci.nsIPrefBranch2);
-  });
-
-  XPCOMUtils.defineLazyGetter(this, 'supportsString', function() {
-    return Cc["@mozilla.org/supports-string;1"]
-             .createInstance(Ci.nsISupportsString);
-  });
-
-  XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
-                                    "resource://gre/modules/NetUtil.jsm");
-  XPCOMUtils.defineLazyModuleGetter(this, "console",
-                                    "resource://gre/modules/devtools/Console.jsm");
-
-  const PREF_DIR = "devtools.commands.dir";
-
-  /**
-   * A place to store the names of the commands that we have added as a result of
-   * calling refreshAutoCommands(). Used by refreshAutoCommands to remove the
-   * added commands.
-   */
-  let commands = [];
-
-  /**
-   * Exported API
-   */
-  this.CmdCommands = {
-    /**
-     * Called to look in a directory pointed at by the devtools.commands.dir pref
-     * for *.mozcmd files which are then loaded.
-     * @param nsIPrincipal aSandboxPrincipal Scope object for the Sandbox in which
-     * we eval the script from the .mozcmd file. This should be a chrome window.
-     */
-    refreshAutoCommands: function GC_refreshAutoCommands(aSandboxPrincipal) {
-      // First get rid of the last set of commands
-      commands.forEach(function(name) {
-        gcli.removeCommand(name);
-      });
-
-      let dirName = prefBranch.getComplexValue(PREF_DIR,
-                                              Ci.nsISupportsString).data.trim();
-      if (dirName == "") {
-        return;
-      }
-
-      // replaces ~ with the home directory path in unix and windows
-      if (dirName.indexOf("~") == 0) {
-        let dirService = Cc["@mozilla.org/file/directory_service;1"]
-                          .getService(Ci.nsIProperties);
-        let homeDirFile = dirService.get("Home", Ci.nsIFile);
-        let homeDir = homeDirFile.path;
-        dirName = dirName.substr(1);
-        dirName = homeDir + dirName;
-      }
-
-      let statPromise = OS.File.stat(dirName);
-      statPromise = statPromise.then(
-        function onSuccess(stat) {
-          if (!stat.isDir) {
-            throw new Error('\'' + dirName + '\' is not a directory.');
-          } else {
-            return dirName;
-          }
-        },
-        function onFailure(reason) {
-          if (reason instanceof OS.File.Error && reason.becauseNoSuchFile) {
-            throw new Error('\'' + dirName + '\' does not exist.');
-          } else {
-            throw reason;
-          }
-        }
-      );
-
-      statPromise.then(
-        function onSuccess() {
-          let iterator = new OS.File.DirectoryIterator(dirName);
-          let iterPromise = iterator.forEach(
-            function onEntry(entry) {
-              if (entry.name.match(/.*\.mozcmd$/) && !entry.isDir) {
-                loadCommandFile(entry, aSandboxPrincipal);
-              }
-            }
-          );
-
-          iterPromise.then(
-            function onSuccess() {
-              iterator.close();
-            },
-            function onFailure(reason) {
-              iterator.close();
-              throw reason;
-            }
-          );
-        }
-      );
-    }
-  };
-
-  /**
-   * Load the commands from a single file
-   * @param OS.File.DirectoryIterator.Entry aFileEntry The DirectoryIterator
-   * Entry of the file containing the commands that we should read
-   * @param nsIPrincipal aSandboxPrincipal Scope object for the Sandbox in which
-   * we eval the script from the .mozcmd file. This should be a chrome window.
-   */
-  function loadCommandFile(aFileEntry, aSandboxPrincipal) {
-    let readPromise = OS.File.read(aFileEntry.path);
-    readPromise = readPromise.then(
-      function onSuccess(array) {
-        let decoder = new TextDecoder();
-        let source = decoder.decode(array);
-
-        let sandbox = new Cu.Sandbox(aSandboxPrincipal, {
-          sandboxPrototype: aSandboxPrincipal,
-          wantXrays: false,
-          sandboxName: aFileEntry.path
-        });
-        let data = Cu.evalInSandbox(source, sandbox, "1.8", aFileEntry.name, 1);
-
-        if (!Array.isArray(data)) {
-          console.error("Command file '" + aFileEntry.name + "' does not have top level array.");
-          return;
-        }
-
-        data.forEach(function(commandSpec) {
-          gcli.addCommand(commandSpec);
-          commands.push(commandSpec.name);
-        });
-      },
-      function onError(reason) {
-        console.error("OS.File.read(" + aFileEntry.path + ") failed.");
-        throw reason;
-      }
-    );
-  }
-
-  /**
-   * 'cmd' command
-   */
-  gcli.addCommand({
-    name: "cmd",
-    get hidden() {
-      return !prefBranch.prefHasUserValue(PREF_DIR);
-    },
-    description: gcli.lookup("cmdDesc")
-  });
-
-  /**
-   * 'cmd refresh' command
-   */
-  gcli.addCommand({
-    name: "cmd refresh",
-    description: gcli.lookup("cmdRefreshDesc"),
-    get hidden() {
-      return !prefBranch.prefHasUserValue(PREF_DIR);
-    },
-    exec: function(args, context) {
-      let chromeWindow = context.environment.chromeDocument.defaultView;
-      CmdCommands.refreshAutoCommands(chromeWindow);
-
-      let dirName = prefBranch.getComplexValue(PREF_DIR,
-                                              Ci.nsISupportsString).data.trim();
-      return gcli.lookupFormat("cmdStatus", [ commands.length, dirName ]);
-    }
-  });
-
-  /**
-   * 'cmd setdir' command
-   */
-  gcli.addCommand({
-    name: "cmd setdir",
-    description: gcli.lookup("cmdSetdirDesc"),
-    params: [
-      {
-        name: "directory",
-        description: gcli.lookup("cmdSetdirDirectoryDesc"),
-        type: {
-          name: "file",
-          filetype: "directory",
-          existing: "yes"
-        },
-        defaultValue: null
-      }
-    ],
-    returnType: "string",
-    get hidden() {
-      return true; // !prefBranch.prefHasUserValue(PREF_DIR);
-    },
-    exec: function(args, context) {
-      supportsString.data = args.directory;
-      prefBranch.setComplexValue(PREF_DIR, Ci.nsISupportsString, supportsString);
-
-      let chromeWindow = context.environment.chromeDocument.defaultView;
-      CmdCommands.refreshAutoCommands(chromeWindow);
-
-      return gcli.lookupFormat("cmdStatus", [ commands.length, args.directory ]);
-    }
-  });
-}(this));
-
-/* CmdConsole -------------------------------------------------------------- */
-
-(function(module) {
-  Object.defineProperty(this, "HUDService", {
-    get: function() {
-      return devtools.require("devtools/webconsole/hudservice");
-    },
-    configurable: true,
-    enumerable: true
-  });
-
-  /**
-   * 'console' command
-   */
-  gcli.addCommand({
-    name: "console",
-    description: gcli.lookup("consoleDesc"),
-    manual: gcli.lookup("consoleManual")
-  });
-
-  /**
-   * 'console clear' command
-   */
-  gcli.addCommand({
-    name: "console clear",
-    description: gcli.lookup("consoleclearDesc"),
-    exec: function Command_consoleClear(args, context) {
-      let hud = HUDService.getHudByWindow(context.environment.window);
-      // hud will be null if the web console has not been opened for this window
-      if (hud) {
-        hud.jsterm.clearOutput();
-      }
-    }
-  });
-
-  /**
-   * 'console close' command
-   */
-  gcli.addCommand({
-    name: "console close",
-    description: gcli.lookup("consolecloseDesc"),
-    exec: function(args, context) {
-      return gDevTools.closeToolbox(context.environment.target);
-    }
-  });
-
-  /**
-   * 'console open' command
-   */
-  gcli.addCommand({
-    name: "console open",
-    description: gcli.lookup("consoleopenDesc"),
-    exec: function(args, context) {
-      return gDevTools.showToolbox(context.environment.target, "webconsole");
-    }
-  });
-}(this));
-
-/* CmdCookie --------------------------------------------------------------- */
-
-(function(module) {
-  XPCOMUtils.defineLazyModuleGetter(this, "console",
-                                    "resource://gre/modules/devtools/Console.jsm");
-
-  const cookieMgr = Cc["@mozilla.org/cookiemanager;1"]
-                      .getService(Ci.nsICookieManager2);
-
-  /**
-   * The template for the 'cookie list' command.
-   */
-  let cookieListHtml = "" +
-    "<ul class='gcli-cookielist-list'>" +
-    "  <li foreach='cookie in ${cookies}'>" +
-    "    <div>${cookie.name}=${cookie.value}</div>" +
-    "    <table class='gcli-cookielist-detail'>" +
-    "      <tr>" +
-    "        <td>" + gcli.lookup("cookieListOutHost") + "</td>" +
-    "        <td>${cookie.host}</td>" +
-    "      </tr>" +
-    "      <tr>" +
-    "        <td>" + gcli.lookup("cookieListOutPath") + "</td>" +
-    "        <td>${cookie.path}</td>" +
-    "      </tr>" +
-    "      <tr>" +
-    "        <td>" + gcli.lookup("cookieListOutExpires") + "</td>" +
-    "        <td>${cookie.expires}</td>" +
-    "      </tr>" +
-    "      <tr>" +
-    "        <td>" + gcli.lookup("cookieListOutAttributes") + "</td>" +
-    "        <td>${cookie.attrs}</td>" +
-    "      </tr>" +
-    "      <tr><td colspan='2'>" +
-    "        <span class='gcli-out-shortcut' onclick='${onclick}'" +
-    "            data-command='cookie set ${cookie.name} '" +
-    "            >" + gcli.lookup("cookieListOutEdit") + "</span>" +
-    "        <span class='gcli-out-shortcut'" +
-    "            onclick='${onclick}' ondblclick='${ondblclick}'" +
-    "            data-command='cookie remove ${cookie.name}'" +
-    "            >" + gcli.lookup("cookieListOutRemove") + "</span>" +
-    "      </td></tr>" +
-    "    </table>" +
-    "  </li>" +
-    "</ul>" +
-    "";
-
-  gcli.addConverter({
-    from: "cookies",
-    to: "view",
-    exec: function(cookies, context) {
-      if (cookies.length == 0) {
-        let host = context.environment.document.location.host;
-        let msg = gcli.lookupFormat("cookieListOutNoneHost", [ host ]);
-        return context.createView({ html: "<span>" + msg + "</span>" });
-      }
-
-      for (let cookie of cookies) {
-        cookie.expires = translateExpires(cookie.expires);
-
-        let noAttrs = !cookie.secure && !cookie.httpOnly && !cookie.sameDomain;
-        cookie.attrs = (cookie.secure ? 'secure' : ' ') +
-                       (cookie.httpOnly ? 'httpOnly' : ' ') +
-                       (cookie.sameDomain ? 'sameDomain' : ' ') +
-                       (noAttrs ? gcli.lookup("cookieListOutNone") : ' ');
-      }
-
-      return context.createView({
-        html: cookieListHtml,
-        data: {
-          options: { allowEval: true },
-          cookies: cookies,
-          onclick: context.update,
-          ondblclick: context.updateExec
-        }
-      });
-    }
-  });
-
-  /**
-   * The cookie 'expires' value needs converting into something more readable
-   */
-  function translateExpires(expires) {
-    if (expires == 0) {
-      return gcli.lookup("cookieListOutSession");
-    }
-    return new Date(expires).toLocaleString();
-  }
-
-  /**
-   * Check if a given cookie matches a given host
-   */
-  function isCookieAtHost(cookie, host) {
-    if (cookie.host == null) {
-      return host == null;
-    }
-    if (cookie.host.startsWith(".")) {
-      return host.endsWith(cookie.host);
-    }
-    else {
-      return cookie.host == host;
-    }
-  }
-
-  /**
-   * 'cookie' command
-   */
-  gcli.addCommand({
-    name: "cookie",
-    description: gcli.lookup("cookieDesc"),
-    manual: gcli.lookup("cookieManual")
-  });
-
-  /**
-   * 'cookie list' command
-   */
-  gcli.addCommand({
-    name: "cookie list",
-    description: gcli.lookup("cookieListDesc"),
-    manual: gcli.lookup("cookieListManual"),
-    returnType: "cookies",
-    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 = [];
-      while (enm.hasMoreElements()) {
-        let cookie = enm.getNext().QueryInterface(Ci.nsICookie);
-        if (isCookieAtHost(cookie, host)) {
-          cookies.push({
-            host: cookie.host,
-            name: cookie.name,
-            value: cookie.value,
-            path: cookie.path,
-            expires: cookie.expires,
-            secure: cookie.secure,
-            httpOnly: cookie.httpOnly,
-            sameDomain: cookie.sameDomain
-          });
-        }
-      }
-
-      return cookies;
-    }
-  });
-
-  /**
-   * 'cookie remove' command
-   */
-  gcli.addCommand({
-    name: "cookie remove",
-    description: gcli.lookup("cookieRemoveDesc"),
-    manual: gcli.lookup("cookieRemoveManual"),
-    params: [
-      {
-        name: "name",
-        type: "string",
-        description: gcli.lookup("cookieRemoveKeyDesc"),
-      }
-    ],
-    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) {
-            cookieMgr.remove(cookie.host, cookie.name, cookie.path, false);
-          }
-        }
-      }
-    }
-  });
-
-  /**
-   * 'cookie set' command
-   */
-  gcli.addCommand({
-    name: "cookie set",
-    description: gcli.lookup("cookieSetDesc"),
-    manual: gcli.lookup("cookieSetManual"),
-    params: [
-      {
-        name: "name",
-        type: "string",
-        description: gcli.lookup("cookieSetKeyDesc")
-      },
-      {
-        name: "value",
-        type: "string",
-        description: gcli.lookup("cookieSetValueDesc")
-      },
-      {
-        group: gcli.lookup("cookieSetOptionsDesc"),
-        params: [
-          {
-            name: "path",
-            type: { name: "string", allowBlank: true },
-            defaultValue: "/",
-            description: gcli.lookup("cookieSetPathDesc")
-          },
-          {
-            name: "domain",
-            type: "string",
-            defaultValue: null,
-            description: gcli.lookup("cookieSetDomainDesc")
-          },
-          {
-            name: "secure",
-            type: "boolean",
-            description: gcli.lookup("cookieSetSecureDesc")
-          },
-          {
-            name: "httpOnly",
-            type: "boolean",
-            description: gcli.lookup("cookieSetHttpOnlyDesc")
-          },
-          {
-            name: "session",
-            type: "boolean",
-            description: gcli.lookup("cookieSetSessionDesc")
-          },
-          {
-            name: "expires",
-            type: "string",
-            defaultValue: "Jan 17, 2038",
-            description: gcli.lookup("cookieSetExpiresDesc")
-          },
-        ]
-      }
-    ],
-    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);
-    }
-  });
-}(this));
-
-/* CmdExport --------------------------------------------------------------- */
-
-(function(module) {
-  /**
-   * 'export' command
-   */
-  gcli.addCommand({
-    name: "export",
-    description: gcli.lookup("exportDesc"),
-  });
-
-  /**
-   * The 'export html' command. This command allows the user to export the page to
-   * HTML after they do DOM changes.
-   */
-  gcli.addCommand({
-    name: "export html",
-    description: gcli.lookup("exportHtmlDesc"),
-    exec: function(args, context) {
-      let html = context.environment.document.documentElement.outerHTML;
-      let url = 'data:text/plain;charset=utf8,' + encodeURIComponent(html);
-      context.environment.window.open(url);
-    }
-  });
-}(this));
-
-/* CmdJsb ------------------------------------------------------------------ */
-
-(function(module) {
-  const XMLHttpRequest =
-    Components.Constructor("@mozilla.org/xmlextras/xmlhttprequest;1");
-
-  XPCOMUtils.defineLazyModuleGetter(this, "js_beautify",
-                                    "resource:///modules/devtools/Jsbeautify.jsm");
-
-  /**
-   * jsb command.
-   */
-  gcli.addCommand({
-    name: 'jsb',
-    description: gcli.lookup('jsbDesc'),
-    returnValue:'string',
-    params: [
-      {
-        name: 'url',
-        type: 'string',
-        description: gcli.lookup('jsbUrlDesc')
-      },
-      {
-        group: gcli.lookup("jsbOptionsDesc"),
-        params: [
-          {
-            name: 'indentSize',
-            type: 'number',
-            description: gcli.lookup('jsbIndentSizeDesc'),
-            manual: gcli.lookup('jsbIndentSizeManual'),
-            defaultValue: 2
-          },
-          {
-            name: 'indentChar',
-            type: {
-              name: 'selection',
-              lookup: [
-                { name: "space", value: " " },
-                { name: "tab", value: "\t" }
-              ]
-            },
-            description: gcli.lookup('jsbIndentCharDesc'),
-            manual: gcli.lookup('jsbIndentCharManual'),
-            defaultValue: ' ',
-          },
-          {
-            name: 'doNotPreserveNewlines',
-            type: 'boolean',
-            description: gcli.lookup('jsbDoNotPreserveNewlinesDesc')
-          },
-          {
-            name: 'preserveMaxNewlines',
-            type: 'number',
-            description: gcli.lookup('jsbPreserveMaxNewlinesDesc'),
-            manual: gcli.lookup('jsbPreserveMaxNewlinesManual'),
-            defaultValue: -1
-          },
-          {
-            name: 'jslintHappy',
-            type: 'boolean',
-            description: gcli.lookup('jsbJslintHappyDesc'),
-            manual: gcli.lookup('jsbJslintHappyManual')
-          },
-          {
-            name: 'braceStyle',
-            type: {
-              name: 'selection',
-              data: ['collapse', 'expand', 'end-expand', 'expand-strict']
-            },
-            description: gcli.lookup('jsbBraceStyleDesc2'),
-            manual: gcli.lookup('jsbBraceStyleManual2'),
-            defaultValue: "collapse"
-          },
-          {
-            name: 'noSpaceBeforeConditional',
-            type: 'boolean',
-            description: gcli.lookup('jsbNoSpaceBeforeConditionalDesc')
-          },
-          {
-            name: 'unescapeStrings',
-            type: 'boolean',
-            description: gcli.lookup('jsbUnescapeStringsDesc'),
-            manual: gcli.lookup('jsbUnescapeStringsManual')
-          }
-        ]
-      }
-    ],
-    exec: function(args, context) {
-      let opts = {
-        indent_size: args.indentSize,
-        indent_char: args.indentChar,
-        preserve_newlines: !args.doNotPreserveNewlines,
-        max_preserve_newlines: args.preserveMaxNewlines == -1 ?
-                              undefined : args.preserveMaxNewlines,
-        jslint_happy: args.jslintHappy,
-        brace_style: args.braceStyle,
-        space_before_conditional: !args.noSpaceBeforeConditional,
-        unescape_strings: args.unescapeStrings
-      };
-
-      let xhr = new XMLHttpRequest();
-
-      try {
-        xhr.open("GET", args.url, true);
-      } catch(e) {
-        return gcli.lookup('jsbInvalidURL');
-      }
-
-      let deferred = context.defer();
-
-      xhr.onreadystatechange = function(aEvt) {
-        if (xhr.readyState == 4) {
-          if (xhr.status == 200 || xhr.status == 0) {
-            let browserDoc = context.environment.chromeDocument;
-            let browserWindow = browserDoc.defaultView;
-            let gBrowser = browserWindow.gBrowser;
-            let result = js_beautify(xhr.responseText, opts);
-
-            browserWindow.Scratchpad.ScratchpadManager.openScratchpad({text: result});
-
-            deferred.resolve();
-          } else {
-            deferred.resolve("Unable to load page to beautify: " + args.url + " " +
-                             xhr.status + " " + xhr.statusText);
-          }
-        };
-      }
-      xhr.send(null);
-      return deferred.promise;
-    }
-  });
-}(this));
-
-/* CmdPagemod -------------------------------------------------------------- */
-
-(function(module) {
-  /**
-   * 'pagemod' command
-   */
-  gcli.addCommand({
-    name: "pagemod",
-    description: gcli.lookup("pagemodDesc"),
-  });
-
-  /**
-   * The 'pagemod replace' command. This command allows the user to search and
-   * replace within text nodes and attributes.
-   */
-  gcli.addCommand({
-    name: "pagemod replace",
-    description: gcli.lookup("pagemodReplaceDesc"),
-    params: [
-      {
-        name: "search",
-        type: "string",
-        description: gcli.lookup("pagemodReplaceSearchDesc"),
-      },
-      {
-        name: "replace",
-        type: "string",
-        description: gcli.lookup("pagemodReplaceReplaceDesc"),
-      },
-      {
-        name: "ignoreCase",
-        type: "boolean",
-        description: gcli.lookup("pagemodReplaceIgnoreCaseDesc"),
-      },
-      {
-        name: "selector",
-        type: "string",
-        description: gcli.lookup("pagemodReplaceSelectorDesc"),
-        defaultValue: "*:not(script):not(style):not(embed):not(object):not(frame):not(iframe):not(frameset)",
-      },
-      {
-        name: "root",
-        type: "node",
-        description: gcli.lookup("pagemodReplaceRootDesc"),
-        defaultValue: null,
-      },
-      {
-        name: "attrOnly",
-        type: "boolean",
-        description: gcli.lookup("pagemodReplaceAttrOnlyDesc"),
-      },
-      {
-        name: "contentOnly",
-        type: "boolean",
-        description: gcli.lookup("pagemodReplaceContentOnlyDesc"),
-      },
-      {
-        name: "attributes",
-        type: "string",
-        description: gcli.lookup("pagemodReplaceAttributesDesc"),
-        defaultValue: null,
-      },
-    ],
-    exec: function(args, context) {
-      let searchTextNodes = !args.attrOnly;
-      let searchAttributes = !args.contentOnly;
-      let regexOptions = args.ignoreCase ? 'ig' : 'g';
-      let search = new RegExp(escapeRegex(args.search), regexOptions);
-      let attributeRegex = null;
-      if (args.attributes) {
-        attributeRegex = new RegExp(args.attributes, regexOptions);
-      }
-
-      let root = args.root || context.environment.document;
-      let elements = root.querySelectorAll(args.selector);
-      elements = Array.prototype.slice.call(elements);
-
-      let replacedTextNodes = 0;
-      let replacedAttributes = 0;
-
-      function replaceAttribute() {
-        replacedAttributes++;
-        return args.replace;
-      }
-      function replaceTextNode() {
-        replacedTextNodes++;
-        return args.replace;
-      }
-
-      for (let i = 0; i < elements.length; i++) {
-        let element = elements[i];
-        if (searchTextNodes) {
-          for (let y = 0; y < element.childNodes.length; y++) {
-            let node = element.childNodes[y];
-            if (node.nodeType == node.TEXT_NODE) {
-              node.textContent = node.textContent.replace(search, replaceTextNode);
-            }
-          }
-        }
-
-        if (searchAttributes) {
-          if (!element.attributes) {
-            continue;
-          }
-          for (let y = 0; y < element.attributes.length; y++) {
-            let attr = element.attributes[y];
-            if (!attributeRegex || attributeRegex.test(attr.name)) {
-              attr.value = attr.value.replace(search, replaceAttribute);
-            }
-          }
-        }
-      }
-
-      return gcli.lookupFormat("pagemodReplaceResult",
-                              [elements.length, replacedTextNodes,
-                                replacedAttributes]);
-    }
-  });
-
-  /**
-   * 'pagemod remove' command
-   */
-  gcli.addCommand({
-    name: "pagemod remove",
-    description: gcli.lookup("pagemodRemoveDesc"),
-  });
-
-
-  /**
-   * The 'pagemod remove element' command.
-   */
-  gcli.addCommand({
-    name: "pagemod remove element",
-    description: gcli.lookup("pagemodRemoveElementDesc"),
-    params: [
-      {
-        name: "search",
-        type: "string",
-        description: gcli.lookup("pagemodRemoveElementSearchDesc"),
-      },
-      {
-        name: "root",
-        type: "node",
-        description: gcli.lookup("pagemodRemoveElementRootDesc"),
-        defaultValue: null,
-      },
-      {
-        name: 'stripOnly',
-        type: 'boolean',
-        description: gcli.lookup("pagemodRemoveElementStripOnlyDesc"),
-      },
-      {
-        name: 'ifEmptyOnly',
-        type: 'boolean',
-        description: gcli.lookup("pagemodRemoveElementIfEmptyOnlyDesc"),
-      },
-    ],
-    exec: function(args, context) {
-      let root = args.root || context.environment.document;
-      let elements = Array.prototype.slice.call(root.querySelectorAll(args.search));
-
-      let removed = 0;
-      for (let i = 0; i < elements.length; i++) {
-        let element = elements[i];
-        let parentNode = element.parentNode;
-        if (!parentNode || !element.removeChild) {
-          continue;
-        }
-        if (args.stripOnly) {
-          while (element.hasChildNodes()) {
-            parentNode.insertBefore(element.childNodes[0], element);
-          }
-        }
-        if (!args.ifEmptyOnly || !element.hasChildNodes()) {
-          element.parentNode.removeChild(element);
-          removed++;
-        }
-      }
-
-      return gcli.lookupFormat("pagemodRemoveElementResultMatchedAndRemovedElements",
-                              [elements.length, removed]);
-    }
-  });
-
-  /**
-   * The 'pagemod remove attribute' command.
-   */
-  gcli.addCommand({
-    name: "pagemod remove attribute",
-    description: gcli.lookup("pagemodRemoveAttributeDesc"),
-    params: [
-      {
-        name: "searchAttributes",
-        type: "string",
-        description: gcli.lookup("pagemodRemoveAttributeSearchAttributesDesc"),
-      },
-      {
-        name: "searchElements",
-        type: "string",
-        description: gcli.lookup("pagemodRemoveAttributeSearchElementsDesc"),
-      },
-      {
-        name: "root",
-        type: "node",
-        description: gcli.lookup("pagemodRemoveAttributeRootDesc"),
-        defaultValue: null,
-      },
-      {
-        name: "ignoreCase",
-        type: "boolean",
-        description: gcli.lookup("pagemodRemoveAttributeIgnoreCaseDesc"),
-      },
-    ],
-    exec: function(args, context) {
-      let root = args.root || context.environment.document;
-      let regexOptions = args.ignoreCase ? 'ig' : 'g';
-      let attributeRegex = new RegExp(args.searchAttributes, regexOptions);
-      let elements = root.querySelectorAll(args.searchElements);
-      elements = Array.prototype.slice.call(elements);
-
-      let removed = 0;
-      for (let i = 0; i < elements.length; i++) {
-        let element = elements[i];
-        if (!element.attributes) {
-          continue;
-        }
-
-        var attrs = Array.prototype.slice.call(element.attributes);
-        for (let y = 0; y < attrs.length; y++) {
-          let attr = attrs[y];
-          if (attributeRegex.test(attr.name)) {
-            element.removeAttribute(attr.name);
-            removed++;
-          }
-        }
-      }
-
-      return gcli.lookupFormat("pagemodRemoveAttributeResult",
-                              [elements.length, removed]);
-    }
-  });
-
-  /**
-   * Make a given string safe to use  in a regular expression.
-   *
-   * @param string aString
-   *        The string you want to use in a regex.
-   * @return string
-   *         The equivalent of |aString| but safe to use in a regex.
-   */
-  function escapeRegex(aString) {
-    return aString.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
-  }
-}(this));
-
-/* CmdTools -------------------------------------------------------------- */
-
-(function(module) {
-  gcli.addCommand({
-    name: "tools",
-    description: gcli.lookupFormat("toolsDesc2", [BRAND_SHORT_NAME]),
-    manual: gcli.lookupFormat("toolsManual2", [BRAND_SHORT_NAME]),
-    get hidden() gcli.hiddenByChromePref(),
-  });
-
-  gcli.addCommand({
-    name: "tools srcdir",
-    description: gcli.lookup("toolsSrcdirDesc"),
-    manual: gcli.lookupFormat("toolsSrcdirManual2", [BRAND_SHORT_NAME]),
-    get hidden() gcli.hiddenByChromePref(),
-    params: [
-      {
-        name: "srcdir",
-        type: "string" /* {
-          name: "file",
-          filetype: "directory",
-          existing: "yes"
-        } */,
-        description: gcli.lookup("toolsSrcdirDir")
-      }
-    ],
-    returnType: "string",
-    exec: function(args, context) {
-      let clobber = OS.Path.join(args.srcdir, "CLOBBER");
-      return OS.File.exists(clobber).then(function(exists) {
-        if (exists) {
-          let str = Cc["@mozilla.org/supports-string;1"]
-                    .createInstance(Ci.nsISupportsString);
-          str.data = args.srcdir;
-          Services.prefs.setComplexValue("devtools.loader.srcdir",
-                                         Ci.nsISupportsString, str);
-          devtools.reload();
-
-          let msg = gcli.lookupFormat("toolsSrcdirReloaded", [args.srcdir]);
-          throw new Error(msg);
-        }
-
-        return gcli.lookupFormat("toolsSrcdirNotFound", [args.srcdir]);
-      });
-    }
-  });
-
-  gcli.addCommand({
-    name: "tools builtin",
-    description: gcli.lookup("toolsBuiltinDesc"),
-    manual: gcli.lookup("toolsBuiltinManual"),
-    get hidden() gcli.hiddenByChromePref(),
-    returnType: "string",
-    exec: function(args, context) {
-      Services.prefs.clearUserPref("devtools.loader.srcdir");
-      devtools.reload();
-      return gcli.lookup("toolsBuiltinReloaded");
-    }
-  });
-
-  gcli.addCommand({
-    name: "tools reload",
-    description: gcli.lookup("toolsReloadDesc"),
-    get hidden() gcli.hiddenByChromePref() || !Services.prefs.prefHasUserValue("devtools.loader.srcdir"),
-
-    returnType: "string",
-    exec: function(args, context) {
-      devtools.reload();
-      return gcli.lookup("toolsReloaded2");
-    }
-  });
-}(this));
-
-/* CmdRestart -------------------------------------------------------------- */
-
-(function(module) {
-  /**
-   * Restart command
-   *
-   * @param boolean nocache
-   *        Disables loading content from cache upon restart.
-   *
-   * Examples :
-   * >> restart
-   * - restarts browser immediately
-   * >> restart --nocache
-   * - restarts immediately and starts Firefox without using cache
-   */
-  gcli.addCommand({
-    name: "restart",
-    description: gcli.lookupFormat("restartBrowserDesc", [BRAND_SHORT_NAME]),
-    params: [
-      {
-        name: "nocache",
-        type: "boolean",
-        description: gcli.lookup("restartBrowserNocacheDesc")
-      }
-    ],
-    returnType: "string",
-    exec: function Restart(args, context) {
-      let canceled = Cc["@mozilla.org/supports-PRBool;1"]
-                      .createInstance(Ci.nsISupportsPRBool);
-      Services.obs.notifyObservers(canceled, "quit-application-requested", "restart");
-      if (canceled.data) {
-        return gcli.lookup("restartBrowserRequestCancelled");
-      }
-
-      // disable loading content from cache.
-      if (args.nocache) {
-        Services.appinfo.invalidateCachesOnRestart();
-      }
-
-      // restart
-      Cc['@mozilla.org/toolkit/app-startup;1']
-        .getService(Ci.nsIAppStartup)
-        .quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
-      return gcli.lookupFormat("restartBrowserRestarting", [BRAND_SHORT_NAME]);
-    }
-  });
-}(this));
-
-/* CmdScreenshot ----------------------------------------------------------- */
-
-(function(module) {
-  XPCOMUtils.defineLazyModuleGetter(this, "LayoutHelpers",
-                                    "resource://gre/modules/devtools/LayoutHelpers.jsm");
-
-  // String used as an indication to generate default file name in the following
-  // format: "Screen Shot yyyy-mm-dd at HH.MM.SS.png"
-  const FILENAME_DEFAULT_VALUE = " ";
-
-  /**
-   * 'screenshot' command
-   */
-  gcli.addCommand({
-    name: "screenshot",
-    description: gcli.lookup("screenshotDesc"),
-    manual: gcli.lookup("screenshotManual"),
-    returnType: "dom",
-    params: [
-      {
-        name: "filename",
-        type: "string",
-        defaultValue: FILENAME_DEFAULT_VALUE,
-        description: gcli.lookup("screenshotFilenameDesc"),
-        manual: gcli.lookup("screenshotFilenameManual")
-      },
-      {
-        group: gcli.lookup("screenshotGroupOptions"),
-        params: [
-          {
-            name: "clipboard",
-            type: "boolean",
-            description: gcli.lookup("screenshotClipboardDesc"),
-            manual: gcli.lookup("screenshotClipboardManual")
-          },
-          {
-            name: "chrome",
-            type: "boolean",
-            description: gcli.lookupFormat("screenshotChromeDesc2", [BRAND_SHORT_NAME]),
-            manual: gcli.lookupFormat("screenshotChromeManual2", [BRAND_SHORT_NAME])
-          },
-          {
-            name: "delay",
-            type: { name: "number", min: 0 },
-            defaultValue: 0,
-            description: gcli.lookup("screenshotDelayDesc"),
-            manual: gcli.lookup("screenshotDelayManual")
-          },
-          {
-            name: "fullpage",
-            type: "boolean",
-            description: gcli.lookup("screenshotFullPageDesc"),
-            manual: gcli.lookup("screenshotFullPageManual")
-          },
-          {
-            name: "selector",
-            type: "node",
-            defaultValue: null,
-            description: gcli.lookup("inspectNodeDesc"),
-            manual: gcli.lookup("inspectNodeManual")
-          }
-        ]
-      }
-    ],
-    exec: function Command_screenshot(args, context) {
-      if (args.chrome && args.selector) {
-        // Node screenshot with chrome option does not work as inteded
-        // Refer https://bugzilla.mozilla.org/show_bug.cgi?id=659268#c7
-        // throwing for now.
-        throw new Error(gcli.lookup("screenshotSelectorChromeConflict"));
-      }
-      var document = args.chrome? context.environment.chromeDocument
-                                : context.environment.document;
-      var deferred = context.defer();
-      if (args.delay > 0) {
-        document.defaultView.setTimeout(function Command_screenshotDelay() {
-          let promise = this.grabScreen(document, args.filename, args.clipboard,
-                                        args.fullpage);
-          promise.then(deferred.resolve, deferred.reject);
-        }.bind(this), args.delay * 1000);
-      }
-      else {
-        let promise = this.grabScreen(document, args.filename, args.clipboard,
-                                      args.fullpage, args.selector);
-        promise.then(deferred.resolve, deferred.reject);
-      }
-      return deferred.promise;
-    },
-    grabScreen: function(document, filename, clipboard, fullpage, node) {
-      return Task.spawn(function() {
-        let window = document.defaultView;
-        let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
-        let left = 0;
-        let top = 0;
-        let width;
-        let height;
-        let div = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
-
-        if (!fullpage) {
-          if (!node) {
-            left = window.scrollX;
-            top = window.scrollY;
-            width = window.innerWidth;
-            height = window.innerHeight;
-          } else {
-            let lh = new LayoutHelpers(window);
-            let rect = lh.getRect(node, window);
-            top = rect.top;
-            left = rect.left;
-            width = rect.width;
-            height = rect.height;
-          }
-        } else {
-          width = window.innerWidth + window.scrollMaxX;
-          height = window.innerHeight + window.scrollMaxY;
-        }
-        canvas.width = width;
-        canvas.height = height;
-
-        let ctx = canvas.getContext("2d");
-        ctx.drawWindow(window, left, top, width, height, "#fff");
-        let data = canvas.toDataURL("image/png", "");
-
-        let loadContext = document.defaultView
-                                  .QueryInterface(Ci.nsIInterfaceRequestor)
-                                  .getInterface(Ci.nsIWebNavigation)
-                                  .QueryInterface(Ci.nsILoadContext);
-
-        if (clipboard) {
-          try {
-            let io = Cc["@mozilla.org/network/io-service;1"]
-                      .getService(Ci.nsIIOService);
-            let channel = io.newChannel(data, null, null);
-            let input = channel.open();
-            let imgTools = Cc["@mozilla.org/image/tools;1"]
-                            .getService(Ci.imgITools);
-
-            let container = {};
-            imgTools.decodeImageData(input, channel.contentType, container);
-
-            let wrapped = Cc["@mozilla.org/supports-interface-pointer;1"]
-                            .createInstance(Ci.nsISupportsInterfacePointer);
-            wrapped.data = container.value;
-
-            let trans = Cc["@mozilla.org/widget/transferable;1"]
-                          .createInstance(Ci.nsITransferable);
-            trans.init(loadContext);
-            trans.addDataFlavor(channel.contentType);
-            trans.setTransferData(channel.contentType, wrapped, -1);
-
-            let clipid = Ci.nsIClipboard;
-            let clip = Cc["@mozilla.org/widget/clipboard;1"].getService(clipid);
-            clip.setData(trans, null, clipid.kGlobalClipboard);
-            div.textContent = gcli.lookup("screenshotCopied");
-          }
-          catch (ex) {
-            div.textContent = gcli.lookup("screenshotErrorCopying");
-          }
-          throw new Task.Result(div);
-        }
-
-        let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
-
-        // Create a name for the file if not present
-        if (filename == FILENAME_DEFAULT_VALUE) {
-          let date = new Date();
-          let dateString = date.getFullYear() + "-" + (date.getMonth() + 1) +
-                          "-" + date.getDate();
-          dateString = dateString.split("-").map(function(part) {
-            if (part.length == 1) {
-              part = "0" + part;
-            }
-            return part;
-          }).join("-");
-          let timeString = date.toTimeString().replace(/:/g, ".").split(" ")[0];
-          filename = gcli.lookupFormat("screenshotGeneratedFilename",
-                                      [dateString, timeString]) + ".png";
-        }
-        // Check there is a .png extension to filename
-        else if (!filename.match(/.png$/i)) {
-          filename += ".png";
-        }
-        // If the filename is relative, tack it onto the download directory
-        if (!filename.match(/[\\\/]/)) {
-          let preferredDir = yield Downloads.getPreferredDownloadsDirectory();
-          filename = OS.Path.join(preferredDir, filename);
-        }
-
-        try {
-          file.initWithPath(filename);
-        } catch (ex) {
-          div.textContent = gcli.lookup("screenshotErrorSavingToFile") + " " + filename;
-          throw new Task.Result(div);
-        }
-
-        let ioService = Cc["@mozilla.org/network/io-service;1"]
-                          .getService(Ci.nsIIOService);
-
-        let Persist = Ci.nsIWebBrowserPersist;
-        let persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
-                        .createInstance(Persist);
-        persist.persistFlags = Persist.PERSIST_FLAGS_REPLACE_EXISTING_FILES |
-                               Persist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
-
-        let source = ioService.newURI(data, "UTF8", null);
-        persist.saveURI(source, null, null, null, null, file, loadContext);
-
-        div.textContent = gcli.lookup("screenshotSavedToFile") + " \"" + filename +
-                          "\"";
-        div.addEventListener("click", function openFile() {
-          div.removeEventListener("click", openFile);
-          file.reveal();
-        });
-        div.style.cursor = "pointer";
-        let image = document.createElement("div");
-        let previewHeight = parseInt(256*height/width);
-        image.setAttribute("style",
-                          "width:256px; height:" + previewHeight + "px;" +
-                          "max-height: 256px;" +
-                          "background-image: url('" + data + "');" +
-                          "background-size: 256px " + previewHeight + "px;" +
-                          "margin: 4px; display: block");
-        div.appendChild(image);
-        throw new Task.Result(div);
-      });
-    }
-  });
-}(this));
-
-
-/* Remoting ----------------------------------------------------------- */
-
-const { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
-
-/**
- * 'listen' command
- */
-gcli.addCommand({
-  name: "listen",
-  description: gcli.lookup("listenDesc"),
-  manual: gcli.lookupFormat("listenManual2", [BRAND_SHORT_NAME]),
-  params: [
-    {
-      name: "port",
-      type: "number",
-      get defaultValue() {
-        return Services.prefs.getIntPref("devtools.debugger.chrome-debugging-port");
-      },
-      description: gcli.lookup("listenPortDesc"),
-    }
-  ],
-  exec: function Command_screenshot(args, context) {
-    if (!DebuggerServer.initialized) {
-      DebuggerServer.init();
-      DebuggerServer.addBrowserActors();
-    }
-    var reply = DebuggerServer.openListener(args.port);
-    if (!reply) {
-      throw new Error(gcli.lookup("listenDisabledOutput"));
-    }
-
-    if (DebuggerServer.initialized) {
-      return gcli.lookupFormat("listenInitOutput", [ '' + args.port ]);
-    }
-
-    return gcli.lookup("listenNoInitOutput");
-  },
-});
-
-
-/* CmdPaintFlashing ------------------------------------------------------- */
-
-(function(module) {
-  /**
-   * 'paintflashing' command
-   */
-  gcli.addCommand({
-    name: 'paintflashing',
-    description: gcli.lookup('paintflashingDesc')
-  });
-
-  gcli.addCommand({
-    name: 'paintflashing on',
-    description: gcli.lookup('paintflashingOnDesc'),
-    manual: gcli.lookup('paintflashingManual'),
-    params: [{
-      group: "options",
-      params: [
-        {
-          type: "boolean",
-          name: "chrome",
-          get hidden() gcli.hiddenByChromePref(),
-          description: gcli.lookup("paintflashingChromeDesc"),
-        }
-      ]
-    }],
-    exec: function(args, context) {
-      var window = args.chrome ?
-                  context.environment.chromeWindow :
-                  context.environment.window;
-
-      window.QueryInterface(Ci.nsIInterfaceRequestor)
-            .getInterface(Ci.nsIDOMWindowUtils)
-            .paintFlashing = true;
-      onPaintFlashingChanged(context);
-    }
-  });
-
-  gcli.addCommand({
-    name: 'paintflashing off',
-    description: gcli.lookup('paintflashingOffDesc'),
-    manual: gcli.lookup('paintflashingManual'),
-    params: [{
-      group: "options",
-      params: [
-        {
-          type: "boolean",
-          name: "chrome",
-          get hidden() gcli.hiddenByChromePref(),
-          description: gcli.lookup("paintflashingChromeDesc"),
-        }
-      ]
-    }],
-    exec: function(args, context) {
-      var window = args.chrome ?
-                  context.environment.chromeWindow :
-                  context.environment.window;
-
-      window.QueryInterface(Ci.nsIInterfaceRequestor)
-            .getInterface(Ci.nsIDOMWindowUtils)
-            .paintFlashing = false;
-      onPaintFlashingChanged(context);
-    }
-  });
-
-  gcli.addCommand({
-    name: 'paintflashing toggle',
-    hidden: true,
-    buttonId: "command-button-paintflashing",
-    buttonClass: "command-button command-button-invertable",
-    state: {
-      isChecked: function(aTarget) {
-        if (aTarget.isLocalTab) {
-          let window = aTarget.tab.linkedBrowser.contentWindow;
-          let wUtils = window.QueryInterface(Ci.nsIInterfaceRequestor).
-                              getInterface(Ci.nsIDOMWindowUtils);
-          return wUtils.paintFlashing;
-        } else {
-          throw new Error("Unsupported target");
-        }
-      },
-      onChange: function(aTarget, aChangeHandler) {
-        eventEmitter.on("changed", aChangeHandler);
-      },
-      offChange: function(aTarget, aChangeHandler) {
-        eventEmitter.off("changed", aChangeHandler);
-      },
-    },
-    tooltipText: gcli.lookup("paintflashingTooltip"),
-    description: gcli.lookup('paintflashingToggleDesc'),
-    manual: gcli.lookup('paintflashingManual'),
-    exec: function(args, context) {
-      var window = context.environment.window;
-      var wUtils = window.QueryInterface(Ci.nsIInterfaceRequestor).
-                   getInterface(Ci.nsIDOMWindowUtils);
-      wUtils.paintFlashing = !wUtils.paintFlashing;
-      onPaintFlashingChanged(context);
-    }
-  });
-
-  let eventEmitter = new EventEmitter();
-  function onPaintFlashingChanged(context) {
-    var gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
-    var tab = gBrowser.selectedTab;
-    eventEmitter.emit("changed", tab);
-    function fireChange() {
-      eventEmitter.emit("changed", tab);
-    }
-    var target = devtools.TargetFactory.forTab(tab);
-    target.off("navigate", fireChange);
-    target.once("navigate", fireChange);
-
-    var window = context.environment.window;
-    var wUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
-                       .getInterface(Ci.nsIDOMWindowUtils);
-    if (wUtils.paintFlashing) {
-      telemetry.toolOpened("paintflashing");
-    } else {
-      telemetry.toolClosed("paintflashing");
-    }
-  }
-}(this));
-
-/* CmdAppCache ------------------------------------------------------- */
-
-(function(module) {
-  /**
-   * 'appcache' command
-   */
-
-  gcli.addCommand({
-    name: 'appcache',
-    description: gcli.lookup('appCacheDesc')
-  });
-
-  gcli.addConverter({
-    from: "appcacheerrors",
-    to: "view",
-    exec: function([errors, manifestURI], context) {
-      if (errors.length == 0) {
-        return context.createView({
-          html: "<span>" + gcli.lookup("appCacheValidatedSuccessfully") + "</span>"
-        });
-      }
-
-      let appcacheValidateHtml =
-        "<h4>Manifest URI: ${manifestURI}</h4>" +
-        "<ol>" +
-        "  <li foreach='error in ${errors}'>" +
-        "    ${error.msg}" +
-        "  </li>" +
-        "</ol>";
-
-      return context.createView({
-        html: "<div>" + appcacheValidateHtml + "</div>",
-        data: {
-          errors: errors,
-          manifestURI: manifestURI
-        }
-      });
-    }
-  });
-
-  gcli.addCommand({
-    name: 'appcache validate',
-    description: gcli.lookup('appCacheValidateDesc'),
-    manual: gcli.lookup('appCacheValidateManual'),
-    returnType: 'appcacheerrors',
-    params: [{
-      group: "options",
-      params: [
-        {
-          type: "string",
-          name: "uri",
-          description: gcli.lookup("appCacheValidateUriDesc"),
-          defaultValue: null,
-        }
-      ]
-    }],
-    exec: function(args, context) {
-      let utils;
-      let deferred = context.defer();
-
-      if (args.uri) {
-        utils = new AppCacheUtils(args.uri);
-      } else {
-        utils = new AppCacheUtils(context.environment.document);
-      }
-
-      utils.validateManifest().then(function(errors) {
-        deferred.resolve([errors, utils.manifestURI || "-"]);
-      });
-
-      return deferred.promise;
-    }
-  });
-
-  gcli.addCommand({
-    name: 'appcache clear',
-    description: gcli.lookup('appCacheClearDesc'),
-    manual: gcli.lookup('appCacheClearManual'),
-    exec: function(args, context) {
-      let utils = new AppCacheUtils(args.uri);
-      utils.clearAll();
-
-      return gcli.lookup("appCacheClearCleared");
-    }
-  });
-
-  let appcacheListEntries = "" +
-    "<ul class='gcli-appcache-list'>" +
-    "  <li foreach='entry in ${entries}'>" +
-    "    <table class='gcli-appcache-detail'>" +
-    "      <tr>" +
-    "        <td>" + gcli.lookup("appCacheListKey") + "</td>" +
-    "        <td>${entry.key}</td>" +
-    "      </tr>" +
-    "      <tr>" +
-    "        <td>" + gcli.lookup("appCacheListFetchCount") + "</td>" +
-    "        <td>${entry.fetchCount}</td>" +
-    "      </tr>" +
-    "      <tr>" +
-    "        <td>" + gcli.lookup("appCacheListLastFetched") + "</td>" +
-    "        <td>${entry.lastFetched}</td>" +
-    "      </tr>" +
-    "      <tr>" +
-    "        <td>" + gcli.lookup("appCacheListLastModified") + "</td>" +
-    "        <td>${entry.lastModified}</td>" +
-    "      </tr>" +
-    "      <tr>" +
-    "        <td>" + gcli.lookup("appCacheListExpirationTime") + "</td>" +
-    "        <td>${entry.expirationTime}</td>" +
-    "      </tr>" +
-    "      <tr>" +
-    "        <td>" + gcli.lookup("appCacheListDataSize") + "</td>" +
-    "        <td>${entry.dataSize}</td>" +
-    "      </tr>" +
-    "      <tr>" +
-    "        <td>" + gcli.lookup("appCacheListDeviceID") + "</td>" +
-    "        <td>${entry.deviceID} <span class='gcli-out-shortcut' " +
-    "onclick='${onclick}' ondblclick='${ondblclick}' " +
-    "data-command='appcache viewentry ${entry.key}'" +
-    ">" + gcli.lookup("appCacheListViewEntry") + "</span>" +
-    "        </td>" +
-    "      </tr>" +
-    "    </table>" +
-    "  </li>" +
-    "</ul>";
-
-  gcli.addConverter({
-    from: "appcacheentries",
-    to: "view",
-    exec: function(entries, context) {
-      return context.createView({
-        html: appcacheListEntries,
-        data: {
-          entries: entries,
-          onclick: context.update,
-          ondblclick: context.updateExec
-        }
-      });
-    }
-  });
-
-  gcli.addCommand({
-    name: 'appcache list',
-    description: gcli.lookup('appCacheListDesc'),
-    manual: gcli.lookup('appCacheListManual'),
-    returnType: "appcacheentries",
-    params: [{
-      group: "options",
-      params: [
-        {
-          type: "string",
-          name: "search",
-          description: gcli.lookup("appCacheListSearchDesc"),
-          defaultValue: null,
-        },
-      ]
-    }],
-    exec: function(args, context) {
-      let utils = new AppCacheUtils();
-      return utils.listEntries(args.search);
-    }
-  });
-
-  gcli.addCommand({
-    name: 'appcache viewentry',
-    description: gcli.lookup('appCacheViewEntryDesc'),
-    manual: gcli.lookup('appCacheViewEntryManual'),
-    params: [
-      {
-        type: "string",
-        name: "key",
-        description: gcli.lookup("appCacheViewEntryKey"),
-        defaultValue: null,
-      }
-    ],
-    exec: function(args, context) {
-      let utils = new AppCacheUtils();
-      return utils.viewEntry(args.key);
-    }
-  });
-}(this));
-
-/* CmdMedia ------------------------------------------------------- */
-
-(function(module) {
-  /**
-   * 'media' command
-   */
-
-  gcli.addCommand({
-    name: "media",
-    description: gcli.lookup("mediaDesc")
-  });
-
-  gcli.addCommand({
-    name: "media emulate",
-    description: gcli.lookup("mediaEmulateDesc"),
-    manual: gcli.lookup("mediaEmulateManual"),
-    params: [
-      {
-        name: "type",
-        description: gcli.lookup("mediaEmulateType"),
-        type: {
-               name: "selection",
-               data: ["braille", "embossed", "handheld", "print", "projection",
-                      "screen", "speech", "tty", "tv"]
-              }
-      }
-    ],
-    exec: function(args, context) {
-      let markupDocumentViewer = context.environment.chromeWindow
-                                        .gBrowser.markupDocumentViewer;
-      markupDocumentViewer.emulateMedium(args.type);
-    }
-  });
-
-  gcli.addCommand({
-    name: "media reset",
-    description: gcli.lookup("mediaResetDesc"),
-    manual: gcli.lookup("mediaEmulateManual"),
-    exec: function(args, context) {
-      let markupDocumentViewer = context.environment.chromeWindow
-                                        .gBrowser.markupDocumentViewer;
-      markupDocumentViewer.stopEmulatingMedium();
-    }
-  });
-}(this));
-
-/* CmdSplitConsole ------------------------------------------------------- */
-
-(function(module) {
-  /**
-   * 'splitconsole' command (hidden)
-   */
-
-  gcli.addCommand({
-    name: 'splitconsole',
-    hidden: true,
-    buttonId: "command-button-splitconsole",
-    buttonClass: "command-button command-button-invertable",
-    tooltipText: gcli.lookup("splitconsoleTooltip"),
-    state: {
-      isChecked: function(aTarget) {
-        let toolbox = gDevTools.getToolbox(aTarget);
-        return toolbox &&
-          toolbox.splitConsole;
-      },
-      onChange: function(aTarget, aChangeHandler) {
-        eventEmitter.on("changed", aChangeHandler);
-      },
-      offChange: function(aTarget, aChangeHandler) {
-        eventEmitter.off("changed", aChangeHandler);
-      },
-    },
-    exec: function(args, context) {
-      toggleSplitConsole(context);
-    }
-  });
-
-  function toggleSplitConsole(context) {
-    let gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
-    let target = devtools.TargetFactory.forTab(gBrowser.selectedTab);
-    let toolbox = gDevTools.getToolbox(target);
-
-    if (!toolbox) {
-      gDevTools.showToolbox(target, "inspector").then((toolbox) => {
-        toolbox.toggleSplitConsole();
-      });
-    } else {
-      toolbox.toggleSplitConsole();
-    }
-  }
-
-  let eventEmitter = new EventEmitter();
-  function fireChange(tab) {
-    eventEmitter.emit("changed", tab);
-  }
-
-  gDevTools.on("toolbox-ready", (e, toolbox) => {
-    if (!toolbox.target) {
-      return;
-    }
-    let fireChangeForTab = fireChange.bind(this, toolbox.target.tab);
-    toolbox.on("split-console", fireChangeForTab);
-    toolbox.on("select", fireChangeForTab);
-    toolbox.once("destroyed", () => {
-      toolbox.off("split-console", fireChangeForTab);
-      toolbox.off("select", fireChangeForTab);
-    });
-  });
-
-}(this));
deleted file mode 100644
--- a/browser/devtools/commandline/Makefile.in
+++ /dev/null
@@ -1,9 +0,0 @@
-#
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-include $(topsrcdir)/config/rules.mk
-
-libs::
-	$(NSINSTALL) $(srcdir)/*.jsm $(FINAL_TARGET)/modules/devtools
rename from browser/devtools/commandline/Commands.jsm
rename to browser/devtools/commandline/commands-index.js
--- a/browser/devtools/commandline/Commands.jsm
+++ b/browser/devtools/commandline/commands-index.js
@@ -1,18 +1,37 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-this.EXPORTED_SYMBOLS = [];
+"use strict";
 
-const Cu = Components.utils;
-const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
+const gcli = require("gcli/index");
 
-Cu.import("resource:///modules/devtools/BuiltinCommands.jsm");
-Cu.import("resource:///modules/devtools/CmdDebugger.jsm");
-Cu.import("resource:///modules/devtools/CmdEdit.jsm");
-Cu.import("resource:///modules/devtools/CmdInspect.jsm");
-Cu.import("resource:///modules/devtools/CmdResize.jsm");
-Cu.import("resource:///modules/devtools/CmdTilt.jsm");
-Cu.import("resource:///modules/devtools/CmdScratchpad.jsm");
+const commandModules = [
+  "devtools/tilt/tilt-commands",
+  "gcli/commands/addon",
+  "gcli/commands/appcache",
+  "gcli/commands/calllog",
+  "gcli/commands/cmd",
+  "gcli/commands/cookie",
+  "gcli/commands/jsb",
+  "gcli/commands/listen",
+  "gcli/commands/media",
+  "gcli/commands/pagemod",
+  "gcli/commands/paintflashing",
+  "gcli/commands/restart",
+  "gcli/commands/screenshot",
+  "gcli/commands/tools",
+];
 
-require("devtools/profiler/commands.js");
+gcli.addItemsByModule(commandModules, { delayedLoad: true });
+
+const defaultTools = require("main").defaultTools;
+for (let definition of defaultTools) {
+  if (definition.commands) {
+    gcli.addItemsByModule(definition.commands, { delayedLoad: true });
+  }
+}
+
+const { mozDirLoader } = require("gcli/commands/cmd");
+
+gcli.addItemsByModule("mozcmd", { delayedLoad: true, loader: mozDirLoader });
deleted file mode 100644
--- a/browser/devtools/commandline/gcli.jsm
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * 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.
- */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = [ "gcli" ];
-Components.utils.import("resource://gre/modules/devtools/gcli.jsm");
--- a/browser/devtools/commandline/moz.build
+++ b/browser/devtools/commandline/moz.build
@@ -1,7 +1,11 @@
-# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+JS_MODULES_PATH = 'modules/devtools/commandline'
+
+EXTRA_JS_MODULES += [
+    'commands-index.js'
+]
+
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
--- a/browser/devtools/commandline/test/browser_cmd_addon.js
+++ b/browser/devtools/commandline/test/browser_cmd_addon.js
@@ -1,151 +1,134 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that the addon commands works as they should
 
-let CmdAddonFlags = (Cu.import("resource:///modules/devtools/BuiltinCommands.jsm", {})).CmdAddonFlags;
-
-let tests = {};
-
 function test() {
-  helpers.addTabWithToolbar("about:blank", function(options) {
-    return helpers.runTests(options, tests);
-  }).then(finish, helpers.handleError);
+  return Task.spawn(spawnTest).then(finish, helpers.handleError);
 }
 
-tests.gatTest = function(options) {
-  let deferred = promise.defer();
-
-  let onGatReady = function() {
-    Services.obs.removeObserver(onGatReady, "gcli_addon_commands_ready");
-    info("gcli_addon_commands_ready notification received, running tests");
+function spawnTest() {
+  let options = yield helpers.openTab("about:blank");
+  yield helpers.openToolbar(options);
 
-    let auditDone = helpers.audit(options, [
-      {
-        setup: 'addon list dictionary',
-        check: {
-          input:  'addon list dictionary',
-          hints:                       '',
-          markup: 'VVVVVVVVVVVVVVVVVVVVV',
-          status: 'VALID'
-        },
-        exec: {
-          output: 'There are no add-ons of that type installed.'
-        }
+  yield helpers.audit(options, [
+    {
+      setup: 'addon list dictionary',
+      check: {
+        input:  'addon list dictionary',
+        hints:                       '',
+        markup: 'VVVVVVVVVVVVVVVVVVVVV',
+        status: 'VALID'
+      },
+      exec: {
+        output: 'There are no add-ons of that type installed.'
+      }
+    },
+    {
+      setup: 'addon list extension',
+      check: {
+        input:  'addon list extension',
+        hints:                      '',
+        markup: 'VVVVVVVVVVVVVVVVVVVV',
+        status: 'VALID'
+      },
+      exec: {
+        output: [/The following/, /Mochitest/, /Special Powers/]
+      }
+    },
+    {
+      setup: 'addon list locale',
+      check: {
+        input:  'addon list locale',
+        hints:                   '',
+        markup: 'VVVVVVVVVVVVVVVVV',
+        status: 'VALID'
+      },
+      exec: {
+        output: 'There are no add-ons of that type installed.'
+      }
+    },
+    {
+      setup: 'addon list plugin',
+      check: {
+        input:  'addon list plugin',
+        hints:                   '',
+        markup: 'VVVVVVVVVVVVVVVVV',
+        status: 'VALID'
       },
-      {
-        setup: 'addon list extension',
-        check: {
-          input:  'addon list extension',
-          hints:                      '',
-          markup: 'VVVVVVVVVVVVVVVVVVVV',
-          status: 'VALID'
-        },
-        exec: {
-          output: [/The following/, /Mochitest/, /Special Powers/]
-        }
+      exec: {
+        output: [/Test Plug-in/, /Second Test Plug-in/]
+      }
+    },
+    {
+      setup: 'addon list theme',
+      check: {
+        input:  'addon list theme',
+        hints:                  '',
+        markup: 'VVVVVVVVVVVVVVVV',
+        status: 'VALID'
+      },
+      exec: {
+        output: [/following themes/, /Default/]
+      }
+    },
+    {
+      setup: 'addon list all',
+      check: {
+        input:  'addon list all',
+        hints:                '',
+        markup: 'VVVVVVVVVVVVVV',
+        status: 'VALID'
       },
-      {
-        setup: 'addon list locale',
-        check: {
-          input:  'addon list locale',
-          hints:                   '',
-          markup: 'VVVVVVVVVVVVVVVVV',
-          status: 'VALID'
-        },
-        exec: {
-          output: 'There are no add-ons of that type installed.'
-        }
+      exec: {
+        output: [/The following/, /Default/, /Mochitest/, /Test Plug-in/,
+                 /Second Test Plug-in/, /Special Powers/]
+      }
+    },
+    {
+      setup: 'addon disable Test_Plug-in_1.0.0.0',
+      check: {
+        input:  'addon disable Test_Plug-in_1.0.0.0',
+        hints:                                    '',
+        markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV',
+        status: 'VALID'
       },
-      {
-        setup: 'addon list plugin',
-        check: {
-          input:  'addon list plugin',
-          hints:                   '',
-          markup: 'VVVVVVVVVVVVVVVVV',
-          status: 'VALID'
-        },
-        exec: {
-          output: [/Test Plug-in/, /Second Test Plug-in/]
+      exec: {
+        output: 'Test Plug-in 1.0.0.0 disabled.'
+      }
+    },
+    {
+      setup: 'addon disable WRONG',
+      check: {
+        input:  'addon disable WRONG',
+        hints:                     '',
+        markup: 'VVVVVVVVVVVVVVEEEEE',
+        status: 'ERROR'
+      }
+    },
+    {
+      setup: 'addon enable Test_Plug-in_1.0.0.0',
+      check: {
+        input:  'addon enable Test_Plug-in_1.0.0.0',
+        hints:                                   '',
+        markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV',
+        status: 'VALID',
+        args: {
+          command: { name: 'addon enable' },
+          addon: {
+            value: function(addon) {
+              is(addon.name, 'Test Plug-in', 'test plugin name');
+            },
+            status: 'VALID'
+          }
         }
       },
-      {
-        setup: 'addon list theme',
-        check: {
-          input:  'addon list theme',
-          hints:                  '',
-          markup: 'VVVVVVVVVVVVVVVV',
-          status: 'VALID'
-        },
-        exec: {
-          output: [/following themes/, /Default/]
-        }
-      },
-      {
-        setup: 'addon list all',
-        check: {
-          input:  'addon list all',
-          hints:                '',
-          markup: 'VVVVVVVVVVVVVV',
-          status: 'VALID'
-        },
-        exec: {
-          output: [/The following/, /Default/, /Mochitest/, /Test Plug-in/,
-                   /Second Test Plug-in/, /Special Powers/]
-        }
-      },
-      {
-        setup: 'addon disable Test_Plug-in_1.0.0.0',
-        check: {
-          input:  'addon disable Test_Plug-in_1.0.0.0',
-          hints:                                    '',
-          markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV',
-          status: 'VALID'
-        },
-        exec: {
-          output: 'Test Plug-in 1.0.0.0 disabled.'
-        }
-      },
-      {
-        setup: 'addon disable WRONG',
-        check: {
-          input:  'addon disable WRONG',
-          hints:                     '',
-          markup: 'VVVVVVVVVVVVVVEEEEE',
-          status: 'ERROR'
-        }
-      },
-      {
-        setup: 'addon enable Test_Plug-in_1.0.0.0',
-        check: {
-          input:  'addon enable Test_Plug-in_1.0.0.0',
-          hints:                                   '',
-          markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV',
-          status: 'VALID',
-          args: {
-            command: { name: 'addon enable' },
-            name: { value: 'Test Plug-in', status: 'VALID' },
-          }
-        },
-        exec: {
-          output: 'Test Plug-in 1.0.0.0 enabled.'
-        }
+      exec: {
+        output: 'Test Plug-in 1.0.0.0 enabled.'
       }
-    ]);
-
-    auditDone.then(deferred.resolve, deferred.reject);
-  };
-
-  Services.obs.addObserver(onGatReady, "gcli_addon_commands_ready", false);
+    }
+  ]);
 
-  if (CmdAddonFlags.addonsLoaded) {
-    info("The call to AddonManager.getAllAddons in BuiltinCommands.jsm is done.");
-    info("Send the gcli_addon_commands_ready notification ourselves.");
-
-    Services.obs.notifyObservers(null, "gcli_addon_commands_ready", null);
-  } else {
-    info("Waiting for gcli_addon_commands_ready notification.");
-  }
-
-  return deferred.promise;
-};
+  yield helpers.closeToolbar(options);
+  yield helpers.closeTab(options);
+}
--- a/browser/devtools/commandline/test/browser_cmd_pagemod_export.js
+++ b/browser/devtools/commandline/test/browser_cmd_pagemod_export.js
@@ -6,17 +6,16 @@
 const TEST_URI = "http://example.com/browser/browser/devtools/commandline/"+
                  "test/browser_cmd_pagemod_export.html";
 
 function test() {
   return Task.spawn(spawnTest).then(finish, helpers.handleError);
 }
 
 function spawnTest() {
-  // Setup
   let options = yield helpers.openTab(TEST_URI);
   yield helpers.openToolbar(options);
 
   const documentElement = options.document.documentElement;
   const initialHtml = documentElement.innerHTML;
   function resetContent() {
     options.document.documentElement.innerHTML = initialHtml;
   }
@@ -26,36 +25,57 @@ function spawnTest() {
   let openURL = "";
   options.window.open = function(url) {
     // The URL is a data: URL that contains the document source
     openURL = decodeURIComponent(url);
   };
 
   yield helpers.audit(options, [
     {
-      setup: 'export html',
+      setup:    'export html',
+      skipIf: true,
       check: {
         input:  'export html',
-        hints:             '',
+        hints:             ' [destination]',
         markup: 'VVVVVVVVVVV',
-        status: 'VALID'
+        status: 'VALID',
       },
       exec: {
         output: ''
       },
       post: function() {
         isnot(openURL.indexOf('<html lang="en">'), -1, "export html works: <html>");
         isnot(openURL.indexOf("<title>GCLI"), -1, "export html works: <title>");
         isnot(openURL.indexOf('<p id="someid">#'), -1, "export html works: <p>");
-
-         options.window.open = oldOpen;
+      }
+    },
+    {
+      setup:    'export html stdout',
+      check: {
+        input:  'export html stdout',
+        hints:                    '',
+        markup: 'VVVVVVVVVVVVVVVVVV',
+        status: 'VALID',
+        args: {
+          destination: { value: "stdout" }
+        },
+      },
+      exec: {
+        output: [
+          /<html lang="en">/,
+          /<title>GCLI/,
+          /<p id="someid">#/
+        ]
       }
     }
   ]);
 
+  options.window.open = oldOpen;
+  oldOpen = undefined;
+
   // Test 'pagemod replace'
   yield helpers.audit(options, [
     {
       setup: 'pagemod replace',
       check: {
         input:  'pagemod replace',
         hints:                 ' <search> <replace> [ignoreCase] [selector] [root] [attrOnly] [contentOnly] [attributes]',
         markup: 'VVVVVVVVVVVVVVV',
deleted file mode 100644
--- a/browser/devtools/debugger/Makefile.in
+++ /dev/null
@@ -1,8 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-include $(topsrcdir)/config/rules.mk
-
-libs::
-	$(NSINSTALL) $(srcdir)/*.jsm $(FINAL_TARGET)/modules/devtools
rename from browser/devtools/debugger/CmdDebugger.jsm
rename to browser/devtools/debugger/debugger-commands.js
--- a/browser/devtools/debugger/CmdDebugger.jsm
+++ b/browser/devtools/debugger/debugger-commands.js
@@ -1,28 +1,23 @@
-/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
 "use strict";
 
-const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+const { Cc, Ci, Cu } = require("chrome");
+const gcli = require("gcli/index");
 
-this.EXPORTED_SYMBOLS = [ ];
+loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
 
-let devtools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
-var gcli = devtools.require('gcli/index');
-Cu.import('resource://gre/modules/XPCOMUtils.jsm');
-
-XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
-  "resource:///modules/devtools/gDevTools.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "console",
-  "resource://gre/modules/devtools/Console.jsm");
+/**
+ * The commands and converters that are exported to GCLI
+ */
+exports.items = [];
 
 /**
  * Utility to get access to the current breakpoint list.
  *
  * @param DebuggerPanel dbg
  *        The debugger panel.
  * @return array
  *         An array of objects, one for each breakpoint, where each breakpoint
@@ -51,36 +46,37 @@ function getAllBreakpoints(dbg) {
   }
 
   return breakpoints;
 }
 
 /**
  * 'break' command
  */
-gcli.addCommand({
+exports.items.push({
   name: "break",
   description: gcli.lookup("breakDesc"),
   manual: gcli.lookup("breakManual")
 });
 
 /**
  * 'break list' command
  */
-gcli.addCommand({
+exports.items.push({
   name: "break list",
   description: gcli.lookup("breaklistDesc"),
   returnType: "breakpoints",
   exec: function(args, context) {
     let dbg = getPanel(context, "jsdebugger", { ensureOpened: true });
     return dbg.then(getAllBreakpoints);
   }
 });
 
-gcli.addConverter({
+exports.items.push({
+  item: "converter",
   from: "breakpoints",
   to: "view",
   exec: function(breakpoints, context) {
     let dbg = getPanel(context, "jsdebugger");
     if (dbg && breakpoints.length) {
       return context.createView({
         html: breakListHtml,
         data: {
@@ -124,26 +120,26 @@ var breakListHtml = "" +
       "";
 
 var MAX_LINE_TEXT_LENGTH = 30;
 var MAX_LABEL_LENGTH = 20;
 
 /**
  * 'break add' command
  */
-gcli.addCommand({
+exports.items.push({
   name: "break add",
   description: gcli.lookup("breakaddDesc"),
   manual: gcli.lookup("breakaddManual")
 });
 
 /**
  * 'break add line' command
  */
-gcli.addCommand({
+exports.items.push({
   name: "break add line",
   description: gcli.lookup("breakaddlineDesc"),
   params: [
     {
       name: "file",
       type: {
         name: "selection",
         data: function(context) {
@@ -180,17 +176,17 @@ gcli.addCommand({
 
     return deferred.promise;
   }
 });
 
 /**
  * 'break del' command
  */
-gcli.addCommand({
+exports.items.push({
   name: "break del",
   description: gcli.lookup("breakdelDesc"),
   params: [
     {
       name: "breakpoint",
       type: {
         name: "selection",
         lookup: function(context) {
@@ -226,55 +222,55 @@ gcli.addCommand({
 
     return deferred.promise;
   }
 });
 
 /**
  * 'dbg' command
  */
-gcli.addCommand({
+exports.items.push({
   name: "dbg",
   description: gcli.lookup("dbgDesc"),
   manual: gcli.lookup("dbgManual")
 });
 
 /**
  * 'dbg open' command
  */
-gcli.addCommand({
+exports.items.push({
   name: "dbg open",
   description: gcli.lookup("dbgOpen"),
   params: [],
   exec: function(args, context) {
     let target = context.environment.target;
     return gDevTools.showToolbox(target, "jsdebugger").then(() => null);
   }
 });
 
 /**
  * 'dbg close' command
  */
-gcli.addCommand({
+exports.items.push({
   name: "dbg close",
   description: gcli.lookup("dbgClose"),
   params: [],
   exec: function(args, context) {
     if (!getPanel(context, "jsdebugger")) {
       return;
     }
     let target = context.environment.target;
     return gDevTools.closeToolbox(target).then(() => null);
   }
 });
 
 /**
  * 'dbg interrupt' command
  */
-gcli.addCommand({
+exports.items.push({
   name: "dbg interrupt",
   description: gcli.lookup("dbgInterrupt"),
   params: [],
   exec: function(args, context) {
     let dbg = getPanel(context, "jsdebugger");
     if (!dbg) {
       return gcli.lookup("debuggerStopped");
     }
@@ -285,17 +281,17 @@ gcli.addCommand({
       thread.interrupt();
     }
   }
 });
 
 /**
  * 'dbg continue' command
  */
-gcli.addCommand({
+exports.items.push({
   name: "dbg continue",
   description: gcli.lookup("dbgContinue"),
   params: [],
   exec: function(args, context) {
     let dbg = getPanel(context, "jsdebugger");
     if (!dbg) {
       return gcli.lookup("debuggerStopped");
     }
@@ -306,26 +302,26 @@ gcli.addCommand({
       thread.resume();
     }
   }
 });
 
 /**
  * 'dbg step' command
  */
-gcli.addCommand({
+exports.items.push({
   name: "dbg step",
   description: gcli.lookup("dbgStepDesc"),
   manual: gcli.lookup("dbgStepManual")
 });
 
 /**
  * 'dbg step over' command
  */
-gcli.addCommand({
+exports.items.push({
   name: "dbg step over",
   description: gcli.lookup("dbgStepOverDesc"),
   params: [],
   exec: function(args, context) {
     let dbg = getPanel(context, "jsdebugger");
     if (!dbg) {
       return gcli.lookup("debuggerStopped");
     }
@@ -336,17 +332,17 @@ gcli.addCommand({
       thread.stepOver();
     }
   }
 });
 
 /**
  * 'dbg step in' command
  */
-gcli.addCommand({
+exports.items.push({
   name: 'dbg step in',
   description: gcli.lookup("dbgStepInDesc"),
   params: [],
   exec: function(args, context) {
     let dbg = getPanel(context, "jsdebugger");
     if (!dbg) {
       return gcli.lookup("debuggerStopped");
     }
@@ -357,17 +353,17 @@ gcli.addCommand({
       thread.stepIn();
     }
   }
 });
 
 /**
  * 'dbg step over' command
  */
-gcli.addCommand({
+exports.items.push({
   name: 'dbg step out',
   description: gcli.lookup("dbgStepOutDesc"),
   params: [],
   exec: function(args, context) {
     let dbg = getPanel(context, "jsdebugger");
     if (!dbg) {
       return gcli.lookup("debuggerStopped");
     }
@@ -378,17 +374,17 @@ gcli.addCommand({
       thread.stepOut();
     }
   }
 });
 
 /**
  * 'dbg list' command
  */
-gcli.addCommand({
+exports.items.push({
   name: "dbg list",
   description: gcli.lookup("dbgListSourcesDesc"),
   params: [],
   returnType: "dom",
   exec: function(args, context) {
     let dbg = getPanel(context, "jsdebugger");
     if (!dbg) {
       return gcli.lookup("debuggerClosed");
@@ -424,17 +420,17 @@ gcli.addCommand({
     clientMethod: "unblackBox",
     l10nPrefix: "dbgUnBlackBox"
   }
 ].forEach(function(cmd) {
   const lookup = function(id) {
     return gcli.lookup(cmd.l10nPrefix + id);
   };
 
-  gcli.addCommand({
+  exports.items.push({
     name: "dbg " + cmd.name,
     description: lookup("Desc"),
     params: [
       {
         name: "source",
         type: {
           name: "selection",
           data: function(context) {
--- a/browser/devtools/debugger/moz.build
+++ b/browser/devtools/debugger/moz.build
@@ -1,13 +1,14 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 JS_MODULES_PATH = 'modules/devtools/debugger'
 
 EXTRA_JS_MODULES += [
+    'debugger-commands.js',
     'panel.js'
 ]
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
 
--- a/browser/devtools/framework/toolbox.js
+++ b/browser/devtools/framework/toolbox.js
@@ -36,20 +36,16 @@ loader.lazyGetter(this, "toolboxStrings"
       return bundle.formatStringFromName(name, args, args.length);
     } catch (ex) {
       Services.console.logStringMessage("Error reading '" + name + "'");
       return null;
     }
   };
 });
 
-loader.lazyGetter(this, "Requisition", () => {
-  return require("gcli/cli").Requisition;
-});
-
 loader.lazyGetter(this, "Selection", () => require("devtools/framework/selection").Selection);
 loader.lazyGetter(this, "InspectorFront", () => require("devtools/server/actors/inspector").InspectorFront);
 
 /**
  * A "Toolbox" is the component that holds all the tools for one specific
  * target. Visually, it's a document that includes the tools tabs and all
  * the iframes where the tool panels will be living in.
  *
@@ -534,17 +530,17 @@ Toolbox.prototype = {
     this._buildPickerButton();
 
     if (!this.target.isLocalTab) {
       return;
     }
 
     let spec = CommandUtils.getCommandbarSpec("devtools.toolbox.toolbarSpec");
     let environment = CommandUtils.createEnvironment(this, '_target');
-    this._requisition = new Requisition({ environment: environment });
+    this._requisition = CommandUtils.createRequisition(environment);
     let buttons = CommandUtils.createButtons(spec, this._target,
                                              this.doc, this._requisition);
     let container = this.doc.getElementById("toolbox-buttons");
     buttons.forEach(container.appendChild.bind(container));
     this.setToolboxButtonsVisibility();
   },
 
   /**
deleted file mode 100644
--- a/browser/devtools/inspector/Makefile.in
+++ /dev/null
@@ -1,9 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-include $(topsrcdir)/config/rules.mk
-
-libs::
-	$(NSINSTALL) $(srcdir)/*.jsm $(FINAL_TARGET)/modules/devtools/
-	$(NSINSTALL) $(srcdir)/*.js $(FINAL_TARGET)/modules/devtools/inspector
rename from browser/devtools/inspector/CmdInspect.jsm
rename to browser/devtools/inspector/inspector-commands.js
--- a/browser/devtools/inspector/CmdInspect.jsm
+++ b/browser/devtools/inspector/inspector-commands.js
@@ -1,38 +1,29 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
-this.EXPORTED_SYMBOLS = [ ];
-
-Cu.import('resource://gre/modules/XPCOMUtils.jsm');
-XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
-                                  "resource:///modules/devtools/gDevTools.jsm");
+"use strict";
 
-const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
-const gcli = devtools.require('gcli/index');
+const gcli = require("gcli/index");
 
-/**
- * 'inspect' command
- */
-gcli.addCommand({
+exports.items = [{
   name: "inspect",
   description: gcli.lookup("inspectDesc"),
   manual: gcli.lookup("inspectManual"),
   params: [
     {
       name: "selector",
       type: "node",
       description: gcli.lookup("inspectNodeDesc"),
       manual: gcli.lookup("inspectNodeManual")
     }
   ],
-  exec: function Command_inspect(args, context) {
-    let gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
-    let target = devtools.TargetFactory.forTab(gBrowser.selectedTab);
+  exec: function(args, context) {
+    let target = context.environment.target;
+    let gDevTools = require("resource:///modules/devtools/gDevTools.jsm").gDevTools;
 
-    return gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
+    return gDevTools.showToolbox(target, "inspector").then(toolbox => {
       toolbox.getCurrentPanel().selection.setNode(args.selector, "gcli");
-    }.bind(this));
+    });
   }
-});
+}];
--- a/browser/devtools/inspector/moz.build
+++ b/browser/devtools/inspector/moz.build
@@ -1,7 +1,14 @@
-# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+JS_MODULES_PATH = 'modules/devtools/inspector'
+
+EXTRA_JS_MODULES += [
+    'breadcrumbs.js',
+    'inspector-commands.js',
+    'inspector-panel.js',
+    'selector-search.js'
+]
+
 TEST_DIRS += ['test']
--- a/browser/devtools/jar.mn
+++ b/browser/devtools/jar.mn
@@ -10,16 +10,17 @@ browser.jar:
     content/browser/devtools/netmonitor.xul                            (netmonitor/netmonitor.xul)
     content/browser/devtools/netmonitor.css                            (netmonitor/netmonitor.css)
     content/browser/devtools/netmonitor-controller.js                  (netmonitor/netmonitor-controller.js)
     content/browser/devtools/netmonitor-view.js                        (netmonitor/netmonitor-view.js)
     content/browser/devtools/NetworkPanel.xhtml                        (webconsole/NetworkPanel.xhtml)
     content/browser/devtools/webconsole.xul                            (webconsole/webconsole.xul)
 *   content/browser/devtools/scratchpad.xul                            (scratchpad/scratchpad.xul)
     content/browser/devtools/scratchpad.js                             (scratchpad/scratchpad.js)
+    content/browser/devtools/scratchpad-commands.js                    (scratchpad/scratchpad-commands.js)
     content/browser/devtools/splitview.css                             (shared/splitview.css)
     content/browser/devtools/theme-switching.js                        (shared/theme-switching.js)
     content/browser/devtools/styleeditor.xul                           (styleeditor/styleeditor.xul)
     content/browser/devtools/styleeditor.css                           (styleeditor/styleeditor.css)
     content/browser/devtools/computedview.xhtml                        (styleinspector/computedview.xhtml)
     content/browser/devtools/cssruleview.xhtml                         (styleinspector/cssruleview.xhtml)
     content/browser/devtools/ruleview.css                              (styleinspector/ruleview.css)
     content/browser/devtools/layoutview/view.js                        (layoutview/view.js)
@@ -74,19 +75,21 @@ browser.jar:
     content/browser/devtools/profiler/cleopatra/js/tree.js             (profiler/cleopatra/js/tree.js)
     content/browser/devtools/profiler/cleopatra/js/ui.js               (profiler/cleopatra/js/ui.js)
     content/browser/devtools/profiler/cleopatra/js/ProgressReporter.js (profiler/cleopatra/js/ProgressReporter.js)
     content/browser/devtools/profiler/cleopatra/js/devtools.js         (profiler/cleopatra/js/devtools.js)
     content/browser/devtools/profiler/cleopatra/images/circlearrow.svg (profiler/cleopatra/images/circlearrow.svg)
     content/browser/devtools/profiler/cleopatra/images/noise.png       (profiler/cleopatra/images/noise.png)
     content/browser/devtools/profiler/cleopatra/images/throbber.svg    (profiler/cleopatra/images/throbber.svg)
     content/browser/devtools/profiler/cleopatra/images/treetwisty.svg  (profiler/cleopatra/images/treetwisty.svg)
+    content/browser/devtools/responsivedesign/resize-commands.js       (responsivedesign/resize-commands.js)
     content/browser/devtools/commandline.css                           (commandline/commandline.css)
     content/browser/devtools/commandlineoutput.xhtml                   (commandline/commandlineoutput.xhtml)
     content/browser/devtools/commandlinetooltip.xhtml                  (commandline/commandlinetooltip.xhtml)
+    content/browser/devtools/commandline/commands-index.js             (commandline/commands-index.js)
     content/browser/devtools/framework/toolbox-window.xul              (framework/toolbox-window.xul)
     content/browser/devtools/framework/toolbox-options.xul             (framework/toolbox-options.xul)
     content/browser/devtools/framework/toolbox-options.js              (framework/toolbox-options.js)
 *   content/browser/devtools/framework/toolbox.xul                     (framework/toolbox.xul)
     content/browser/devtools/framework/options-panel.css               (framework/options-panel.css)
     content/browser/devtools/framework/toolbox-process-window.xul      (framework/toolbox-process-window.xul)
     content/browser/devtools/framework/toolbox-process-window.js       (framework/toolbox-process-window.js)
     content/browser/devtools/inspector/inspector.xul                   (inspector/inspector.xul)
--- a/browser/devtools/main.js
+++ b/browser/devtools/main.js
@@ -85,16 +85,17 @@ Tools.webConsole = {
   ordinal: 1,
   icon: "chrome://browser/skin/devtools/tool-webconsole.svg",
   invertIconForLightTheme: true,
   url: "chrome://browser/content/devtools/webconsole.xul",
   label: l10n("ToolboxTabWebconsole.label", webConsoleStrings),
   menuLabel: l10n("MenuWebconsole.label", webConsoleStrings),
   tooltip: l10n("ToolboxWebconsole.tooltip", webConsoleStrings),
   inMenu: true,
+  commands: "devtools/webconsole/console-commands",
 
   preventClosingOnKey: true,
   onkey: function(panel, toolbox) {
     if (toolbox.splitConsole)
       return toolbox.focusConsoleInput();
 
     panel.focusInput();
   },
@@ -115,16 +116,20 @@ Tools.inspector = {
   ordinal: 2,
   modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift",
   icon: "chrome://browser/skin/devtools/tool-inspector.svg",
   invertIconForLightTheme: true,
   url: "chrome://browser/content/devtools/inspector/inspector.xul",
   label: l10n("inspector.label", inspectorStrings),
   tooltip: l10n("inspector.tooltip", inspectorStrings),
   inMenu: true,
+  commands: [
+    "devtools/resize-commands",
+    "devtools/inspector/inspector-commands",
+  ],
 
   preventClosingOnKey: true,
   onkey: function(panel) {
     panel.toolbox.highlighterUtils.togglePicker();
   },
 
   isTargetSupported: function(target) {
     return !target.isAddon;
@@ -144,16 +149,17 @@ Tools.jsdebugger = {
   ordinal: 3,
   icon: "chrome://browser/skin/devtools/tool-debugger.svg",
   invertIconForLightTheme: true,
   highlightedicon: "chrome://browser/skin/devtools/tool-debugger-paused.svg",
   url: "chrome://browser/content/devtools/debugger.xul",
   label: l10n("ToolboxDebugger.label", debuggerStrings),
   tooltip: l10n("ToolboxDebugger.tooltip", debuggerStrings),
   inMenu: true,
+  commands: "devtools/debugger/debugger-commands",
 
   isTargetSupported: function(target) {
     return true;
   },
 
   build: function(iframeWindow, toolbox) {
     let panel = new DebuggerPanel(iframeWindow, toolbox);
     return panel.open();
@@ -167,16 +173,17 @@ Tools.styleEditor = {
   accesskey: l10n("open.accesskey", styleEditorStrings),
   modifiers: "shift",
   icon: "chrome://browser/skin/devtools/tool-styleeditor.svg",
   invertIconForLightTheme: true,
   url: "chrome://browser/content/devtools/styleeditor.xul",
   label: l10n("ToolboxStyleEditor.label", styleEditorStrings),
   tooltip: l10n("ToolboxStyleEditor.tooltip2", styleEditorStrings),
   inMenu: true,
+  commands: "devtools/styleeditor/styleeditor-commands",
 
   isTargetSupported: function(target) {
     return !target.isAddon;
   },
 
   build: function(iframeWindow, toolbox) {
     let panel = new StyleEditorPanel(iframeWindow, toolbox);
     return panel.open();
@@ -231,16 +238,17 @@ Tools.jsprofiler = {
   modifiers: "shift",
   visibilityswitch: "devtools.profiler.enabled",
   icon: "chrome://browser/skin/devtools/tool-profiler.svg",
   invertIconForLightTheme: true,
   url: "chrome://browser/content/devtools/profiler.xul",
   label: l10n("profiler.label", profilerStrings),
   tooltip: l10n("profiler.tooltip2", profilerStrings),
   inMenu: true,
+  commands: "devtools/profiler/commands",
 
   isTargetSupported: function (target) {
     return !target.isAddon;
   },
 
   build: function (frame, target) {
     let panel = new ProfilerPanel(frame, target);
     return panel.open();
@@ -277,16 +285,17 @@ Tools.scratchpad = {
   ordinal: 9,
   visibilityswitch: "devtools.scratchpad.enabled",
   icon: "chrome://browser/skin/devtools/tool-scratchpad.svg",
   invertIconForLightTheme: true,
   url: "chrome://browser/content/devtools/scratchpad.xul",
   label: l10n("scratchpad.label", scratchpadStrings),
   tooltip: l10n("scratchpad.tooltip", scratchpadStrings),
   inMenu: false,
+  commands: "devtools/scratchpad/scratchpad-commands",
 
   isTargetSupported: function(target) {
     return !target.isAddon && target.isRemote;
   },
 
   build: function(iframeWindow, toolbox) {
     let panel = new ScratchpadPanel(iframeWindow, toolbox);
     return panel.open();
--- a/browser/devtools/profiler/commands.js
+++ b/browser/devtools/profiler/commands.js
@@ -1,181 +1,137 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-const { Cu } = require("chrome");
-module.exports = [];
+"use strict";
 
-var gcli = require('gcli/index');
+const gcli = require('gcli/index');
 
 loader.lazyGetter(this, "gDevTools",
   () => Cu.import("resource:///modules/devtools/gDevTools.jsm", {}).gDevTools);
 
-var { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
 
-/*
- * 'profiler' command. Doesn't do anything.
- */
-gcli.addCommand({
+module.exports.items = [
+{
   name: "profiler",
   description: gcli.lookup("profilerDesc"),
   manual: gcli.lookup("profilerManual")
-});
-
-/*
- * 'profiler open' command
- */
-gcli.addCommand({
+},
+{
   name: "profiler open",
   description: gcli.lookup("profilerOpenDesc"),
-  params: [],
-
   exec: function (args, context) {
     return gDevTools.showToolbox(context.environment.target, "jsprofiler")
       .then(function () null);
   }
-});
-
-/*
- * 'profiler close' command
- */
-gcli.addCommand({
+},
+{
   name: "profiler close",
   description: gcli.lookup("profilerCloseDesc"),
-  params: [],
-
   exec: function (args, context) {
-    if (!getPanel(context, "jsprofiler"))
+    let toolbox = gDevTools.getToolbox(context.environment.target);
+    let panel = (toolbox == null) ? null : toolbox.getPanel(id);
+    if (panel == null)
       return;
 
     return gDevTools.closeToolbox(context.environment.target)
       .then(function () null);
   }
-});
-
-/*
- * 'profiler start' command
- */
-gcli.addCommand({
+},
+{
   name: "profiler start",
   description: gcli.lookup("profilerStartDesc"),
   returnType: "string",
-  params: [],
-
   exec: function (args, context) {
-    function start() {
-      let panel = getPanel(context, "jsprofiler");
+    let target = context.environment.target
+    return gDevTools.showToolbox(target, "jsprofiler").then(toolbox => {
+      let panel = toolbox.getCurrentPanel();
 
       if (panel.recordingProfile)
         throw gcli.lookup("profilerAlreadyStarted2");
 
       panel.toggleRecording();
       return gcli.lookup("profilerStarted2");
-    }
-
-    return gDevTools.showToolbox(context.environment.target, "jsprofiler")
-      .then(start);
+    });
   }
-});
-
-/*
- * 'profiler stop' command
- */
-gcli.addCommand({
+},
+{
   name: "profiler stop",
   description: gcli.lookup("profilerStopDesc"),
   returnType: "string",
-  params: [],
-
   exec: function (args, context) {
-    function stop() {
-      let panel = getPanel(context, "jsprofiler");
+    let target = context.environment.target
+    return gDevTools.showToolbox(target, "jsprofiler").then(toolbox => {
+      let panel = toolbox.getCurrentPanel();
 
       if (!panel.recordingProfile)
         throw gcli.lookup("profilerNotStarted3");
 
       panel.toggleRecording();
       return gcli.lookup("profilerStopped");
-    }
-
-    return gDevTools.showToolbox(context.environment.target, "jsprofiler")
-      .then(stop);
+    });
   }
-});
-
-/*
- * 'profiler list' command
- */
-gcli.addCommand({
+},
+{
   name: "profiler list",
   description: gcli.lookup("profilerListDesc"),
-  returnType: "dom",
-  params: [],
-
+  returnType: "profileList",
   exec: function (args, context) {
-    let panel = getPanel(context, "jsprofiler");
+    let toolbox = gDevTools.getToolbox(context.environment.target);
+    let panel = (toolbox == null) ? null : toolbox.getPanel("jsprofiler");
 
-    if (!panel) {
+    if (panel == null) {
       throw gcli.lookup("profilerNotReady");
     }
 
-    let doc = panel.document;
-    let div = createXHTMLElement(doc, "div");
-    let ol = createXHTMLElement(doc, "ol");
-
-    for ([ uid, profile] of panel.profiles) {
-      let li = createXHTMLElement(doc, "li");
-      li.textContent = profile.name;
-      if (profile.isStarted) {
-        li.textContent += " *";
-      }
-      ol.appendChild(li);
+    let profileList = [];
+    for ([ uid, profile ] of panel.profiles) {
+      profileList.push({ name: profile.name, started: profile.isStarted });
     }
-
-    div.appendChild(ol);
-    return div;
+    return profileList;
   }
-});
-
-/*
- * 'profiler show' command
- */
-gcli.addCommand({
+},
+{
+  item: "converter",
+  from: "profileList",
+  to: "view",
+  exec: function(profileList, context) {
+    return {
+      html: "<div>" +
+            "  <ol>" +
+            "    <li forEach='profile of ${profiles}'>${profile.name}</li>" +
+            "      ${profile.name} ${profile.started ? '*' : ''}" +
+            "    </li>" +
+            "  </ol>" +
+            "</div>",
+      data: { profiles: profileList.profiles },
+      options: { allowEval: true }
+    };
+  },
+},
+{
   name: "profiler show",
   description: gcli.lookup("profilerShowDesc"),
-
   params: [
     {
       name: "name",
       type: "string",
       manual: gcli.lookup("profilerShowManual")
     }
   ],
 
   exec: function (args, context) {
-    let panel = getPanel(context, "jsprofiler");
+    let toolbox = gDevTools.getToolbox(context.environment.target);
+    let panel = (toolbox == null) ? null : toolbox.getPanel(id);
 
     if (!panel) {
       throw gcli.lookup("profilerNotReady");
     }
 
     let profile = panel.getProfileByName(args.name);
     if (!profile) {
       throw gcli.lookup("profilerNotFound");
     }
 
     panel.sidebar.selectedItem = panel.sidebar.getItemByProfile(profile);
   }
-});
-
-function getPanel(context, id) {
-  if (context == null) {
-    return undefined;
-  }
-
-  let toolbox = gDevTools.getToolbox(context.environment.target);
-  return toolbox == null ? undefined : toolbox.getPanel(id);
-}
-
-function createXHTMLElement(document, tagname) {
-  return document.createElementNS("http://www.w3.org/1999/xhtml", tagname);
-}
+}];
--- a/browser/devtools/responsivedesign/moz.build
+++ b/browser/devtools/responsivedesign/moz.build
@@ -1,8 +1,11 @@
-# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+JS_MODULES_PATH = 'modules/devtools'
+
+EXTRA_JS_MODULES += [
+    'resize-commands.js'
+]
+
 TEST_DIRS += ['test']
-
rename from browser/devtools/responsivedesign/CmdResize.jsm
rename to browser/devtools/responsivedesign/resize-commands.js
--- a/browser/devtools/responsivedesign/CmdResize.jsm
+++ b/browser/devtools/responsivedesign/resize-commands.js
@@ -1,94 +1,89 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+"use strict";
+
+const { Cc, Ci, Cu } = require("chrome");
 
 const BRAND_SHORT_NAME = Cc["@mozilla.org/intl/stringbundle;1"].
                          getService(Ci.nsIStringBundleService).
                          createBundle("chrome://branding/locale/brand.properties").
                          GetStringFromName("brandShortName");
 
-this.EXPORTED_SYMBOLS = [ ];
-
-const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
-const gcli = devtools.require("gcli/index");
-
-/* Responsive Mode commands */
-gcli.addCommand({
-  name: 'resize',
-  description: gcli.lookup('resizeModeDesc')
-});
-
-gcli.addCommand({
-  name: 'resize on',
-  description: gcli.lookup('resizeModeOnDesc'),
-  manual: gcli.lookupFormat('resizeModeManual2', [BRAND_SHORT_NAME]),
-  exec: gcli_cmd_resize
-});
+const gcli = require("gcli/index");
 
-gcli.addCommand({
-  name: 'resize off',
-  description: gcli.lookup('resizeModeOffDesc'),
-  manual: gcli.lookupFormat('resizeModeManual2', [BRAND_SHORT_NAME]),
-  exec: gcli_cmd_resize
-});
-
-gcli.addCommand({
-  name: 'resize toggle',
-  buttonId: "command-button-responsive",
-  buttonClass: "command-button command-button-invertable",
-  tooltipText: gcli.lookup("resizeModeToggleTooltip"),
-  description: gcli.lookup('resizeModeToggleDesc'),
-  manual: gcli.lookupFormat('resizeModeManual2', [BRAND_SHORT_NAME]),
-  state: {
-    isChecked: function(aTarget) {
-      let browserWindow = aTarget.tab.ownerDocument.defaultView;
-      let mgr = browserWindow.ResponsiveUI.ResponsiveUIManager;
-      return mgr.isActiveForTab(aTarget.tab);
-    },
-    onChange: function(aTarget, aChangeHandler) {
-      let browserWindow = aTarget.tab.ownerDocument.defaultView;
-      let mgr = browserWindow.ResponsiveUI.ResponsiveUIManager;
-      mgr.on("on", aChangeHandler);
-      mgr.on("off", aChangeHandler);
-    },
-    offChange: function(aTarget, aChangeHandler) {
-      if (aTarget.tab) {
+exports.items = [
+  {
+    name: 'resize',
+    description: gcli.lookup('resizeModeDesc')
+  },
+  {
+    name: 'resize on',
+    description: gcli.lookup('resizeModeOnDesc'),
+    manual: gcli.lookupFormat('resizeModeManual2', [BRAND_SHORT_NAME]),
+    exec: gcli_cmd_resize
+  },
+  {
+    name: 'resize off',
+    description: gcli.lookup('resizeModeOffDesc'),
+    manual: gcli.lookupFormat('resizeModeManual2', [BRAND_SHORT_NAME]),
+    exec: gcli_cmd_resize
+  },
+  {
+    name: 'resize toggle',
+    buttonId: "command-button-responsive",
+    buttonClass: "command-button command-button-invertable",
+    tooltipText: gcli.lookup("resizeModeToggleTooltip"),
+    description: gcli.lookup('resizeModeToggleDesc'),
+    manual: gcli.lookupFormat('resizeModeManual2', [BRAND_SHORT_NAME]),
+    state: {
+      isChecked: function(aTarget) {
+        let browserWindow = aTarget.tab.ownerDocument.defaultView;
+        let mgr = browserWindow.ResponsiveUI.ResponsiveUIManager;
+        return mgr.isActiveForTab(aTarget.tab);
+      },
+      onChange: function(aTarget, aChangeHandler) {
         let browserWindow = aTarget.tab.ownerDocument.defaultView;
         let mgr = browserWindow.ResponsiveUI.ResponsiveUIManager;
-        mgr.off("on", aChangeHandler);
-        mgr.off("off", aChangeHandler);
-      }
+        mgr.on("on", aChangeHandler);
+        mgr.on("off", aChangeHandler);
+      },
+      offChange: function(aTarget, aChangeHandler) {
+        if (aTarget.tab) {
+          let browserWindow = aTarget.tab.ownerDocument.defaultView;
+          let mgr = browserWindow.ResponsiveUI.ResponsiveUIManager;
+          mgr.off("on", aChangeHandler);
+          mgr.off("off", aChangeHandler);
+        }
+      },
     },
+    exec: gcli_cmd_resize
   },
-  exec: gcli_cmd_resize
-});
-
-gcli.addCommand({
-  name: 'resize to',
-  description: gcli.lookup('resizeModeToDesc'),
-  params: [
-    {
-      name: 'width',
-      type: 'number',
-      description: gcli.lookup("resizePageArgWidthDesc"),
-    },
-    {
-      name: 'height',
-      type: 'number',
-      description: gcli.lookup("resizePageArgHeightDesc"),
-    },
-  ],
-  exec: gcli_cmd_resize
-});
+  {
+    name: 'resize to',
+    description: gcli.lookup('resizeModeToDesc'),
+    params: [
+      {
+        name: 'width',
+        type: 'number',
+        description: gcli.lookup("resizePageArgWidthDesc"),
+      },
+      {
+        name: 'height',
+        type: 'number',
+        description: gcli.lookup("resizePageArgHeightDesc"),
+      },
+    ],
+    exec: gcli_cmd_resize
+  }
+];
 
 function gcli_cmd_resize(args, context) {
-  let browserDoc = context.environment.chromeDocument;
-  let browserWindow = browserDoc.defaultView;
+  let browserWindow = context.environment.chromeWindow;
   let mgr = browserWindow.ResponsiveUI.ResponsiveUIManager;
   mgr.handleGcliCommand(browserWindow,
                         browserWindow.gBrowser.selectedTab,
                         this.name,
                         args);
 }
--- a/browser/devtools/scratchpad/moz.build
+++ b/browser/devtools/scratchpad/moz.build
@@ -4,10 +4,11 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 TEST_DIRS += ['test']
 
 JS_MODULES_PATH = 'modules/devtools/scratchpad'
 
 EXTRA_JS_MODULES += [
+    'scratchpad-commands.js',
     'scratchpad-panel.js'
 ]
rename from browser/devtools/scratchpad/CmdScratchpad.jsm
rename to browser/devtools/scratchpad/scratchpad-commands.js
--- a/browser/devtools/scratchpad/CmdScratchpad.jsm
+++ b/browser/devtools/scratchpad/scratchpad-commands.js
@@ -1,23 +1,19 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-this.EXPORTED_SYMBOLS = [ ];
-
-const { devtools } = Components.utils.import("resource://gre/modules/devtools/Loader.jsm", {});
-const gcli = devtools.require("gcli/index");
+"use strict";
 
-/**
- * 'scratchpad' command
- */
-gcli.addCommand({
+const gcli = require("gcli/index");
+
+exports.items = [{
   name: "scratchpad",
   buttonId: "command-button-scratchpad",
   buttonClass: "command-button command-button-invertable",
   tooltipText: gcli.lookup("scratchpadOpenTooltip"),
   hidden: true,
   exec: function(args, context) {
-    let chromeWindow = context.environment.chromeDocument.defaultView;
-    chromeWindow.Scratchpad.ScratchpadManager.openScratchpad();
+    let Scratchpad = context.environment.chromeWindow.Scratchpad;
+    Scratchpad.ScratchpadManager.openScratchpad();
   }
-});
+}];
--- a/browser/devtools/shared/DeveloperToolbar.jsm
+++ b/browser/devtools/shared/DeveloperToolbar.jsm
@@ -7,28 +7,24 @@
 this.EXPORTED_SYMBOLS = [ "DeveloperToolbar", "CommandUtils" ];
 
 const NS_XHTML = "http://www.w3.org/1999/xhtml";
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource:///modules/devtools/Commands.jsm");
 
 const { require, TargetFactory } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
 
 const Node = Ci.nsIDOMNode;
 
 XPCOMUtils.defineLazyModuleGetter(this, "console",
                                   "resource://gre/modules/devtools/Console.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "CmdCommands",
-                                  "resource:///modules/devtools/BuiltinCommands.jsm");
-
 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
                                   "resource://gre/modules/PluralForm.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
                                   "resource://gre/modules/devtools/event-emitter.js");
 
 XPCOMUtils.defineLazyGetter(this, "prefBranch", function() {
   let prefService = Cc["@mozilla.org/preferences-service;1"]
@@ -38,33 +34,48 @@ XPCOMUtils.defineLazyGetter(this, "prefB
 });
 
 XPCOMUtils.defineLazyGetter(this, "toolboxStrings", function () {
   return Services.strings.createBundle("chrome://browser/locale/devtools/toolbox.properties");
 });
 
 const Telemetry = require("devtools/shared/telemetry");
 
-XPCOMUtils.defineLazyGetter(this, "gcli", () => require("gcli/index"));
+// This lazy getter is needed to prevent a require loop
+XPCOMUtils.defineLazyGetter(this, "gcli", () => {
+  let gcli = require("gcli/index");
+  require("devtools/commandline/commands-index");
+  gcli.load();
+  return gcli;
+});
 
 Object.defineProperty(this, "ConsoleServiceListener", {
   get: function() {
     return require("devtools/toolkit/webconsole/utils").ConsoleServiceListener;
   },
   configurable: true,
   enumerable: true
 });
 
 const promise = Cu.import('resource://gre/modules/Promise.jsm', {}).Promise;
 
 /**
  * A collection of utilities to help working with commands
  */
 let CommandUtils = {
   /**
+   * Utility to ensure that things are loaded in the correct order
+   */
+  createRequisition: function(environment) {
+    let temp = gcli.createDisplay; // Ensure GCLI is loaded
+    let Requisition = require("gcli/cli").Requisition
+    return new Requisition({ environment: environment });
+  },
+
+  /**
    * Read a toolbarSpec from preferences
    * @param pref The name of the preference to read
    */
   getCommandbarSpec: function(pref) {
     let value = prefBranch.getComplexValue(pref, Ci.nsISupportsString).data;
     return JSON.parse(value);
   },
 
@@ -344,23 +355,16 @@ DeveloperToolbar.prototype.show = functi
   if (this._showPromise != null) {
     return this._showPromise;
   }
 
   // hide() is async, so ensure we don't need to wait for hide() to finish
   var waitPromise = this._hidePromise || promise.resolve();
 
   this._showPromise = waitPromise.then(() => {
-    try {
-      CmdCommands.refreshAutoCommands(this._chromeWindow);
-    }
-    catch (ex) {
-      console.error(ex);
-    }
-
     Services.prefs.setBoolPref("devtools.toolbar.visible", true);
 
     this._telemetry.toolOpened("developertoolbar");
 
     this._notify(NOTIFICATIONS.LOAD);
 
     this._input = this._doc.querySelector(".gclitoolbar-input-node");
 
rename from browser/devtools/styleeditor/CmdEdit.jsm
rename to browser/devtools/styleeditor/styleeditor-commands.js
--- a/browser/devtools/styleeditor/CmdEdit.jsm
+++ b/browser/devtools/styleeditor/styleeditor-commands.js
@@ -1,27 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
-
-this.EXPORTED_SYMBOLS = [ ];
-
-let devtools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
-const gcli = devtools.require("gcli/index");
+"use strict";
 
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
-                                  "resource:///modules/devtools/gDevTools.jsm");
+const gcli = require("gcli/index");
 
-/**
- * 'edit' command
- */
-gcli.addCommand({
+exports.items = [{
   name: "edit",
   description: gcli.lookup("editDesc"),
   manual: gcli.lookup("editManual2"),
   params: [
      {
        name: 'resource',
        type: {
          name: 'resource',
@@ -37,15 +27,16 @@ gcli.addCommand({
          min: 1,
          step: 10
        },
        description: gcli.lookup("editLineToJumpToDesc")
      }
    ],
    exec: function(args, context) {
      let target = context.environment.target;
+     let gDevTools = require("resource:///modules/devtools/gDevTools.jsm").gDevTools;
      return gDevTools.showToolbox(target, "styleeditor").then(function(toolbox) {
        let styleEditor = toolbox.getCurrentPanel();
        styleEditor.selectStyleSheet(args.resource.element, args.line);
        return null;
      });
    }
-});
+}];
deleted file mode 100644
--- a/browser/devtools/tilt/Makefile.in
+++ /dev/null
@@ -1,9 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-include $(topsrcdir)/config/rules.mk
-
-libs::
-	$(NSINSTALL) $(srcdir)/*.jsm $(FINAL_TARGET)/modules/devtools
-	$(NSINSTALL) $(srcdir)/*.js $(FINAL_TARGET)/modules/devtools/tilt
--- a/browser/devtools/tilt/moz.build
+++ b/browser/devtools/tilt/moz.build
@@ -1,8 +1,19 @@
-# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+JS_MODULES_PATH = 'modules/devtools/tilt'
+
+EXTRA_JS_MODULES += [
+    'tilt-commands.js',
+    'tilt-gl.js',
+    'tilt-math.js',
+    'tilt-utils.js',
+    'tilt-visualizer-style.js',
+    'tilt-visualizer.js',
+    'tilt.js',
+    'TiltWorkerCrafter.js',
+    'TiltWorkerPicker.js'
+]
+
 TEST_DIRS += ['test']
-
rename from browser/devtools/tilt/CmdTilt.jsm
rename to browser/devtools/tilt/tilt-commands.js
--- a/browser/devtools/tilt/CmdTilt.jsm
+++ b/browser/devtools/tilt/tilt-commands.js
@@ -1,62 +1,45 @@
-/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
 "use strict";
 
-this.EXPORTED_SYMBOLS = [ ];
-
-Components.utils.import('resource://gre/modules/XPCOMUtils.jsm');
-const { devtools } = Components.utils.import("resource://gre/modules/devtools/Loader.jsm", {});
-const gcli = devtools.require("gcli/index");
+const gcli = require("gcli/index");
 
 // Fetch TiltManager using the current loader, but don't save a
 // reference to it, because it might change with a tool reload.
 // We can clean this up once the command line is loadered.
 Object.defineProperty(this, "TiltManager", {
   get: function() {
-    return devtools.require("devtools/tilt/tilt").TiltManager;
+    return require("devtools/tilt/tilt").TiltManager;
   },
   enumerable: true
 });
 
-/**
- * 'tilt' command
- */
-gcli.addCommand({
+exports.items = [
+{
   name: 'tilt',
   description: gcli.lookup("tiltDesc"),
   manual: gcli.lookup("tiltManual")
-});
-
-
-/**
- * 'tilt open' command
- */
-gcli.addCommand({
+},
+{
   name: 'tilt open',
   description: gcli.lookup("tiltOpenDesc"),
   manual: gcli.lookup("tiltOpenManual"),
   exec: function(args, context) {
     let chromeWindow = context.environment.chromeDocument.defaultView;
     let Tilt = TiltManager.getTiltForBrowser(chromeWindow);
     if (!Tilt.currentInstance) {
       Tilt.toggle();
     }
   }
-});
-
-
-/**
- * 'tilt toggle' command
- */
-gcli.addCommand({
+},
+{
   name: "tilt toggle",
   buttonId: "command-button-tilt",
   buttonClass: "command-button command-button-invertable",
   tooltipText: gcli.lookup("tiltToggleTooltip"),
   hidden: true,
   state: {
     isChecked: function(aTarget) {
       let browserWindow = aTarget.tab.ownerDocument.defaultView;
@@ -75,23 +58,18 @@ gcli.addCommand({
       }
     },
   },
   exec: function(args, context) {
     let chromeWindow = context.environment.chromeDocument.defaultView;
     let Tilt = TiltManager.getTiltForBrowser(chromeWindow);
     Tilt.toggle();
   }
-});
-
-
-/**
- * 'tilt translate' command
- */
-gcli.addCommand({
+},
+{
   name: 'tilt translate',
   description: gcli.lookup("tiltTranslateDesc"),
   manual: gcli.lookup("tiltTranslateManual"),
   params: [
     {
       name: "x",
       type: "number",
       defaultValue: 0,
@@ -108,23 +86,18 @@ gcli.addCommand({
   ],
   exec: function(args, context) {
     let chromeWindow = context.environment.chromeDocument.defaultView;
     let Tilt = TiltManager.getTiltForBrowser(chromeWindow);
     if (Tilt.currentInstance) {
       Tilt.currentInstance.controller.arcball.translate([args.x, args.y]);
     }
   }
-});
-
-
-/**
- * 'tilt rotate' command
- */
-gcli.addCommand({
+},
+{
   name: 'tilt rotate',
   description: gcli.lookup("tiltRotateDesc"),
   manual: gcli.lookup("tiltRotateManual"),
   params: [
     {
       name: "x",
       type: { name: 'number', min: -360, max: 360, step: 10 },
       defaultValue: 0,
@@ -148,23 +121,18 @@ gcli.addCommand({
   ],
   exec: function(args, context) {
     let chromeWindow = context.environment.chromeDocument.defaultView;
     let Tilt = TiltManager.getTiltForBrowser(chromeWindow);
     if (Tilt.currentInstance) {
       Tilt.currentInstance.controller.arcball.rotate([args.x, args.y, args.z]);
     }
   }
-});
-
-
-/**
- * 'tilt zoom' command
- */
-gcli.addCommand({
+},
+{
   name: 'tilt zoom',
   description: gcli.lookup("tiltZoomDesc"),
   manual: gcli.lookup("tiltZoomManual"),
   params: [
     {
       name: "zoom",
       type: { name: 'number' },
       description: gcli.lookup("tiltZoomAmountDesc"),
@@ -174,43 +142,34 @@ gcli.addCommand({
   exec: function(args, context) {
     let chromeWindow = context.environment.chromeDocument.defaultView;
     let Tilt = TiltManager.getTiltForBrowser(chromeWindow);
 
     if (Tilt.currentInstance) {
       Tilt.currentInstance.controller.arcball.zoom(-args.zoom);
     }
   }
-});
-
-
-/**
- * 'tilt reset' command
- */
-gcli.addCommand({
+},
+{
   name: 'tilt reset',
   description: gcli.lookup("tiltResetDesc"),
   manual: gcli.lookup("tiltResetManual"),
   exec: function(args, context) {
     let chromeWindow = context.environment.chromeDocument.defaultView;
     let Tilt = TiltManager.getTiltForBrowser(chromeWindow);
 
     if (Tilt.currentInstance) {
       Tilt.currentInstance.controller.arcball.reset();
     }
   }
-});
-
-
-/**
- * 'tilt close' command
- */
-gcli.addCommand({
+},
+{
   name: 'tilt close',
   description: gcli.lookup("tiltCloseDesc"),
   manual: gcli.lookup("tiltCloseManual"),
   exec: function(args, context) {
     let chromeWindow = context.environment.chromeDocument.defaultView;
     let Tilt = TiltManager.getTiltForBrowser(chromeWindow);
 
     Tilt.destroy(Tilt.currentWindowId);
   }
-});
+}
+];
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/console-commands.js
@@ -0,0 +1,99 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const gcli = require("gcli/index");
+const EventEmitter = require("devtools/toolkit/event-emitter");
+const { gDevTools } = require("resource:///modules/devtools/gDevTools.jsm");
+
+const eventEmitter = new EventEmitter();
+
+gDevTools.on("toolbox-ready", (e, toolbox) => {
+  if (!toolbox.target) {
+    return;
+  }
+
+  let fireChangeForTab = () => {
+    eventEmitter.emit("changed", toolbox.target.tab);
+  };
+
+  toolbox.on("split-console", fireChangeForTab);
+  toolbox.on("select", fireChangeForTab);
+
+  toolbox.once("destroyed", () => {
+    toolbox.off("split-console", fireChangeForTab);
+    toolbox.off("select", fireChangeForTab);
+  });
+});
+
+exports.items = [
+  {
+    name: 'splitconsole',
+    hidden: true,
+    buttonId: "command-button-splitconsole",
+    buttonClass: "command-button command-button-invertable",
+    tooltipText: gcli.lookup("splitconsoleTooltip"),
+    state: {
+      isChecked: function(target) {
+        let toolbox = gDevTools.getToolbox(target);
+        return toolbox && toolbox.splitConsole;
+      },
+      onChange: function(target, changeHandler) {
+        eventEmitter.on("changed", changeHandler);
+      },
+      offChange: function(target, changeHandler) {
+        eventEmitter.off("changed", changeHandler);
+      },
+    },
+    exec: function(args, context) {
+      let target = context.environment.target;
+      let toolbox = gDevTools.getToolbox(target);
+
+      if (!toolbox) {
+        return gDevTools.showToolbox(target, "inspector").then((toolbox) => {
+          toolbox.toggleSplitConsole();
+        });
+      } else {
+        toolbox.toggleSplitConsole();
+      }
+    }
+  },
+  {
+    name: "console",
+    description: gcli.lookup("consoleDesc"),
+    manual: gcli.lookup("consoleManual")
+  },
+  {
+    name: "console clear",
+    description: gcli.lookup("consoleclearDesc"),
+    exec: function(args, context) {
+      let toolbox = gDevTools.getToolbox(context.environment.target);
+      if (toolbox == null) {
+        return;
+      }
+
+      let panel = toolbox.getPanel("webconsole");
+      if (panel == null) {
+        return;
+      }
+
+      panel.hud.jsterm.clearOutput();
+    }
+  },
+  {
+    name: "console close",
+    description: gcli.lookup("consolecloseDesc"),
+    exec: function(args, context) {
+      return gDevTools.closeToolbox(context.environment.target);
+    }
+  },
+  {
+    name: "console open",
+    description: gcli.lookup("consoleopenDesc"),
+    exec: function(args, context) {
+      return gDevTools.showToolbox(context.environment.target, "webconsole");
+    }
+  }
+];
--- a/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
@@ -557,20 +557,19 @@ resizeModeManual2=Responsive websites re
 # name, which is why it should be as short as possible.
 cmdDesc=Manipulate the commands
 
 # LOCALIZATION NOTE (cmdRefreshDesc) A very short description of the 'cmd refresh'
 # command. This string is designed to be shown in a menu alongside the command
 # name, which is why it should be as short as possible.
 cmdRefreshDesc=Re-read mozcmd directory
 
-# LOCALIZATION NOTE (cmdStatus) When the we load new commands from mozcmd
-# directory, we report on how many we loaded. %1$S is a count of the number
-# of loaded commands, and %2$S is the directory we loaded from.
-cmdStatus=Read %1$S commands from '%2$S'
+# LOCALIZATION NOTE (cmdStatus2) When the we load new commands from mozcmd
+# directory, we report where we loaded from using %1$S.
+cmdStatus2=Read commands from '%1$S'
 
 # LOCALIZATION NOTE (cmdSetdirDesc) 
 cmdSetdirDesc=Setup a mozcmd directory
 
 # LOCALIZATION NOTE (cmdSetdirManual) 
 cmdSetdirManual=A 'mozcmd' directory is an easy way to create new custom commands for the Firefox command line. For more information see the <a href="https://developer.mozilla.org/en-US/docs/Tools/GCLI/Customization">MDN documentation</a>.
 
 # LOCALIZATION NOTE (cmdSetdirDirectoryDesc) The description of the directory
--- a/toolkit/devtools/gcli/Makefile.in
+++ b/toolkit/devtools/gcli/Makefile.in
@@ -1,12 +1,16 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+gcli_commands_internal_FILES = $(wildcard $(srcdir)/commands/*)
+gcli_commands_internal_DEST = $(FINAL_TARGET)/modules/devtools/gcli/commands
+INSTALL_TARGETS += gcli_commands_internal
+
 gcli_commands_FILES = $(wildcard $(srcdir)/source/lib/gcli/commands/*)
 gcli_commands_DEST = $(FINAL_TARGET)/modules/devtools/gcli/commands
 INSTALL_TARGETS += gcli_commands
 
 gcli_connectors_FILES = $(wildcard $(srcdir)/source/lib/gcli/connectors/*)
 gcli_connectors_DEST = $(FINAL_TARGET)/modules/devtools/gcli/connectors
 INSTALL_TARGETS += gcli_connectors
 
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/gcli/commands/addon.js
@@ -0,0 +1,256 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Cc, Ci, Cu } = require("chrome");
+const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
+const gcli = require("gcli/index");
+const { Promise: promise } = require("resource://gre/modules/Promise.jsm");
+
+const BRAND_SHORT_NAME = Cc["@mozilla.org/intl/stringbundle;1"]
+                           .getService(Ci.nsIStringBundleService)
+                           .createBundle("chrome://branding/locale/brand.properties")
+                           .GetStringFromName("brandShortName");
+
+/**
+ * Takes a function that uses a callback as its last parameter, and returns a
+ * new function that returns a promise instead.
+ * This should probably live in async-util
+ */
+const promiseify = function(scope, functionWithLastParamCallback) {
+  return (...args) => {
+    return new Promise(resolve => {
+      args.push((...results) => {
+        resolve(results.length > 1 ? results : results[0]);
+      });
+      functionWithLastParamCallback.apply(scope, args);
+    });
+  }
+};
+
+// Convert callback based functions to promise based ones
+const getAllAddons = promiseify(AddonManager, AddonManager.getAllAddons);
+const getAddonsByTypes = promiseify(AddonManager, AddonManager.getAddonsByTypes);
+
+/**
+ * Return a string array containing the pending operations on an addon
+ */
+function pendingOperations(addon) {
+  let allOperations = [
+    "PENDING_ENABLE", "PENDING_DISABLE", "PENDING_UNINSTALL",
+    "PENDING_INSTALL", "PENDING_UPGRADE"
+  ];
+  return allOperations.reduce(function(operations, opName) {
+    return addon.pendingOperations & AddonManager[opName] ?
+      operations.concat(opName) :
+      operations;
+  }, []);
+}
+
+exports.items = [
+  {
+    item: "type",
+    name: "addon",
+    parent: "selection",
+    stringifyProperty: "name",
+    cacheable: true,
+    constructor: function() {
+      // Tell GCLI to clear the cache of addons when one is added or removed
+      let listener = {
+        onInstalled: addon => { this.clearCache(); },
+        onUninstalled: addon => { this.clearCache(); },
+      };
+      AddonManager.addAddonListener(listener);
+    },
+    lookup: function() {
+      return getAllAddons().then(addons => {
+        return addons.map(addon => {
+          let name = addon.name + " " + addon.version;
+          name = name.trim().replace(/\s/g, "_");
+          return { name: name, value: addon };
+        });
+      });
+    }
+  },
+  {
+    name: "addon",
+    description: gcli.lookup("addonDesc")
+  },
+  {
+    name: "addon list",
+    description: gcli.lookup("addonListDesc"),
+    returnType: "addonsInfo",
+    params: [{
+      name: "type",
+      type: {
+        name: "selection",
+        data: [ "dictionary", "extension", "locale", "plugin", "theme", "all" ]
+      },
+      defaultValue: "all",
+      description: gcli.lookup("addonListTypeDesc")
+    }],
+    exec: function(args, context) {
+      let types = (args.type === "all") ? null : [ args.type ];
+      return getAddonsByTypes(types).then(addons => {
+        addons = addons.map(function(addon) {
+          return {
+            name: addon.name,
+            version: addon.version,
+            isActive: addon.isActive,
+            pendingOperations: pendingOperations(addon)
+          };
+        });
+        return { addons: addons, type: args.type };
+      });
+    }
+  },
+  {
+    item: "converter",
+    from: "addonsInfo",
+    to: "view",
+    exec: function(addonsInfo, context) {
+      if (!addonsInfo.addons.length) {
+        return context.createView({
+          html: "<p>${message}</p>",
+          data: { message: gcli.lookup("addonNoneOfType") }
+        });
+      }
+
+      let headerLookups = {
+        "dictionary": "addonListDictionaryHeading",
+        "extension": "addonListExtensionHeading",
+        "locale": "addonListLocaleHeading",
+        "plugin": "addonListPluginHeading",
+        "theme": "addonListThemeHeading",
+        "all": "addonListAllHeading"
+      };
+      let header = gcli.lookup(headerLookups[addonsInfo.type] ||
+                               "addonListUnknownHeading");
+
+      let operationLookups = {
+        "PENDING_ENABLE": "addonPendingEnable",
+        "PENDING_DISABLE": "addonPendingDisable",
+        "PENDING_UNINSTALL": "addonPendingUninstall",
+        "PENDING_INSTALL": "addonPendingInstall",
+        "PENDING_UPGRADE": "addonPendingUpgrade"
+      };
+      function lookupOperation(opName) {
+        let lookupName = operationLookups[opName];
+        return lookupName ? gcli.lookup(lookupName) : opName;
+      }
+
+      function arrangeAddons(addons) {
+        let enabledAddons = [];
+        let disabledAddons = [];
+        addons.forEach(function(addon) {
+          if (addon.isActive) {
+            enabledAddons.push(addon);
+          } else {
+            disabledAddons.push(addon);
+          }
+        });
+
+        function compareAddonNames(nameA, nameB) {
+          return String.localeCompare(nameA.name, nameB.name);
+        }
+        enabledAddons.sort(compareAddonNames);
+        disabledAddons.sort(compareAddonNames);
+
+        return enabledAddons.concat(disabledAddons);
+      }
+
+      function isActiveForToggle(addon) {
+        return (addon.isActive && ~~addon.pendingOperations.indexOf("PENDING_DISABLE"));
+      }
+
+      return context.createView({
+        html:
+          "<table>" +
+          " <caption>${header}</caption>" +
+          " <tbody>" +
+          "  <tr foreach='addon in ${addons}'" +
+          "      class=\"gcli-addon-${addon.status}\">" +
+          "    <td>${addon.name} ${addon.version}</td>" +
+          "    <td>${addon.pendingOperations}</td>" +
+          "    <td>" +
+          "      <span class='gcli-out-shortcut'" +
+          "          data-command='addon ${addon.toggleActionName} ${addon.label}'" +
+          "          onclick='${onclick}' ondblclick='${ondblclick}'" +
+          "      >${addon.toggleActionMessage}</span>" +
+          "    </td>" +
+          "  </tr>" +
+          " </tbody>" +
+          "</table>",
+        data: {
+          header: header,
+          addons: arrangeAddons(addonsInfo.addons).map(function(addon) {
+            return {
+              name: addon.name,
+              label: addon.name.replace(/\s/g, "_") +
+                    (addon.version ? "_" + addon.version : ""),
+              status: addon.isActive ? "enabled" : "disabled",
+              version: addon.version,
+              pendingOperations: addon.pendingOperations.length ?
+                (" (" + gcli.lookup("addonPending") + ": "
+                 + addon.pendingOperations.map(lookupOperation).join(", ")
+                 + ")") :
+                "",
+              toggleActionName: isActiveForToggle(addon) ? "disable": "enable",
+              toggleActionMessage: isActiveForToggle(addon) ?
+                gcli.lookup("addonListOutDisable") :
+                gcli.lookup("addonListOutEnable")
+            };
+          }),
+          onclick: context.update,
+          ondblclick: context.updateExec
+        }
+      });
+    }
+  },
+  {
+    name: "addon enable",
+    description: gcli.lookup("addonEnableDesc"),
+    params: [
+      {
+        name: "addon",
+        type: "addon",
+        description: gcli.lookup("addonNameDesc")
+      }
+    ],
+    exec: function(args, context) {
+      let name = (args.addon.name + " " + args.addon.version).trim();
+      if (args.addon.userDisabled) {
+        args.addon.userDisabled = false;
+        return gcli.lookupFormat("addonEnabled", [ name ]);
+      }
+
+      return gcli.lookupFormat("addonAlreadyEnabled", [ name ]);
+    }
+  },
+  {
+    name: "addon disable",
+    description: gcli.lookup("addonDisableDesc"),
+    params: [
+      {
+        name: "addon",
+        type: "addon",
+        description: gcli.lookup("addonNameDesc")
+      }
+    ],
+    exec: function(args, context) {
+      // If the addon is not disabled or is set to "click to play" then
+      // disable it. Otherwise display the message "Add-on is already
+      // disabled."
+      let name = (args.addon.name + " " + args.addon.version).trim();
+      if (!args.addon.userDisabled ||
+          args.addon.userDisabled === AddonManager.STATE_ASK_TO_ACTIVATE) {
+        args.addon.userDisabled = true;
+        return gcli.lookupFormat("addonDisabled", [ name ]);
+      }
+
+      return gcli.lookupFormat("addonAlreadyDisabled", [ name ]);
+    }
+  }
+];
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/gcli/commands/appcache.js
@@ -0,0 +1,177 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const gcli = require("gcli/index");
+
+loader.lazyImporter(this, "AppCacheUtils", "resource:///modules/devtools/AppCacheUtils.jsm");
+
+exports.items = [
+  {
+    name: "appcache",
+    description: gcli.lookup("appCacheDesc")
+  },
+  {
+    name: "appcache validate",
+    description: gcli.lookup("appCacheValidateDesc"),
+    manual: gcli.lookup("appCacheValidateManual"),
+    returnType: "appcacheerrors",
+    params: [{
+      group: "options",
+      params: [
+        {
+          type: "string",
+          name: "uri",
+          description: gcli.lookup("appCacheValidateUriDesc"),
+          defaultValue: null,
+        }
+      ]
+    }],
+    exec: function(args, context) {
+      let utils;
+      let deferred = context.defer();
+
+      if (args.uri) {
+        utils = new AppCacheUtils(args.uri);
+      } else {
+        utils = new AppCacheUtils(context.environment.document);
+      }
+
+      utils.validateManifest().then(function(errors) {
+        deferred.resolve([errors, utils.manifestURI || "-"]);
+      });
+
+      return deferred.promise;
+    }
+  },
+  {
+    item: "converter",
+    from: "appcacheerrors",
+    to: "view",
+    exec: function([errors, manifestURI], context) {
+      if (errors.length == 0) {
+        return context.createView({
+          html: "<span>" + gcli.lookup("appCacheValidatedSuccessfully") + "</span>"
+        });
+      }
+
+      return context.createView({
+        html:
+          "<div>" +
+          "  <h4>Manifest URI: ${manifestURI}</h4>" +
+          "  <ol>" +
+          "    <li foreach='error in ${errors}'>${error.msg}</li>" +
+          "  </ol>" +
+          "</div>",
+        data: {
+          errors: errors,
+          manifestURI: manifestURI
+        }
+      });
+    }
+  },
+  {
+    name: "appcache clear",
+    description: gcli.lookup("appCacheClearDesc"),
+    manual: gcli.lookup("appCacheClearManual"),
+    exec: function(args, context) {
+      let utils = new AppCacheUtils(args.uri);
+      utils.clearAll();
+
+      return gcli.lookup("appCacheClearCleared");
+    }
+  },
+  {
+    name: "appcache list",
+    description: gcli.lookup("appCacheListDesc"),
+    manual: gcli.lookup("appCacheListManual"),
+    returnType: "appcacheentries",
+    params: [{
+      group: "options",
+      params: [
+        {
+          type: "string",
+          name: "search",
+          description: gcli.lookup("appCacheListSearchDesc"),
+          defaultValue: null,
+        },
+      ]
+    }],
+    exec: function(args, context) {
+      let utils = new AppCacheUtils();
+      return utils.listEntries(args.search);
+    }
+  },
+  {
+    item: "converter",
+    from: "appcacheentries",
+    to: "view",
+    exec: function(entries, context) {
+      return context.createView({
+        html: "" +
+          "<ul class='gcli-appcache-list'>" +
+          "  <li foreach='entry in ${entries}'>" +
+          "    <table class='gcli-appcache-detail'>" +
+          "      <tr>" +
+          "        <td>" + gcli.lookup("appCacheListKey") + "</td>" +
+          "        <td>${entry.key}</td>" +
+          "      </tr>" +
+          "      <tr>" +
+          "        <td>" + gcli.lookup("appCacheListFetchCount") + "</td>" +
+          "        <td>${entry.fetchCount}</td>" +
+          "      </tr>" +
+          "      <tr>" +
+          "        <td>" + gcli.lookup("appCacheListLastFetched") + "</td>" +
+          "        <td>${entry.lastFetched}</td>" +
+          "      </tr>" +
+          "      <tr>" +
+          "        <td>" + gcli.lookup("appCacheListLastModified") + "</td>" +
+          "        <td>${entry.lastModified}</td>" +
+          "      </tr>" +
+          "      <tr>" +
+          "        <td>" + gcli.lookup("appCacheListExpirationTime") + "</td>" +
+          "        <td>${entry.expirationTime}</td>" +
+          "      </tr>" +
+          "      <tr>" +
+          "        <td>" + gcli.lookup("appCacheListDataSize") + "</td>" +
+          "        <td>${entry.dataSize}</td>" +
+          "      </tr>" +
+          "      <tr>" +
+          "        <td>" + gcli.lookup("appCacheListDeviceID") + "</td>" +
+          "        <td>${entry.deviceID} <span class='gcli-out-shortcut' " +
+          "onclick='${onclick}' ondblclick='${ondblclick}' " +
+          "data-command='appcache viewentry ${entry.key}'" +
+          ">" + gcli.lookup("appCacheListViewEntry") + "</span>" +
+          "        </td>" +
+          "      </tr>" +
+          "    </table>" +
+          "  </li>" +
+          "</ul>",
+        data: {
+          entries: entries,
+          onclick: context.update,
+          ondblclick: context.updateExec
+        }
+      });
+    }
+  },
+  {
+    name: "appcache viewentry",
+    description: gcli.lookup("appCacheViewEntryDesc"),
+    manual: gcli.lookup("appCacheViewEntryManual"),
+    params: [
+      {
+        type: "string",
+        name: "key",
+        description: gcli.lookup("appCacheViewEntryKey"),
+        defaultValue: null,
+      }
+    ],
+    exec: function(args, context) {
+      let utils = new AppCacheUtils();
+      return utils.viewEntry(args.key);
+    }
+  }
+];
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/gcli/commands/calllog.js
@@ -0,0 +1,213 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Cc, Ci, Cu } = require("chrome");
+const TargetFactory = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.TargetFactory;
+const gcli = require("gcli/index");
+
+loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
+
+loader.lazyGetter(this, "Debugger", () => {
+  let global = Cu.getGlobalForObject({});
+  let JsDebugger = Cu.import("resource://gre/modules/jsdebugger.jsm", {});
+  JsDebugger.addDebuggerToGlobal(global);
+  return global.Debugger;
+});
+
+let debuggers = [];
+let chromeDebuggers = [];
+let sandboxes = [];
+
+exports.items = [
+  {
+    name: "calllog",
+    description: gcli.lookup("calllogDesc")
+  },
+  {
+    name: "calllog start",
+    description: gcli.lookup("calllogStartDesc"),
+
+    exec: function(args, context) {
+      let contentWindow = context.environment.window;
+
+      let dbg = new Debugger(contentWindow);
+      dbg.onEnterFrame = function(frame) {
+        // BUG 773652 -  Make the output from the GCLI calllog command nicer
+        contentWindow.console.log("Method call: " + this.callDescription(frame));
+      }.bind(this);
+
+      debuggers.push(dbg);
+
+      let gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
+      let target = TargetFactory.forTab(gBrowser.selectedTab);
+      gDevTools.showToolbox(target, "webconsole");
+
+      return gcli.lookup("calllogStartReply");
+    },
+
+    callDescription: function(frame) {
+      let name = "<anonymous>";
+      if (frame.callee.name) {
+        name = frame.callee.name;
+      }
+      else {
+        let desc = frame.callee.getOwnPropertyDescriptor("displayName");
+        if (desc && desc.value && typeof desc.value == "string") {
+          name = desc.value;
+        }
+      }
+
+      let args = frame.arguments.map(this.valueToString).join(", ");
+      return name + "(" + args + ")";
+    },
+
+    valueToString: function(value) {
+      if (typeof value !== "object" || value === null) {
+        return uneval(value);
+      }
+      return "[object " + value.class + "]";
+    }
+  },
+  {
+    name: "calllog stop",
+    description: gcli.lookup("calllogStopDesc"),
+
+    exec: function(args, context) {
+      let numDebuggers = debuggers.length;
+      if (numDebuggers == 0) {
+        return gcli.lookup("calllogStopNoLogging");
+      }
+
+      for (let dbg of debuggers) {
+        dbg.onEnterFrame = undefined;
+      }
+      debuggers = [];
+
+      return gcli.lookupFormat("calllogStopReply", [ numDebuggers ]);
+    }
+  },
+  {
+    name: "calllog chromestart",
+    description: gcli.lookup("calllogChromeStartDesc"),
+    get hidden() gcli.hiddenByChromePref(),
+    params: [
+      {
+        name: "sourceType",
+        type: {
+          name: "selection",
+          data: ["content-variable", "chrome-variable", "jsm", "javascript"]
+        }
+      },
+      {
+        name: "source",
+        type: "string",
+        description: gcli.lookup("calllogChromeSourceTypeDesc"),
+        manual: gcli.lookup("calllogChromeSourceTypeManual"),
+      }
+    ],
+    exec: function(args, context) {
+      let globalObj;
+      let contentWindow = context.environment.window;
+
+      if (args.sourceType == "jsm") {
+        try {
+          globalObj = Cu.import(args.source);
+        }
+        catch (e) {
+          return gcli.lookup("callLogChromeInvalidJSM");
+        }
+      } else if (args.sourceType == "content-variable") {
+        if (args.source in contentWindow) {
+          globalObj = Cu.getGlobalForObject(contentWindow[args.source]);
+        } else {
+          throw new Error(gcli.lookup("callLogChromeVarNotFoundContent"));
+        }
+      } else if (args.sourceType == "chrome-variable") {
+        let chromeWin = context.environment.chromeDocument.defaultView;
+        if (args.source in chromeWin) {
+          globalObj = Cu.getGlobalForObject(chromeWin[args.source]);
+        } else {
+          return gcli.lookup("callLogChromeVarNotFoundChrome");
+        }
+      } else {
+        let chromeWin = context.environment.chromeDocument.defaultView;
+        let sandbox = new Cu.Sandbox(chromeWin,
+                                    {
+                                      sandboxPrototype: chromeWin,
+                                      wantXrays: false,
+                                      sandboxName: "gcli-cmd-calllog-chrome"
+                                    });
+        let returnVal;
+        try {
+          returnVal = Cu.evalInSandbox(args.source, sandbox, "ECMAv5");
+          sandboxes.push(sandbox);
+        } catch(e) {
+          // We need to save the message before cleaning up else e contains a dead
+          // object.
+          let msg = gcli.lookup("callLogChromeEvalException") + ": " + e;
+          Cu.nukeSandbox(sandbox);
+          return msg;
+        }
+
+        if (typeof returnVal == "undefined") {
+          return gcli.lookup("callLogChromeEvalNeedsObject");
+        }
+
+        globalObj = Cu.getGlobalForObject(returnVal);
+      }
+
+      let dbg = new Debugger(globalObj);
+      chromeDebuggers.push(dbg);
+
+      dbg.onEnterFrame = function(frame) {
+        // BUG 773652 -  Make the output from the GCLI calllog command nicer
+        contentWindow.console.log(gcli.lookup("callLogChromeMethodCall") +
+                                  ": " + this.callDescription(frame));
+      }.bind(this);
+
+      let gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
+      let target = TargetFactory.forTab(gBrowser.selectedTab);
+      gDevTools.showToolbox(target, "webconsole");
+
+      return gcli.lookup("calllogChromeStartReply");
+    },
+
+    valueToString: function(value) {
+      if (typeof value !== "object" || value === null)
+        return uneval(value);
+      return "[object " + value.class + "]";
+    },
+
+    callDescription: function(frame) {
+      let name = frame.callee.name || gcli.lookup("callLogChromeAnonFunction");
+      let args = frame.arguments.map(this.valueToString).join(", ");
+      return name + "(" + args + ")";
+    }
+  },
+  {
+    name: "calllog chromestop",
+    description: gcli.lookup("calllogChromeStopDesc"),
+    get hidden() gcli.hiddenByChromePref(),
+    exec: function(args, context) {
+      let numDebuggers = chromeDebuggers.length;
+      if (numDebuggers == 0) {
+        return gcli.lookup("calllogChromeStopNoLogging");
+      }
+
+      for (let dbg of chromeDebuggers) {
+        dbg.onEnterFrame = undefined;
+        dbg.enabled = false;
+      }
+      for (let sandbox of sandboxes) {
+        Cu.nukeSandbox(sandbox);
+      }
+      chromeDebuggers = [];
+      sandboxes = [];
+
+      return gcli.lookupFormat("calllogChromeStopReply", [ numDebuggers ]);
+    }
+  }
+];
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/gcli/commands/cmd.js
@@ -0,0 +1,174 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Cc, Ci, Cu } = require("chrome");
+
+const { Promise: promise } = require("resource://gre/modules/Promise.jsm");
+
+const { OS } = Cu.import("resource://gre/modules/osfile.jsm", {});
+const { TextEncoder, TextDecoder } = Cu.import('resource://gre/modules/commonjs/toolkit/loader.js', {});
+const gcli = require("gcli/index");
+
+loader.lazyGetter(this, "prefBranch", function() {
+  let prefService = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService);
+  return prefService.getBranch(null).QueryInterface(Ci.nsIPrefBranch2);
+});
+
+loader.lazyGetter(this, "supportsString", function() {
+  return Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
+});
+
+loader.lazyImporter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
+
+const PREF_DIR = "devtools.commands.dir";
+
+/**
+ * Load all the .mozcmd files in the directory pointed to by PREF_DIR
+ * @return A promise of an array of items suitable for gcli.addItems or
+ * using in gcli.addItemsByModule
+ */
+function loadItemsFromMozDir() {
+  let dirName = prefBranch.getComplexValue(PREF_DIR,
+                                           Ci.nsISupportsString).data.trim();
+  if (dirName == "") {
+    return promise.resolve([]);
+  }
+
+  // replaces ~ with the home directory path in unix and windows
+  if (dirName.indexOf("~") == 0) {
+    let dirService = Cc["@mozilla.org/file/directory_service;1"]
+                      .getService(Ci.nsIProperties);
+    let homeDirFile = dirService.get("Home", Ci.nsIFile);
+    let homeDir = homeDirFile.path;
+    dirName = dirName.substr(1);
+    dirName = homeDir + dirName;
+  }
+
+  // statPromise resolves to nothing if dirName is a directory, or it
+  // rejects with an error message otherwise
+  let statPromise = OS.File.stat(dirName);
+  statPromise = statPromise.then(
+    function onSuccess(stat) {
+      if (!stat.isDir) {
+        throw new Error("'" + dirName + "' is not a directory.");
+      }
+    },
+    function onFailure(reason) {
+      if (reason instanceof OS.File.Error && reason.becauseNoSuchFile) {
+        throw new Error("'" + dirName + "' does not exist.");
+      } else {
+        throw reason;
+      }
+    }
+  );
+
+  // We need to return (a promise of) an array of items from the *.mozcmd
+  // files in dirName (which we can assume to be a valid directory now)
+  return statPromise.then(() => {
+    let itemPromises = [];
+
+    let iterator = new OS.File.DirectoryIterator(dirName);
+    let iterPromise = iterator.forEach(entry => {
+      if (entry.name.match(/.*\.mozcmd$/) && !entry.isDir) {
+        itemPromises.push(loadCommandFile(entry));
+      }
+    });
+
+    return iterPromise.then(() => {
+      iterator.close();
+      return promise.all(itemPromises).then((itemsArray) => {
+        return itemsArray.reduce((prev, curr) => {
+          return prev.concat(curr);
+        }, []);
+      });
+    }, reason => { iterator.close(); throw reason; });
+  });
+}
+
+exports.mozDirLoader = function(name) {
+  return loadItemsFromMozDir().then(items => {
+    return { items: items };
+  });
+};
+
+/**
+ * Load the commands from a single file
+ * @param OS.File.DirectoryIterator.Entry entry The DirectoryIterator
+ * Entry of the file containing the commands that we should read
+ */
+function loadCommandFile(entry) {
+  let readPromise = OS.File.read(entry.path);
+  return readPromise = readPromise.then(array => {
+    let decoder = new TextDecoder();
+    let source = decoder.decode(array);
+    var principal = Cc["@mozilla.org/systemprincipal;1"]
+                      .createInstance(Ci.nsIPrincipal);
+
+    let sandbox = new Cu.Sandbox(principal, {
+      sandboxName: entry.path
+    });
+    let data = Cu.evalInSandbox(source, sandbox, "1.8", entry.name, 1);
+
+    if (!Array.isArray(data)) {
+      console.error("Command file '" + entry.name + "' does not have top level array.");
+      return;
+    }
+
+    return data;
+  });
+}
+
+exports.items = [
+  {
+    name: "cmd",
+    get hidden() {
+      return !prefBranch.prefHasUserValue(PREF_DIR);
+    },
+    description: gcli.lookup("cmdDesc")
+  },
+  {
+    name: "cmd refresh",
+    description: gcli.lookup("cmdRefreshDesc"),
+    get hidden() {
+      return !prefBranch.prefHasUserValue(PREF_DIR);
+    },
+    exec: function(args, context) {
+      gcli.load();
+
+      let dirName = prefBranch.getComplexValue(PREF_DIR,
+                                              Ci.nsISupportsString).data.trim();
+      return gcli.lookupFormat("cmdStatus2", [ dirName ]);
+    }
+  },
+  {
+    name: "cmd setdir",
+    description: gcli.lookup("cmdSetdirDesc"),
+    params: [
+      {
+        name: "directory",
+        description: gcli.lookup("cmdSetdirDirectoryDesc"),
+        type: {
+          name: "file",
+          filetype: "directory",
+          existing: "yes"
+        },
+        defaultValue: null
+      }
+    ],
+    returnType: "string",
+    get hidden() {
+      return true; // !prefBranch.prefHasUserValue(PREF_DIR);
+    },
+    exec: function(args, context) {
+      supportsString.data = args.directory;
+      prefBranch.setComplexValue(PREF_DIR, Ci.nsISupportsString, supportsString);
+
+      gcli.load();
+
+      return gcli.lookupFormat("cmdStatus2", [ args.directory ]);
+    }
+  }
+];
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/gcli/commands/cookie.js
@@ -0,0 +1,233 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Cc, Ci, Cu } = require("chrome");
+const gcli = require("gcli/index");
+const cookieMgr = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager2);
+
+/**
+ * The cookie 'expires' value needs converting into something more readable
+ */
+function translateExpires(expires) {
+  if (expires == 0) {
+    return gcli.lookup("cookieListOutSession");
+  }
+  return new Date(expires).toLocaleString();
+}
+
+/**
+ * Check if a given cookie matches a given host
+ */
+function isCookieAtHost(cookie, host) {
+  if (cookie.host == null) {
+    return host == null;
+  }
+  if (cookie.host.startsWith(".")) {
+    return host.endsWith(cookie.host);
+  }
+  else {
+    return cookie.host == host;
+  }
+}
+
+exports.items = [
+  {
+    name: "cookie",
+    description: gcli.lookup("cookieDesc"),
+    manual: gcli.lookup("cookieManual")
+  },
+  {
+    name: "cookie list",
+    description: gcli.lookup("cookieListDesc"),
+    manual: gcli.lookup("cookieListManual"),
+    returnType: "cookies",
+    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 = [];
+      while (enm.hasMoreElements()) {
+        let cookie = enm.getNext().QueryInterface(Ci.nsICookie);
+        if (isCookieAtHost(cookie, host)) {
+          cookies.push({
+            host: cookie.host,
+            name: cookie.name,
+            value: cookie.value,
+            path: cookie.path,
+            expires: cookie.expires,
+            secure: cookie.secure,
+            httpOnly: cookie.httpOnly,
+            sameDomain: cookie.sameDomain
+          });
+        }
+      }
+
+      return cookies;
+    }
+  },
+  {
+    name: "cookie remove",
+    description: gcli.lookup("cookieRemoveDesc"),
+    manual: gcli.lookup("cookieRemoveManual"),
+    params: [
+      {
+        name: "name",
+        type: "string",
+        description: gcli.lookup("cookieRemoveKeyDesc"),
+      }
+    ],
+    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) {
+            cookieMgr.remove(cookie.host, cookie.name, cookie.path, false);
+          }
+        }
+      }
+    }
+  },
+  {
+    item: "converter",
+    from: "cookies",
+    to: "view",
+    exec: function(cookies, context) {
+      if (cookies.length == 0) {
+        let host = context.environment.document.location.host;
+        let msg = gcli.lookupFormat("cookieListOutNoneHost", [ host ]);
+        return context.createView({ html: "<span>" + msg + "</span>" });
+      }
+
+      for (let cookie of cookies) {
+        cookie.expires = translateExpires(cookie.expires);
+
+        let noAttrs = !cookie.secure && !cookie.httpOnly && !cookie.sameDomain;
+        cookie.attrs = (cookie.secure ? "secure" : " ") +
+                       (cookie.httpOnly ? "httpOnly" : " ") +
+                       (cookie.sameDomain ? "sameDomain" : " ") +
+                       (noAttrs ? gcli.lookup("cookieListOutNone") : " ");
+      }
+
+      return context.createView({
+        html:
+          "<ul class='gcli-cookielist-list'>" +
+          "  <li foreach='cookie in ${cookies}'>" +
+          "    <div>${cookie.name}=${cookie.value}</div>" +
+          "    <table class='gcli-cookielist-detail'>" +
+          "      <tr>" +
+          "        <td>" + gcli.lookup("cookieListOutHost") + "</td>" +
+          "        <td>${cookie.host}</td>" +
+          "      </tr>" +
+          "      <tr>" +
+          "        <td>" + gcli.lookup("cookieListOutPath") + "</td>" +
+          "        <td>${cookie.path}</td>" +
+          "      </tr>" +
+          "      <tr>" +
+          "        <td>" + gcli.lookup("cookieListOutExpires") + "</td>" +
+          "        <td>${cookie.expires}</td>" +
+          "      </tr>" +
+          "      <tr>" +
+          "        <td>" + gcli.lookup("cookieListOutAttributes") + "</td>" +
+          "        <td>${cookie.attrs}</td>" +
+          "      </tr>" +
+          "      <tr><td colspan='2'>" +
+          "        <span class='gcli-out-shortcut' onclick='${onclick}'" +
+          "            data-command='cookie set ${cookie.name} '" +
+          "            >" + gcli.lookup("cookieListOutEdit") + "</span>" +
+          "        <span class='gcli-out-shortcut'" +
+          "            onclick='${onclick}' ondblclick='${ondblclick}'" +
+          "            data-command='cookie remove ${cookie.name}'" +
+          "            >" + gcli.lookup("cookieListOutRemove") + "</span>" +
+          "      </td></tr>" +
+          "    </table>" +
+          "  </li>" +
+          "</ul>",
+        data: {
+          options: { allowEval: true },
+          cookies: cookies,
+          onclick: context.update,
+          ondblclick: context.updateExec
+        }
+      });
+    }
+  },
+  {
+    name: "cookie set",
+    description: gcli.lookup("cookieSetDesc"),
+    manual: gcli.lookup("cookieSetManual"),
+    params: [
+      {
+        name: "name",
+        type: "string",
+        description: gcli.lookup("cookieSetKeyDesc")
+      },
+      {
+        name: "value",
+        type: "string",
+        description: gcli.lookup("cookieSetValueDesc")
+      },
+      {
+        group: gcli.lookup("cookieSetOptionsDesc"),
+        params: [
+          {
+            name: "path",
+            type: { name: "string", allowBlank: true },
+            defaultValue: "/",
+            description: gcli.lookup("cookieSetPathDesc")
+          },
+          {
+            name: "domain",
+            type: "string",
+            defaultValue: null,
+            description: gcli.lookup("cookieSetDomainDesc")
+          },
+          {
+            name: "secure",
+            type: "boolean",
+            description: gcli.lookup("cookieSetSecureDesc")
+          },
+          {
+            name: "httpOnly",
+            type: "boolean",
+            description: gcli.lookup("cookieSetHttpOnlyDesc")
+          },
+          {
+            name: "session",
+            type: "boolean",
+            description: gcli.lookup("cookieSetSessionDesc")
+          },
+          {
+            name: "expires",
+            type: "string",
+            defaultValue: "Jan 17, 2038",
+            description: gcli.lookup("cookieSetExpiresDesc")
+          },
+        ]
+      }
+    ],
+    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);
+    }
+  }
+];
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/gcli/commands/jsb.js
@@ -0,0 +1,133 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Cc, Ci, Cu } = require("chrome");
+const gcli = require("gcli/index");
+const XMLHttpRequest = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"];
+
+loader.lazyImporter(this, "js_beautify", "resource:///modules/devtools/Jsbeautify.jsm");
+
+exports.items = [
+  {
+    name: "jsb",
+    description: gcli.lookup("jsbDesc"),
+    returnValue:"string",
+    params: [
+      {
+        name: "url",
+        type: "string",
+        description: gcli.lookup("jsbUrlDesc")
+      },
+      {
+        group: gcli.lookup("jsbOptionsDesc"),
+        params: [
+          {
+            name: "indentSize",
+            type: "number",
+            description: gcli.lookup("jsbIndentSizeDesc"),
+            manual: gcli.lookup("jsbIndentSizeManual"),
+            defaultValue: 2
+          },
+          {
+            name: "indentChar",
+            type: {
+              name: "selection",
+              lookup: [
+                { name: "space", value: " " },
+                { name: "tab", value: "\t" }
+              ]
+            },
+            description: gcli.lookup("jsbIndentCharDesc"),
+            manual: gcli.lookup("jsbIndentCharManual"),
+            defaultValue: " ",
+          },
+          {
+            name: "doNotPreserveNewlines",
+            type: "boolean",
+            description: gcli.lookup("jsbDoNotPreserveNewlinesDesc")
+          },
+          {
+            name: "preserveMaxNewlines",
+            type: "number",
+            description: gcli.lookup("jsbPreserveMaxNewlinesDesc"),
+            manual: gcli.lookup("jsbPreserveMaxNewlinesManual"),
+            defaultValue: -1
+          },
+          {
+            name: "jslintHappy",
+            type: "boolean",
+            description: gcli.lookup("jsbJslintHappyDesc"),
+            manual: gcli.lookup("jsbJslintHappyManual")
+          },
+          {
+            name: "braceStyle",
+            type: {
+              name: "selection",
+              data: ["collapse", "expand", "end-expand", "expand-strict"]
+            },
+            description: gcli.lookup("jsbBraceStyleDesc2"),
+            manual: gcli.lookup("jsbBraceStyleManual2"),
+            defaultValue: "collapse"
+          },
+          {
+            name: "noSpaceBeforeConditional",
+            type: "boolean",
+            description: gcli.lookup("jsbNoSpaceBeforeConditionalDesc")
+          },
+          {
+            name: "unescapeStrings",
+            type: "boolean",
+            description: gcli.lookup("jsbUnescapeStringsDesc"),
+            manual: gcli.lookup("jsbUnescapeStringsManual")
+          }
+        ]
+      }
+    ],
+    exec: function(args, context) {
+      let opts = {
+        indent_size: args.indentSize,
+        indent_char: args.indentChar,
+        preserve_newlines: !args.doNotPreserveNewlines,
+        max_preserve_newlines: args.preserveMaxNewlines == -1 ?
+                              undefined : args.preserveMaxNewlines,
+        jslint_happy: args.jslintHappy,
+        brace_style: args.braceStyle,
+        space_before_conditional: !args.noSpaceBeforeConditional,
+        unescape_strings: args.unescapeStrings
+      };
+
+      let xhr = new XMLHttpRequest();
+
+      try {
+        xhr.open("GET", args.url, true);
+      } catch(e) {
+        return gcli.lookup("jsbInvalidURL");
+      }
+
+      let deferred = context.defer();
+
+      xhr.onreadystatechange = function(aEvt) {
+        if (xhr.readyState == 4) {
+          if (xhr.status == 200 || xhr.status == 0) {
+            let browserDoc = context.environment.chromeDocument;
+            let browserWindow = browserDoc.defaultView;
+            let gBrowser = browserWindow.gBrowser;
+            let result = js_beautify(xhr.responseText, opts);
+
+            browserWindow.Scratchpad.ScratchpadManager.openScratchpad({text: result});
+
+            deferred.resolve();
+          } else {
+            deferred.resolve("Unable to load page to beautify: " + args.url + " " +
+                             xhr.status + " " + xhr.statusText);
+          }
+        };
+      }
+      xhr.send(null);
+      return deferred.promise;
+    }
+  }
+];
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/gcli/commands/listen.js
@@ -0,0 +1,49 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Cc, Ci, Cu } = require("chrome");
+const Services = require("Services");
+const gcli = require("gcli/index");
+const { DebuggerServer } = require("resource://gre/modules/devtools/dbg-server.jsm");
+
+const BRAND_SHORT_NAME = Cc["@mozilla.org/intl/stringbundle;1"]
+                           .getService(Ci.nsIStringBundleService)
+                           .createBundle("chrome://branding/locale/brand.properties")
+                           .GetStringFromName("brandShortName");
+
+exports.items = [
+  {
+    name: "listen",
+    description: gcli.lookup("listenDesc"),
+    manual: gcli.lookupFormat("listenManual2", [ BRAND_SHORT_NAME ]),
+    params: [
+      {
+        name: "port",
+        type: "number",
+        get defaultValue() {
+          return Services.prefs.getIntPref("devtools.debugger.chrome-debugging-port");
+        },
+        description: gcli.lookup("listenPortDesc"),
+      }
+    ],
+    exec: function(args, context) {
+      if (!DebuggerServer.initialized) {
+        DebuggerServer.init();
+        DebuggerServer.addBrowserActors();
+      }
+      var reply = DebuggerServer.openListener(args.port);
+      if (!reply) {
+        throw new Error(gcli.lookup("listenDisabledOutput"));
+      }
+
+      if (DebuggerServer.initialized) {
+        return gcli.lookupFormat("listenInitOutput", [ "" + args.port ]);
+      }
+
+      return gcli.lookup("listenNoInitOutput");
+    },
+  }
+];
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/gcli/commands/media.js
@@ -0,0 +1,47 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const gcli = require("gcli/index");
+
+exports.items = [
+  {
+    name: "media",
+    description: gcli.lookup("mediaDesc")
+  },
+  {
+    name: "media emulate",
+    description: gcli.lookup("mediaEmulateDesc"),
+    manual: gcli.lookup("mediaEmulateManual"),
+    params: [
+      {
+        name: "type",
+        description: gcli.lookup("mediaEmulateType"),
+        type: {
+           name: "selection",
+           data: [
+             "braille", "embossed", "handheld", "print", "projection",
+             "screen", "speech", "tty", "tv"
+           ]
+        }
+      }
+    ],
+    exec: function(args, context) {
+      let markupDocumentViewer = context.environment.chromeWindow
+                                        .gBrowser.markupDocumentViewer;
+      markupDocumentViewer.emulateMedium(args.type);
+    }
+  },
+  {
+    name: "media reset",
+    description: gcli.lookup("mediaResetDesc"),
+    manual: gcli.lookup("mediaEmulateManual"),
+    exec: function(args, context) {
+      let markupDocumentViewer = context.environment.chromeWindow
+                                        .gBrowser.markupDocumentViewer;
+      markupDocumentViewer.stopEmulatingMedium();
+    }
+  }
+];
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/gcli/commands/pagemod.js
@@ -0,0 +1,268 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Cc, Ci, Cu } = require("chrome");
+const gcli = require("gcli/index");
+
+exports.items = [
+  {
+    name: "pagemod",
+    description: gcli.lookup("pagemodDesc"),
+  },
+  {
+    name: "pagemod replace",
+    description: gcli.lookup("pagemodReplaceDesc"),
+    params: [
+      {
+        name: "search",
+        type: "string",
+        description: gcli.lookup("pagemodReplaceSearchDesc"),
+      },
+      {
+        name: "replace",
+        type: "string",
+        description: gcli.lookup("pagemodReplaceReplaceDesc"),
+      },
+      {
+        name: "ignoreCase",
+        type: "boolean",
+        description: gcli.lookup("pagemodReplaceIgnoreCaseDesc"),
+      },
+      {
+        name: "selector",
+        type: "string",
+        description: gcli.lookup("pagemodReplaceSelectorDesc"),
+        defaultValue: "*:not(script):not(style):not(embed):not(object):not(frame):not(iframe):not(frameset)",
+      },
+      {
+        name: "root",
+        type: "node",
+        description: gcli.lookup("pagemodReplaceRootDesc"),
+        defaultValue: null,
+      },
+      {
+        name: "attrOnly",
+        type: "boolean",
+        description: gcli.lookup("pagemodReplaceAttrOnlyDesc"),
+      },
+      {
+        name: "contentOnly",
+        type: "boolean",
+        description: gcli.lookup("pagemodReplaceContentOnlyDesc"),
+      },
+      {
+        name: "attributes",
+        type: "string",
+        description: gcli.lookup("pagemodReplaceAttributesDesc"),
+        defaultValue: null,
+      },
+    ],
+    // Make a given string safe to use in a regular expression.
+    escapeRegex: function(aString) {
+      return aString.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
+    },
+    exec: function(args, context) {
+      let searchTextNodes = !args.attrOnly;
+      let searchAttributes = !args.contentOnly;
+      let regexOptions = args.ignoreCase ? "ig" : "g";
+      let search = new RegExp(this.escapeRegex(args.search), regexOptions);
+      let attributeRegex = null;
+      if (args.attributes) {
+        attributeRegex = new RegExp(args.attributes, regexOptions);
+      }
+
+      let root = args.root || context.environment.document;
+      let elements = root.querySelectorAll(args.selector);
+      elements = Array.prototype.slice.call(elements);
+
+      let replacedTextNodes = 0;
+      let replacedAttributes = 0;
+
+      function replaceAttribute() {
+        replacedAttributes++;
+        return args.replace;
+      }
+      function replaceTextNode() {
+        replacedTextNodes++;
+        return args.replace;
+      }
+
+      for (let i = 0; i < elements.length; i++) {
+        let element = elements[i];
+        if (searchTextNodes) {
+          for (let y = 0; y < element.childNodes.length; y++) {
+            let node = element.childNodes[y];
+            if (node.nodeType == node.TEXT_NODE) {
+              node.textContent = node.textContent.replace(search, replaceTextNode);
+            }
+          }
+        }
+
+        if (searchAttributes) {
+          if (!element.attributes) {
+            continue;
+          }
+          for (let y = 0; y < element.attributes.length; y++) {
+            let attr = element.attributes[y];
+            if (!attributeRegex || attributeRegex.test(attr.name)) {
+              attr.value = attr.value.replace(search, replaceAttribute);
+            }
+          }
+        }
+      }
+
+      return gcli.lookupFormat("pagemodReplaceResult",
+                              [elements.length, replacedTextNodes,
+                                replacedAttributes]);
+    }
+  },
+  {
+    name: "pagemod remove",
+    description: gcli.lookup("pagemodRemoveDesc"),
+  },
+  {
+    name: "pagemod remove element",
+    description: gcli.lookup("pagemodRemoveElementDesc"),
+    params: [
+      {
+        name: "search",
+        type: "string",
+        description: gcli.lookup("pagemodRemoveElementSearchDesc"),
+      },
+      {
+        name: "root",
+        type: "node",
+        description: gcli.lookup("pagemodRemoveElementRootDesc"),
+        defaultValue: null,
+      },
+      {
+        name: "stripOnly",
+        type: "boolean",
+        description: gcli.lookup("pagemodRemoveElementStripOnlyDesc"),
+      },
+      {
+        name: "ifEmptyOnly",
+        type: "boolean",
+        description: gcli.lookup("pagemodRemoveElementIfEmptyOnlyDesc"),
+      },
+    ],
+    exec: function(args, context) {
+      let root = args.root || context.environment.document;
+      let elements = Array.prototype.slice.call(root.querySelectorAll(args.search));
+
+      let removed = 0;
+      for (let i = 0; i < elements.length; i++) {
+        let element = elements[i];
+        let parentNode = element.parentNode;
+        if (!parentNode || !element.removeChild) {
+          continue;
+        }
+        if (args.stripOnly) {
+          while (element.hasChildNodes()) {
+            parentNode.insertBefore(element.childNodes[0], element);
+          }
+        }
+        if (!args.ifEmptyOnly || !element.hasChildNodes()) {
+          element.parentNode.removeChild(element);
+          removed++;
+        }
+      }
+
+      return gcli.lookupFormat("pagemodRemoveElementResultMatchedAndRemovedElements",
+                              [elements.length, removed]);
+    }
+  },
+  {
+    name: "pagemod remove attribute",
+    description: gcli.lookup("pagemodRemoveAttributeDesc"),
+    params: [
+      {
+        name: "searchAttributes",
+        type: "string",
+        description: gcli.lookup("pagemodRemoveAttributeSearchAttributesDesc"),
+      },
+      {
+        name: "searchElements",
+        type: "string",
+        description: gcli.lookup("pagemodRemoveAttributeSearchElementsDesc"),
+      },
+      {
+        name: "root",
+        type: "node",
+        description: gcli.lookup("pagemodRemoveAttributeRootDesc"),
+        defaultValue: null,
+      },
+      {
+        name: "ignoreCase",
+        type: "boolean",
+        description: gcli.lookup("pagemodRemoveAttributeIgnoreCaseDesc"),
+      },
+    ],
+    exec: function(args, context) {
+      let root = args.root || context.environment.document;
+      let regexOptions = args.ignoreCase ? "ig" : "g";
+      let attributeRegex = new RegExp(args.searchAttributes, regexOptions);
+      let elements = root.querySelectorAll(args.searchElements);
+      elements = Array.prototype.slice.call(elements);
+
+      let removed = 0;
+      for (let i = 0; i < elements.length; i++) {
+        let element = elements[i];
+        if (!element.attributes) {
+          continue;
+        }
+
+        var attrs = Array.prototype.slice.call(element.attributes);
+        for (let y = 0; y < attrs.length; y++) {
+          let attr = attrs[y];
+          if (attributeRegex.test(attr.name)) {
+            element.removeAttribute(attr.name);
+            removed++;
+          }
+        }
+      }
+
+      return gcli.lookupFormat("pagemodRemoveAttributeResult",
+                              [elements.length, removed]);
+    }
+  },
+  // This command allows the user to export the page to HTML after DOM changes
+  {
+    name: "export",
+    description: gcli.lookup("exportDesc"),
+  },
+  {
+    name: "export html",
+    description: gcli.lookup("exportHtmlDesc"),
+    params: [
+      {
+        name: "destination",
+        type: {
+          name: "selection",
+          data: [ "window", "stdout", "clipboard" ]
+        },
+        defaultValue: "window"
+      }
+    ],
+    exec: function(args, context) {
+      let html = context.environment.document.documentElement.outerHTML;
+      if (args.destination === "stdout") {
+        return html;
+      }
+
+      if (args.desination === "clipboard") {
+        let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"]
+                          .getService(Ci.nsIClipboardHelper);
+        clipboard.copyString(url);
+        return '';
+      }
+
+      let url = "data:text/plain;charset=utf8," + encodeURIComponent(html);
+      context.environment.window.open(url);
+      return '';
+    }
+  }
+];
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/gcli/commands/paintflashing.js
@@ -0,0 +1,129 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Cc, Ci, Cu } = require("chrome");
+const TargetFactory = require("resource://gre/modules/devtools/Loader.jsm").devtools.TargetFactory;
+
+const Telemetry = require("devtools/shared/telemetry");
+const telemetry = new Telemetry();
+
+const EventEmitter = require("devtools/toolkit/event-emitter");
+const eventEmitter = new EventEmitter();
+
+const gcli = require("gcli/index");
+
+function onPaintFlashingChanged(context) {
+  let tab = context.environment.chromeWindow.gBrowser.selectedTab;
+  eventEmitter.emit("changed", tab);
+  function fireChange() {
+    eventEmitter.emit("changed", tab);
+  }
+  let target = TargetFactory.forTab(tab);
+  target.off("navigate", fireChange);
+  target.once("navigate", fireChange);
+
+  let window = context.environment.window;
+  let wUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                     .getInterface(Ci.nsIDOMWindowUtils);
+  if (wUtils.paintFlashing) {
+    telemetry.toolOpened("paintflashing");
+  } else {
+    telemetry.toolClosed("paintflashing");
+  }
+}
+
+exports.items = [
+  {
+    name: "paintflashing",
+    description: gcli.lookup("paintflashingDesc")
+  },
+  {
+    name: "paintflashing on",
+    description: gcli.lookup("paintflashingOnDesc"),
+    manual: gcli.lookup("paintflashingManual"),
+    params: [{
+      group: "options",
+      params: [
+        {
+          type: "boolean",
+          name: "chrome",
+          get hidden() gcli.hiddenByChromePref(),
+          description: gcli.lookup("paintflashingChromeDesc"),
+        }
+      ]
+    }],
+    exec: function(args, context) {
+      let window = args.chrome ?
+                  context.environment.chromeWindow :
+                  context.environment.window;
+
+      window.QueryInterface(Ci.nsIInterfaceRequestor)
+            .getInterface(Ci.nsIDOMWindowUtils)
+            .paintFlashing = true;
+      onPaintFlashingChanged(context);
+    }
+  },
+  {
+    name: "paintflashing off",
+    description: gcli.lookup("paintflashingOffDesc"),
+    manual: gcli.lookup("paintflashingManual"),
+    params: [{
+      group: "options",
+      params: [
+        {
+          type: "boolean",
+          name: "chrome",
+          get hidden() gcli.hiddenByChromePref(),
+          description: gcli.lookup("paintflashingChromeDesc"),
+        }
+      ]
+    }],
+    exec: function(args, context) {
+      let window = args.chrome ?
+                  context.environment.chromeWindow :
+                  context.environment.window;
+
+      window.QueryInterface(Ci.nsIInterfaceRequestor)
+            .getInterface(Ci.nsIDOMWindowUtils)
+            .paintFlashing = false;
+      onPaintFlashingChanged(context);
+    }
+  },
+  {
+    name: "paintflashing toggle",
+    hidden: true,
+    buttonId: "command-button-paintflashing",
+    buttonClass: "command-button command-button-invertable",
+    state: {
+      isChecked: function(aTarget) {
+        if (aTarget.isLocalTab) {
+          let window = aTarget.tab.linkedBrowser.contentWindow;
+          let wUtils = window.QueryInterface(Ci.nsIInterfaceRequestor).
+                              getInterface(Ci.nsIDOMWindowUtils);
+          return wUtils.paintFlashing;
+        } else {
+          throw new Error("Unsupported target");
+        }
+      },
+      onChange: function(aTarget, aChangeHandler) {
+        eventEmitter.on("changed", aChangeHandler);
+      },
+      offChange: function(aTarget, aChangeHandler) {
+        eventEmitter.off("changed", aChangeHandler);
+      },
+    },
+    tooltipText: gcli.lookup("paintflashingTooltip"),
+    description: gcli.lookup("paintflashingToggleDesc"),
+    manual: gcli.lookup("paintflashingManual"),
+    exec: function(args, context) {
+      let window = context.environment.window;
+      let wUtils = window.QueryInterface(Ci.nsIInterfaceRequestor).
+                   getInterface(Ci.nsIDOMWindowUtils);
+      wUtils.paintFlashing = !wUtils.paintFlashing;
+      onPaintFlashingChanged(context);
+    }
+  }
+];
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/gcli/commands/restart.js
@@ -0,0 +1,60 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Cc, Ci, Cu } = require("chrome");
+const gcli = require("gcli/index");
+const Services = require("Services");
+
+const BRAND_SHORT_NAME = Cc["@mozilla.org/intl/stringbundle;1"]
+                           .getService(Ci.nsIStringBundleService)
+                           .createBundle("chrome://branding/locale/brand.properties")
+                           .GetStringFromName("brandShortName");
+
+/**
+ * Restart command
+ *
+ * @param boolean nocache
+ *        Disables loading content from cache upon restart.
+ *
+ * Examples :
+ * >> restart
+ * - restarts browser immediately
+ * >> restart --nocache
+ * - restarts immediately and starts Firefox without using cache
+ */
+exports.items = [
+  {
+    name: "restart",
+    description: gcli.lookupFormat("restartBrowserDesc", [ BRAND_SHORT_NAME ]),
+    params: [
+      {
+        name: "nocache",
+        type: "boolean",
+        description: gcli.lookup("restartBrowserNocacheDesc")
+      }
+    ],
+    returnType: "string",
+    exec: function Restart(args, context) {
+      let canceled = Cc["@mozilla.org/supports-PRBool;1"]
+                      .createInstance(Ci.nsISupportsPRBool);
+      Services.obs.notifyObservers(canceled, "quit-application-requested", "restart");
+      if (canceled.data) {
+        return gcli.lookup("restartBrowserRequestCancelled");
+      }
+
+      // disable loading content from cache.
+      if (args.nocache) {
+        Services.appinfo.invalidateCachesOnRestart();
+      }
+
+      // restart
+      Cc["@mozilla.org/toolkit/app-startup;1"]
+          .getService(Ci.nsIAppStartup)
+          .quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
+      return gcli.lookupFormat("restartBrowserRestarting", [ BRAND_SHORT_NAME ]);
+    }
+  }
+];
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/gcli/commands/screenshot.js
@@ -0,0 +1,236 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Cc, Ci, Cu } = require("chrome");
+const gcli = require("gcli/index");
+
+loader.lazyImporter(this, "Downloads", "resource://gre/modules/Downloads.jsm");
+loader.lazyImporter(this, "LayoutHelpers", "resource://gre/modules/devtools/LayoutHelpers.jsm");
+loader.lazyImporter(this, "Task", "resource://gre/modules/Task.jsm");
+loader.lazyImporter(this, "OS", "resource://gre/modules/osfile.jsm");
+
+const BRAND_SHORT_NAME = Cc["@mozilla.org/intl/stringbundle;1"]
+                           .getService(Ci.nsIStringBundleService)
+                           .createBundle("chrome://branding/locale/brand.properties")
+                           .GetStringFromName("brandShortName");
+
+// String used as an indication to generate default file name in the following
+// format: "Screen Shot yyyy-mm-dd at HH.MM.SS.png"
+const FILENAME_DEFAULT_VALUE = " ";
+
+exports.items = [
+  {
+    name: "screenshot",
+    description: gcli.lookup("screenshotDesc"),
+    manual: gcli.lookup("screenshotManual"),
+    returnType: "dom",
+    params: [
+      {
+        name: "filename",
+        type: "string",
+        defaultValue: FILENAME_DEFAULT_VALUE,
+        description: gcli.lookup("screenshotFilenameDesc"),
+        manual: gcli.lookup("screenshotFilenameManual")
+      },
+      {
+        group: gcli.lookup("screenshotGroupOptions"),
+        params: [
+          {
+            name: "clipboard",
+            type: "boolean",
+            description: gcli.lookup("screenshotClipboardDesc"),
+            manual: gcli.lookup("screenshotClipboardManual")
+          },
+          {
+            name: "chrome",
+            type: "boolean",
+            description: gcli.lookupFormat("screenshotChromeDesc2", [BRAND_SHORT_NAME]),
+            manual: gcli.lookupFormat("screenshotChromeManual2", [BRAND_SHORT_NAME])
+          },
+          {
+            name: "delay",
+            type: { name: "number", min: 0 },
+            defaultValue: 0,
+            description: gcli.lookup("screenshotDelayDesc"),
+            manual: gcli.lookup("screenshotDelayManual")
+          },
+          {
+            name: "fullpage",
+            type: "boolean",
+            description: gcli.lookup("screenshotFullPageDesc"),
+            manual: gcli.lookup("screenshotFullPageManual")
+          },
+          {
+            name: "selector",
+            type: "node",
+            defaultValue: null,
+            description: gcli.lookup("inspectNodeDesc"),
+            manual: gcli.lookup("inspectNodeManual")
+          }
+        ]
+      }
+    ],
+    exec: function(args, context) {
+      if (args.chrome && args.selector) {
+        // Node screenshot with chrome option does not work as intended
+        // Refer https://bugzilla.mozilla.org/show_bug.cgi?id=659268#c7
+        // throwing for now.
+        throw new Error(gcli.lookup("screenshotSelectorChromeConflict"));
+      }
+      var document = args.chrome? context.environment.chromeDocument
+                                : context.environment.document;
+      if (args.delay > 0) {
+        var deferred = context.defer();
+        document.defaultView.setTimeout(() => {
+          this.grabScreen(document, args.filename, args.clipboard,
+                          args.fullpage).then(deferred.resolve, deferred.reject);
+        }, args.delay * 1000);
+        return deferred.promise;
+      }
+
+      return this.grabScreen(document, args.filename, args.clipboard,
+                             args.fullpage, args.selector);
+    },
+    grabScreen: function(document, filename, clipboard, fullpage, node) {
+      return Task.spawn(function() {
+        let window = document.defaultView;
+        let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+        let left = 0;
+        let top = 0;
+        let width;
+        let height;
+        let div = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+
+        if (!fullpage) {
+          if (!node) {
+            left = window.scrollX;
+            top = window.scrollY;
+            width = window.innerWidth;
+            height = window.innerHeight;
+          } else {
+            let lh = new LayoutHelpers(window);
+            let rect = lh.getRect(node, window);
+            top = rect.top;
+            left = rect.left;
+            width = rect.width;
+            height = rect.height;
+          }
+        } else {
+          width = window.innerWidth + window.scrollMaxX;
+          height = window.innerHeight + window.scrollMaxY;
+        }
+        canvas.width = width;
+        canvas.height = height;
+
+        let ctx = canvas.getContext("2d");
+        ctx.drawWindow(window, left, top, width, height, "#fff");
+        let data = canvas.toDataURL("image/png", "");
+
+        let loadContext = document.defaultView
+                                  .QueryInterface(Ci.nsIInterfaceRequestor)
+                                  .getInterface(Ci.nsIWebNavigation)
+                                  .QueryInterface(Ci.nsILoadContext);
+
+        if (clipboard) {
+          try {
+            let io = Cc["@mozilla.org/network/io-service;1"]
+                      .getService(Ci.nsIIOService);
+            let channel = io.newChannel(data, null, null);
+            let input = channel.open();
+            let imgTools = Cc["@mozilla.org/image/tools;1"]
+                            .getService(Ci.imgITools);
+
+            let container = {};
+            imgTools.decodeImageData(input, channel.contentType, container);
+
+            let wrapped = Cc["@mozilla.org/supports-interface-pointer;1"]
+                            .createInstance(Ci.nsISupportsInterfacePointer);
+            wrapped.data = container.value;
+
+            let trans = Cc["@mozilla.org/widget/transferable;1"]
+                          .createInstance(Ci.nsITransferable);
+            trans.init(loadContext);
+            trans.addDataFlavor(channel.contentType);
+            trans.setTransferData(channel.contentType, wrapped, -1);
+
+            let clipid = Ci.nsIClipboard;
+            let clip = Cc["@mozilla.org/widget/clipboard;1"].getService(clipid);
+            clip.setData(trans, null, clipid.kGlobalClipboard);
+            div.textContent = gcli.lookup("screenshotCopied");
+          }
+          catch (ex) {
+            div.textContent = gcli.lookup("screenshotErrorCopying");
+          }
+          throw new Task.Result(div);
+        }
+
+        let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
+
+        // Create a name for the file if not present
+        if (filename == FILENAME_DEFAULT_VALUE) {
+          let date = new Date();
+          let dateString = date.getFullYear() + "-" + (date.getMonth() + 1) +
+                          "-" + date.getDate();
+          dateString = dateString.split("-").map(function(part) {
+            if (part.length == 1) {
+              part = "0" + part;
+            }
+            return part;
+          }).join("-");
+          let timeString = date.toTimeString().replace(/:/g, ".").split(" ")[0];
+          filename = gcli.lookupFormat("screenshotGeneratedFilename",
+                                      [dateString, timeString]) + ".png";
+        }
+        // Check there is a .png extension to filename
+        else if (!filename.match(/.png$/i)) {
+          filename += ".png";
+        }
+        // If the filename is relative, tack it onto the download directory
+        if (!filename.match(/[\\\/]/)) {
+          let preferredDir = yield Downloads.getPreferredDownloadsDirectory();
+          filename = OS.Path.join(preferredDir, filename);
+        }
+
+        try {
+          file.initWithPath(filename);
+        } catch (ex) {
+          div.textContent = gcli.lookup("screenshotErrorSavingToFile") + " " + filename;
+          throw new Task.Result(div);
+        }
+
+        let ioService = Cc["@mozilla.org/network/io-service;1"]
+                          .getService(Ci.nsIIOService);
+
+        let Persist = Ci.nsIWebBrowserPersist;
+        let persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
+                        .createInstance(Persist);
+        persist.persistFlags = Persist.PERSIST_FLAGS_REPLACE_EXISTING_FILES |
+                               Persist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
+
+        let source = ioService.newURI(data, "UTF8", null);
+        persist.saveURI(source, null, null, null, null, file, loadContext);
+
+        div.textContent = gcli.lookup("screenshotSavedToFile") + " \"" + filename +
+                          "\"";
+        div.addEventListener("click", function openFile() {
+          div.removeEventListener("click", openFile);
+          file.reveal();
+        });
+        div.style.cursor = "pointer";
+        let image = document.createElement("div");
+        let previewHeight = parseInt(256*height/width);
+        image.setAttribute("style",
+                          "width:256px; height:" + previewHeight + "px;" +
+                          "max-height: 256px;" +
+                          "background-image: url('" + data + "');" +
+                          "background-size: 256px " + previewHeight + "px;" +
+                          "margin: 4px; display: block");
+        div.appendChild(image);
+        throw new Task.Result(div);
+      });
+    }
+  }
+];
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/gcli/commands/tools.js
@@ -0,0 +1,87 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Cc, Ci, Cu } = require("chrome");
+const Services = require("Services");
+const { OS } = require("resource://gre/modules/osfile.jsm");
+const { devtools } = require("resource://gre/modules/devtools/Loader.jsm");
+const gcli = require("gcli/index");
+
+const BRAND_SHORT_NAME = Cc["@mozilla.org/intl/stringbundle;1"]
+                           .getService(Ci.nsIStringBundleService)
+                           .createBundle("chrome://branding/locale/brand.properties")
+                           .GetStringFromName("brandShortName");
+
+exports.items = [
+  {
+    name: "tools",
+    description: gcli.lookupFormat("toolsDesc2", [ BRAND_SHORT_NAME ]),
+    manual: gcli.lookupFormat("toolsManual2", [ BRAND_SHORT_NAME ]),
+    get hidden() gcli.hiddenByChromePref(),
+  },
+  {
+    name: "tools srcdir",
+    description: gcli.lookup("toolsSrcdirDesc"),
+    manual: gcli.lookupFormat("toolsSrcdirManual2", [ BRAND_SHORT_NAME ]),
+    get hidden() gcli.hiddenByChromePref(),
+    params: [
+      {
+        name: "srcdir",
+        type: "string" /* {
+          name: "file",
+          filetype: "directory",
+          existing: "yes"
+        } */,
+        description: gcli.lookup("toolsSrcdirDir")
+      }
+    ],
+    returnType: "string",
+    exec: function(args, context) {
+      let clobber = OS.Path.join(args.srcdir, "CLOBBER");
+      return OS.File.exists(clobber).then(function(exists) {
+        if (exists) {
+          let str = Cc["@mozilla.org/supports-string;1"]
+                      .createInstance(Ci.nsISupportsString);
+          str.data = args.srcdir;
+          Services.prefs.setComplexValue("devtools.loader.srcdir",
+                                         Ci.nsISupportsString, str);
+          devtools.reload();
+
+          let msg = gcli.lookupFormat("toolsSrcdirReloaded", [ args.srcdir ]);
+          throw new Error(msg);
+        }
+
+        return gcli.lookupFormat("toolsSrcdirNotFound", [ args.srcdir ]);
+      });
+    }
+  },
+  {
+    name: "tools builtin",
+    description: gcli.lookup("toolsBuiltinDesc"),
+    manual: gcli.lookup("toolsBuiltinManual"),
+    get hidden() gcli.hiddenByChromePref(),
+    returnType: "string",
+    exec: function(args, context) {
+      Services.prefs.clearUserPref("devtools.loader.srcdir");
+      devtools.reload();
+      return gcli.lookup("toolsBuiltinReloaded");
+    }
+  },
+  {
+    name: "tools reload",
+    description: gcli.lookup("toolsReloadDesc"),
+    get hidden() {
+      return gcli.hiddenByChromePref() ||
+             !Services.prefs.prefHasUserValue("devtools.loader.srcdir");
+    },
+
+    returnType: "string",
+    exec: function(args, context) {
+      devtools.reload();
+      return gcli.lookup("toolsReloaded2");
+    }
+  }
+];
--- a/toolkit/devtools/gcli/source/lib/gcli/api.js
+++ b/toolkit/devtools/gcli/source/lib/gcli/api.js
@@ -11,16 +11,17 @@
  * 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.
  */
 
 'use strict';
 
+var promise = require('./util/promise');
 var centralCanon = require('./commands/commands').centralCanon;
 var connectors = require('./connectors/connectors');
 var converters = require('./converters/converters');
 var fields = require('./fields/fields');
 var languages = require('./languages/languages');
 var settings = require('./settings');
 var centralTypes = require('./types/types').centralTypes;
 
@@ -28,91 +29,180 @@ var centralTypes = require('./types/type
  * This is the heart of the API that we expose to the outside
  */
 exports.getApi = function() {
   var canon = centralCanon;
   var types = centralTypes;
 
   settings.startup(types);
 
+  var addItem = function(item) {
+    // Some items are registered using the constructor so we need to check
+    // the prototype for the the type of the item
+    var type = item.item;
+    if (type == null && item.prototype) {
+      type = item.prototype.item;
+    }
+    if (type === 'connector') {
+      connectors.addConnector(item);
+    }
+    else if (type === 'converter') {
+      converters.addConverter(item);
+    }
+    else if (type === 'field') {
+      fields.addField(item);
+    }
+    else if (type === 'language') {
+      languages.addLanguage(item);
+    }
+    else if (type === 'setting') {
+      settings.addSetting(item);
+    }
+    else if (type === 'type') {
+      types.addType(item);
+    }
+    else {
+      canon.addCommand(item);
+    }
+  };
+
+  var removeItem = function(item) {
+    if (item.item === 'connector') {
+      connectors.removeConnector(item);
+    }
+    else if (item.item === 'converter') {
+      converters.removeConverter(item);
+    }
+    else if (item.item === 'field') {
+      fields.removeField(item);
+    }
+    else if (item.item === 'language') {
+      languages.removeLanguage(item);
+    }
+    else if (item.item === 'settings') {
+      settings.removeSetting(types, item);
+    }
+    else if (item.item === 'type') {
+      types.removeType(item);
+    }
+    else {
+      canon.removeCommand(item);
+    }
+  };
+
+  /**
+   * loadableModules is a lookup of names to module loader functions (like
+   * the venerable 'require') to which we can pass a name and get back a
+   * JS object (or a promise of a JS object). This allows us to have custom
+   * loaders to get stuff from the filesystem etc.
+   */
+  var loadableModules = {};
+
+  /**
+   * loadedModules is a lookup by name of the things returned by the functions
+   * in loadableModules so we can track what we need to unload / reload.
+   */
+  var loadedModules = {};
+
+  var unloadModule = function(name) {
+    var existingModule = loadedModules[name];
+    if (existingModule != null) {
+      existingModule.items.forEach(removeItem);
+    }
+    delete loadedModules[name];
+  };
+
+  var loadModule = function(name) {
+    var existingModule = loadedModules[name];
+    unloadModule(name);
+
+    // And load the new items
+    try {
+      var loader = loadableModules[name];
+      return promise.resolve(loader(name)).then(function(newModule) {
+        if (existingModule === newModule) {
+          return;
+        }
+
+        if (newModule == null) {
+          throw 'Module \'' + name + '\' not found';
+        }
+
+        if (newModule.items == null || typeof newModule.items.forEach !== 'function') {
+          console.log('Exported properties: ' + Object.keys(newModule).join(', '));
+          throw 'Module \'' + name + '\' has no \'items\' array export';
+        }
+
+        newModule.items.forEach(addItem);
+
+        loadedModules[name] = newModule;
+      });
+    }
+    catch (ex) {
+      console.error(ex);
+      return promise.reject('Failure when loading \'' + name + '\'');
+    }
+  };
+
   var api = {
     addCommand: function(item) { return canon.addCommand(item); },
     removeCommand: function(item) { return canon.removeCommand(item); },
     addConnector: connectors.addConnector,
     removeConnector: connectors.removeConnector,
     addConverter: converters.addConverter,
     removeConverter: converters.removeConverter,
     addLanguage: languages.addLanguage,
     removeLanguage: languages.removeLanguage,
     addType: function(item) { return types.addType(item); },
     removeType: function(item) { return types.removeType(item); },
 
     addItems: function(items) {
-      items.forEach(function(item) {
-        // Some items are registered using the constructor so we need to check
-        // the prototype for the the type of the item
-        var type = item.item;
-        if (type == null && item.prototype) {
-          type = item.prototype.item;
-        }
-        if (type === 'command') {
-          canon.addCommand(item);
-        }
-        else if (type === 'connector') {
-          connectors.addConnector(item);
-        }
-        else if (type === 'converter') {
-          converters.addConverter(item);
+      items.forEach(addItem);
+    },
+
+    removeItems: function(items) {
+      items.forEach(removeItem);
+    },
+
+    addItemsByModule: function(names, options) {
+      options = options || {};
+      if (typeof names === 'string') {
+        names = [ names ];
+      }
+      names.forEach(function(name) {
+        if (options.loader == null) {
+          options.loader = function(name) {
+            return require(name);
+          };
         }
-        else if (type === 'field') {
-          fields.addField(item);
-        }
-        else if (type === 'language') {
-          languages.addLanguage(item);
-        }
-        else if (type === 'setting') {
-          settings.addSetting(item);
-        }
-        else if (type === 'type') {
-          types.addType(item);
-        }
-        else {
-          console.error('Error for: ', item);
-          throw new Error('item property not found');
+        loadableModules[name] = options.loader;
+
+        if (!options.delayedLoad) {
+          loadModule(name).then(null, console.error);
         }
       });
     },
 
-    removeItems: function(items) {
-      items.forEach(function(item) {
-        if (item.item === 'command') {
-          canon.removeCommand(item);
-        }
-        else if (item.item === 'connector') {
-          connectors.removeConnector(item);
-        }
-        else if (item.item === 'converter') {
-          converters.removeConverter(item);
-        }
-        else if (item.item === 'field') {
-          fields.removeField(item);
-        }
-        else if (item.item === 'language') {
-          languages.removeLanguage(item);
-        }
-        else if (item.item === 'settings') {
-          settings.removeSetting(types, item);
-        }
-        else if (item.item === 'type') {
-          types.removeType(item);
-        }
-        else {
-          throw new Error('item property not found');
-        }
+    removeItemsByModule: function(name) {
+      delete loadableModules[name];
+      unloadModule(name);
+    },
+
+    load: function() {
+      // clone loadedModules, so we can remove what is left at the end
+      var modules = Object.keys(loadedModules).map(function(name) {
+        return loadedModules[name];
       });
+
+      Object.keys(loadableModules).forEach(function(name) {
+        delete modules[name];
+        loadModule(name).then(null, console.error);
+      });
+
+      Object.keys(modules).forEach(unloadModule);
     }
   };
 
   Object.defineProperty(api, 'canon', {
     get: function() { return canon; },
     set: function(c) { canon = c; },
     enumerable: true
   });
--- a/toolkit/devtools/gcli/source/lib/gcli/util/promise.js
+++ b/toolkit/devtools/gcli/source/lib/gcli/util/promise.js
@@ -14,8 +14,12 @@
  * limitations under the License.
  */
 
 'use strict';
 
 var Cu = require('chrome').Cu;
 module.exports = exports =
     Cu.import('resource://gre/modules/commonjs/sdk/core/promise.js', {}).Promise;
+
+// When we've solved the debugger/sdk/promise/gcli/helpers/overlap problem then
+// we should use this instead:
+// module.exports = exports = require('resource://gre/modules/Promise.jsm').Promise;