Bug 1425521 - Wrap JsTerm in a React component in new frontend; r=bgrins. draft
authorNicolas Chevobbe <nchevobbe@mozilla.com>
Fri, 30 Mar 2018 17:48:50 +0200
changeset 780874 2a2134bc04ca9efbb83654c93ab4b81232acf2bd
parent 780873 2318adaec61f07eccf66c3f742497aa973b15f2f
child 780875 e7227839798980443b251a2a9ef3951440637282
push id106150
push userbmo:nchevobbe@mozilla.com
push dateThu, 12 Apr 2018 05:54:36 +0000
reviewersbgrins
bugs1425521
milestone61.0a1
Bug 1425521 - Wrap JsTerm in a React component in new frontend; r=bgrins. MozReview-Commit-ID: GGq6ZB760d9
devtools/client/themes/webconsole.css
devtools/client/webconsole/components/JSTerm.js
devtools/client/webconsole/components/moz.build
devtools/client/webconsole/new-console-output-wrapper.js
devtools/client/webconsole/new-webconsole.js
devtools/client/webconsole/test/mochitest/browser_jsterm_hide_when_devtools_chrome_enabled_false.js
devtools/client/webconsole/utils/context-menu.js
devtools/client/webconsole/webconsole.html
--- a/devtools/client/themes/webconsole.css
+++ b/devtools/client/themes/webconsole.css
@@ -348,18 +348,16 @@ a {
 
 .message[category=output] > .icon::before,
 .message.result > .icon::before {
   background-position: -60px -36px;
 }
 
 /* JSTerm Styles */
 
-html #jsterm-wrapper,
-html .jsterm-stack-node,
 html .jsterm-input-node-html,
 html #webconsole-notificationbox {
   flex: 0;
   width: 100vw;
 }
 
 .jsterm-input-container {
   background-color: var(--theme-tab-toolbar-background);
@@ -386,20 +384,16 @@ html #webconsole-notificationbox {
   /* For light theme use a white background for the input - it looks better
      than off-white */
   background-color: #fff;
   border-top-color: #e0e0e0;
 }
 
 /*  styles for the new HTML frontend */
 
-html .jsterm-stack-node {
-  position: relative;
-}
-
 textarea.jsterm-input-node,
 textarea.jsterm-complete-node {
   width: 100%;
   border: 1px solid transparent;
   margin: 0;
   background-color: transparent;
   resize: none;
   font-size: inherit;
@@ -427,16 +421,22 @@ textarea.jsterm-input-node:focus {
   outline: none;
 }
 
 :root[platform="mac"] textarea.jsterm-input-node,
 :root[platform="mac"] textarea.jsterm-complete-node {
   border-radius: 0 0 4px 4px;
 }
 
+/* Unset the bottom right radius on the jsterm inputs when the sidebar is visible */
+:root[platform="mac"] .sidebar ~ .jsterm-input-container textarea.jsterm-input-node,
+:root[platform="mac"] .sidebar ~ .jsterm-input-container textarea.jsterm-complete-node {
+  border-bottom-right-radius: 0;
+}
+
 
 /*  styles for the old frontend, which can be removed in Bug 1381834 */
 
 textbox.jsterm-input-node,
 textbox.jsterm-complete-node {
   border: none;
   padding: 0;
   padding-inline-start: 20px;
@@ -1092,24 +1092,52 @@ body {
   flex-direction: column;
 }
 
 body #output-container {
   flex: 1;
   overflow: hidden;
 }
 
+
+/*
+ * Here's what the layout of the console looks like:
+ *
+ *  +------------------------------+--------------+
+ *  | FILTER BAR                   |              |
+ *  +------------------------------+              |
+ *  |                              |              |
+ *  | CONSOLE OUTPUT               |   SIDEBAR    |
+ *  |                              |              |
+ *  +------------------------------+              |
+ *  | NOTIFICATION BOX             |              |
+ *  +------------------------------+              |
+ *  | JSTERM CONTAINER             |              |
+ *  +------------------------------+--------------+
+ */
 .webconsole-output-wrapper {
   display: grid;
-  grid-template-columns: 1fr auto;
-  grid-template-rows: auto 1fr;
+  grid-template-columns: minmax(200px, 1fr) auto;
+  grid-template-rows: auto 1fr auto auto;
   height: 100%;
   width: 100vw;
 }
 
+.webconsole-output-wrapper #webconsole-notificationbox {
+  grid-column: 1 / 2;
+  grid-row: 3 / 4;
+}
+
+.webconsole-output-wrapper .jsterm-input-container {
+  grid-column: 1 / 2;
+  grid-row: -1 / -2;
+  position: relative;
+  z-index: 1;
+}
+
 /* Object Inspector */
 .webconsole-output-wrapper .object-inspector.tree {
   display: inline-block;
 }
 
 .webconsole-output-wrapper .object-inspector.tree .tree-indent {
   border-inline-start-color: var(--console-output-indent-border-color);
 }
copy from devtools/client/webconsole/jsterm.js
copy to devtools/client/webconsole/components/JSTerm.js
--- a/devtools/client/webconsole/jsterm.js
+++ b/devtools/client/webconsole/components/JSTerm.js
@@ -1,90 +1,214 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 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 {Utils: WebConsoleUtils} =
-  require("devtools/client/webconsole/utils");
+const {Utils: WebConsoleUtils} = require("devtools/client/webconsole/utils");
 const defer = require("devtools/shared/defer");
 const Debugger = require("Debugger");
 const Services = require("Services");
 const {KeyCodes} = require("devtools/client/shared/keycodes");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 
 loader.lazyServiceGetter(this, "clipboardHelper",
                          "@mozilla.org/widget/clipboardhelper;1",
                          "nsIClipboardHelper");
 loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
 loader.lazyRequireGetter(this, "AutocompletePopup", "devtools/client/shared/autocomplete-popup");
-loader.lazyRequireGetter(this, "ToolSidebar", "devtools/client/framework/sidebar", true);
-loader.lazyRequireGetter(this, "Messages", "devtools/client/webconsole/old/console-output", true);
 loader.lazyRequireGetter(this, "asyncStorage", "devtools/shared/async-storage");
-loader.lazyRequireGetter(this, "EnvironmentClient", "devtools/shared/client/environment-client");
-loader.lazyRequireGetter(this, "ObjectClient", "devtools/shared/client/object-client");
-loader.lazyImporter(this, "VariablesView", "resource://devtools/client/shared/widgets/VariablesView.jsm");
-loader.lazyImporter(this, "VariablesViewController", "resource://devtools/client/shared/widgets/VariablesViewController.jsm");
 loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
 loader.lazyRequireGetter(this, "NotificationBox", "devtools/client/shared/components/NotificationBox", true);
 loader.lazyRequireGetter(this, "PriorityLevels", "devtools/client/shared/components/NotificationBox", true);
 
 const l10n = require("devtools/client/webconsole/webconsole-l10n");
 
 // Constants used for defining the direction of JSTerm input history navigation.
 const HISTORY_BACK = -1;
 const HISTORY_FORWARD = 1;
 
-const XHTML_NS = "http://www.w3.org/1999/xhtml";
-
 const HELP_URL = "https://developer.mozilla.org/docs/Tools/Web_Console/Helpers";
 
-const VARIABLES_VIEW_URL = "chrome://devtools/content/shared/widgets/VariablesView.xul";
-
 const PREF_INPUT_HISTORY_COUNT = "devtools.webconsole.inputHistoryCount";
 const PREF_AUTO_MULTILINE = "devtools.webconsole.autoMultiline";
 
+function gSequenceId() {
+  return gSequenceId.n++;
+}
+gSequenceId.n = 0;
+
+const { Component } = require("devtools/client/shared/vendor/react");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+
 /**
  * Create a JSTerminal (a JavaScript command line). This is attached to an
  * existing HeadsUpDisplay (a Web Console instance). This code is responsible
- * with handling command line input, code evaluation and result output.
+ * with handling command line input and code evaluation.
  *
  * @constructor
  * @param object webConsoleFrame
  *        The WebConsoleFrame object that owns this JSTerm instance.
  */
-function JSTerm(webConsoleFrame) {
-  this.hud = webConsoleFrame;
-  this.hudId = this.hud.hudId;
-  this.inputHistoryCount = Services.prefs.getIntPref(PREF_INPUT_HISTORY_COUNT);
+class JSTerm extends Component {
+  static get propTypes() {
+    return {
+      hud: PropTypes.object.isRequired,
+    };
+  }
+
+  constructor(props) {
+    super(props);
+
+    const {
+      hud,
+    } = props;
+    this.hud = hud;
+    this.hudId = this.hud.hudId;
+    this.inputHistoryCount = Services.prefs.getIntPref(PREF_INPUT_HISTORY_COUNT);
+    this._loadHistory();
+
+    /**
+     * Stores the data for the last completion.
+     * @type object
+     */
+    this.lastCompletion = { value: null };
+
+    this._keyPress = this._keyPress.bind(this);
+    this._inputEventHandler = this._inputEventHandler.bind(this);
+    this._focusEventHandler = this._focusEventHandler.bind(this);
+    this._blurEventHandler = this._blurEventHandler.bind(this);
+
+    this.SELECTED_FRAME = -1;
 
-  this.lastCompletion = { value: null };
-  this._loadHistory();
+    /**
+     * Array that caches the user input suggestions received from the server.
+     * @private
+     * @type array
+     */
+    this._autocompleteCache = null;
+
+    /**
+     * The input that caused the last request to the server, whose response is
+     * cached in the _autocompleteCache array.
+     * @private
+     * @type string
+     */
+    this._autocompleteQuery = null;
 
-  this._objectActorsInVariablesViews = new Map();
+    /**
+     * The frameActorId used in the last autocomplete query. Whenever this changes
+     * the autocomplete cache must be invalidated.
+     * @private
+     * @type string
+     */
+    this._lastFrameActorId = null;
+
+    /**
+     * Last input value.
+     * @type string
+     */
+    this.lastInputValue = "";
+
+    /**
+     * Tells if the input node changed since the last focus.
+     *
+     * @private
+     * @type boolean
+     */
+    this._inputChanged = false;
 
-  this._keyPress = this._keyPress.bind(this);
-  this._inputEventHandler = this._inputEventHandler.bind(this);
-  this._focusEventHandler = this._focusEventHandler.bind(this);
-  this._onKeypressInVariablesView = this._onKeypressInVariablesView.bind(this);
-  this._blurEventHandler = this._blurEventHandler.bind(this);
+    /**
+     * Tells if the autocomplete popup was navigated since the last open.
+     *
+     * @private
+     * @type boolean
+     */
+    this._autocompletePopupNavigated = false;
+
+    /**
+     * History of code that was executed.
+     * @type array
+     */
+    this.history = [];
+    this.autocompletePopup = null;
+    this.inputNode = null;
+    this.completeNode = null;
+
+    this.COMPLETE_FORWARD = 0;
+    this.COMPLETE_BACKWARD = 1;
+    this.COMPLETE_HINT_ONLY = 2;
+    this.COMPLETE_PAGEUP = 3;
+    this.COMPLETE_PAGEDOWN = 4;
+
+    EventEmitter.decorate(this);
+    hud.jsterm = this;
+  }
+
+  componentDidMount() {
+    if (!this.inputNode) {
+      return;
+    }
+
+    let autocompleteOptions = {
+      onSelect: this.onAutocompleteSelect.bind(this),
+      onClick: this.acceptProposedCompletion.bind(this),
+      listId: "webConsole_autocompletePopupListBox",
+      position: "top",
+      theme: "auto",
+      autoSelect: true
+    };
 
-  EventEmitter.decorate(this);
-}
+    let doc = this.hud.document;
+    let toolbox = gDevTools.getToolbox(this.hud.owner.target);
+    let tooltipDoc = toolbox ? toolbox.doc : doc;
+    // The popup will be attached to the toolbox document or HUD document in the case
+    // such as the browser console which doesn't have a toolbox.
+    this.autocompletePopup = new AutocompletePopup(tooltipDoc, autocompleteOptions);
+
+    this.inputBorderSize = this.inputNode.getBoundingClientRect().height -
+                           this.inputNode.clientHeight;
+
+    // Update the character width and height needed for the popup offset
+    // calculations.
+    this._updateCharSize();
+
+    this.inputNode.addEventListener("keypress", this._keyPress);
+    this.inputNode.addEventListener("input", this._inputEventHandler);
+    this.inputNode.addEventListener("keyup", this._inputEventHandler);
+    this.inputNode.addEventListener("focus", this._focusEventHandler);
 
-JSTerm.prototype = {
-  SELECTED_FRAME: -1,
+    if (!this.hud.isBrowserConsole) {
+      let okstring = l10n.getStr("selfxss.okstring");
+      let msg = l10n.getFormatStr("selfxss.msg", [okstring]);
+      this._onPaste = WebConsoleUtils.pasteHandlerGen(this.inputNode,
+          this.getNotificationBox(), msg, okstring);
+      this.inputNode.addEventListener("paste", this._onPaste);
+      this.inputNode.addEventListener("drop", this._onPaste);
+    }
+
+    this.hud.window.addEventListener("blur", this._blurEventHandler);
+    this.lastInputValue && this.setInputValue(this.lastInputValue);
+
+    this.focus();
+  }
+
+  shouldComponentUpdate() {
+    // XXX: For now, everything is handled in an imperative way and we only want React
+    // to do the initial rendering of the component.
+    // This should be modified when the actual refactoring will take place.
+    return false;
+  }
 
   /**
    * Load the console history from previous sessions.
    * @private
    */
-  _loadHistory: function() {
+  _loadHistory() {
     this.history = [];
     this.historyIndex = this.historyPlaceHolder = 0;
 
     this.historyLoaded = asyncStorage.getItem("webConsoleHistory")
       .then(value => {
         if (Array.isArray(value)) {
           // Since it was gotten asynchronously, there could be items already in
           // the history.  It's not likely but stick them onto the end anyway.
@@ -95,250 +219,83 @@ JSTerm.prototype = {
           this.historyIndex = this.history.length;
 
           // Holds the index of the history entry that the user is currently
           // viewing. This is reset to this.history.length when this.execute()
           // is invoked.
           this.historyPlaceHolder = this.history.length;
         }
       }, console.error);
-  },
+  }
 
   /**
    * Clear the console history altogether.  Note that this will not affect
    * other consoles that are already opened (since they have their own copy),
    * but it will reset the array for all newly-opened consoles.
    * @returns Promise
    *          Resolves once the changes have been persisted.
    */
-  clearHistory: function() {
+  clearHistory() {
     this.history = [];
     this.historyIndex = this.historyPlaceHolder = 0;
     return this.storeHistory();
-  },
+  }
 
   /**
    * Stores the console history for future console instances.
    * @returns Promise
    *          Resolves once the changes have been persisted.
    */
-  storeHistory: function() {
+  storeHistory() {
     return asyncStorage.setItem("webConsoleHistory", this.history);
-  },
-
-  /**
-   * Stores the data for the last completion.
-   * @type object
-   */
-  lastCompletion: null,
-
-  /**
-   * Array that caches the user input suggestions received from the server.
-   * @private
-   * @type array
-   */
-  _autocompleteCache: null,
-
-  /**
-   * The input that caused the last request to the server, whose response is
-   * cached in the _autocompleteCache array.
-   * @private
-   * @type string
-   */
-  _autocompleteQuery: null,
-
-  /**
-   * The frameActorId used in the last autocomplete query. Whenever this changes
-   * the autocomplete cache must be invalidated.
-   * @private
-   * @type string
-   */
-  _lastFrameActorId: null,
-
-  /**
-   * The Web Console sidebar.
-   * @see this._createSidebar()
-   * @see Sidebar.jsm
-   */
-  sidebar: null,
-
-  /**
-   * The Variables View instance shown in the sidebar.
-   * @private
-   * @type object
-   */
-  _variablesView: null,
-
-  /**
-   * Tells if you want the variables view UI updates to be lazy or not. Tests
-   * disable lazy updates.
-   *
-   * @private
-   * @type boolean
-   */
-  _lazyVariablesView: true,
-
-  /**
-   * Holds a map between VariablesView instances and sets of ObjectActor IDs
-   * that have been retrieved from the server. This allows us to release the
-   * objects when needed.
-   *
-   * @private
-   * @type Map
-   */
-  _objectActorsInVariablesViews: null,
-
-  /**
-   * Last input value.
-   * @type string
-   */
-  lastInputValue: "",
-
-  /**
-   * Tells if the input node changed since the last focus.
-   *
-   * @private
-   * @type boolean
-   */
-  _inputChanged: false,
-
-  /**
-   * Tells if the autocomplete popup was navigated since the last open.
-   *
-   * @private
-   * @type boolean
-   */
-  _autocompletePopupNavigated: false,
-
-  /**
-   * History of code that was executed.
-   * @type array
-   */
-  history: null,
-  autocompletePopup: null,
-  inputNode: null,
-  completeNode: null,
+  }
 
   /**
    * Getter for the element that holds the messages we display.
    * @type nsIDOMElement
    */
   get outputNode() {
     return this.hud.outputNode;
-  },
+  }
 
   /**
    * Getter for the debugger WebConsoleClient.
    * @type object
    */
   get webConsoleClient() {
     return this.hud.webConsoleClient;
-  },
-
-  COMPLETE_FORWARD: 0,
-  COMPLETE_BACKWARD: 1,
-  COMPLETE_HINT_ONLY: 2,
-  COMPLETE_PAGEUP: 3,
-  COMPLETE_PAGEDOWN: 4,
-
-  /**
-   * Initialize the JSTerminal UI.
-   */
-  init: function() {
-    let autocompleteOptions = {
-      onSelect: this.onAutocompleteSelect.bind(this),
-      onClick: this.acceptProposedCompletion.bind(this),
-      listId: "webConsole_autocompletePopupListBox",
-      position: "top",
-      theme: "auto",
-      autoSelect: true
-    };
+  }
 
-    let doc = this.hud.document;
-    let toolbox = gDevTools.getToolbox(this.hud.owner.target);
-    let tooltipDoc = toolbox ? toolbox.doc : doc;
-    // The popup will be attached to the toolbox document or HUD document in the case
-    // such as the browser console which doesn't have a toolbox.
-    this.autocompletePopup = new AutocompletePopup(tooltipDoc, autocompleteOptions);
-    let inputContainer = doc.querySelector(".jsterm-input-container");
-    this.completeNode = doc.querySelector(".jsterm-complete-node");
-    this.inputNode = doc.querySelector(".jsterm-input-node");
-    this.inputBorderSize = this.inputNode.getBoundingClientRect().height -
-                           this.inputNode.clientHeight;
-    // Update the character width and height needed for the popup offset
-    // calculations.
-    this._updateCharSize();
-
-    if (this.hud.isBrowserConsole &&
-        !Services.prefs.getBoolPref("devtools.chrome.enabled")) {
-      inputContainer.style.display = "none";
-    } else {
-      this.inputNode.addEventListener("keypress", this._keyPress);
-      this.inputNode.addEventListener("input", this._inputEventHandler);
-      this.inputNode.addEventListener("keyup", this._inputEventHandler);
-      this.inputNode.addEventListener("focus", this._focusEventHandler);
-    }
-
-    if (!this.hud.isBrowserConsole) {
-      let okstring = l10n.getStr("selfxss.okstring");
-      let msg = l10n.getFormatStr("selfxss.msg", [okstring]);
-      this._onPaste = WebConsoleUtils.pasteHandlerGen(this.inputNode,
-          this.getNotificationBox(), msg, okstring);
-      this.inputNode.addEventListener("paste", this._onPaste);
-      this.inputNode.addEventListener("drop", this._onPaste);
-    }
-
-    this.hud.window.addEventListener("blur", this._blurEventHandler);
-    this.lastInputValue && this.setInputValue(this.lastInputValue);
-  },
-
-  focus: function() {
-    if (!this.inputNode.getAttribute("focused")) {
+  focus() {
+    if (this.inputNode && !this.inputNode.getAttribute("focused")) {
       this.inputNode.focus();
     }
-  },
+  }
 
   /**
    * The JavaScript evaluation response handler.
    *
    * @private
    * @param function [callback]
    *        Optional function to invoke when the evaluation result is added to
    *        the output.
    * @param object response
    *        The message received from the server.
    */
-  _executeResultCallback: function(callback, response) {
+  _executeResultCallback(callback, response) {
     if (!this.hud) {
       return;
     }
     if (response.error) {
-      console.error("Evaluation error " + response.error + ": " +
-                    response.message);
+      console.error("Evaluation error " + response.error + ": " + response.message);
       return;
     }
     let errorMessage = response.exceptionMessage;
-    let errorDocURL = response.exceptionDocURL;
 
-    let errorDocLink;
-    if (errorDocURL) {
-      errorMessage += " ";
-      errorDocLink = this.hud.document.createElementNS(XHTML_NS, "a");
-      errorDocLink.className = "learn-more-link webconsole-learn-more-link";
-      errorDocLink.textContent = `[${l10n.getStr("webConsoleMoreInfoLabel")}]`;
-      errorDocLink.title = errorDocURL.split("?")[0];
-      errorDocLink.href = "#";
-      errorDocLink.draggable = false;
-      errorDocLink.addEventListener("click", () => {
-        this.hud.owner.openLink(errorDocURL);
-      });
-    }
-
-    // Wrap thrown strings in Error objects, so `throw "foo"` outputs
-    // "Error: foo"
+    // Wrap thrown strings in Error objects, so `throw "foo"` outputs "Error: foo"
     if (typeof response.exception === "string") {
       errorMessage = new Error(errorMessage).toString();
     }
     let result = response.result;
     let helperResult = response.helperResult;
     let helperHasRawOutput = !!(helperResult || {}).rawOutput;
 
     if (helperResult && helperResult.type) {
@@ -371,91 +328,46 @@ JSTerm.prototype = {
     // Hide undefined results coming from JSTerm helper functions.
     if (!errorMessage && result && typeof result == "object" &&
       result.type == "undefined" &&
       helperResult && !helperHasRawOutput) {
       callback && callback();
       return;
     }
 
-    if (this.hud.NEW_CONSOLE_OUTPUT_ENABLED) {
+    if (this.hud.newConsoleOutput) {
       this.hud.newConsoleOutput.dispatchMessageAdd(response, true).then(callback);
-      return;
     }
-    let msg = new Messages.JavaScriptEvalOutput(response,
-                                                errorMessage, errorDocLink);
-    this.hud.output.addMessage(msg);
-
-    if (callback) {
-      let oldFlushCallback = this.hud._flushCallback;
-      this.hud._flushCallback = () => {
-        callback(msg.element);
-        if (oldFlushCallback) {
-          oldFlushCallback();
-          this.hud._flushCallback = oldFlushCallback;
-          return true;
-        }
-
-        return false;
-      };
-    }
-
-    msg._objectActors = new Set();
+  }
 
-    if (WebConsoleUtils.isActorGrip(response.exception)) {
-      msg._objectActors.add(response.exception.actor);
-    }
-
-    if (WebConsoleUtils.isActorGrip(result)) {
-      msg._objectActors.add(result.actor);
-    }
-  },
-
-  inspectObjectActor: function(objectActor) {
-    if (this.hud.NEW_CONSOLE_OUTPUT_ENABLED) {
-      this.hud.newConsoleOutput.dispatchMessageAdd({
-        helperResult: {
-          type: "inspectObject",
-          object: objectActor
-        }
-      }, true);
-      return this.hud.newConsoleOutput;
-    }
-
-    return this.openVariablesView({
-      objectActor,
-      label: VariablesView.getString(objectActor, {concise: true}),
-    });
-  },
+  inspectObjectActor(objectActor) {
+    this.hud.newConsoleOutput.dispatchMessageAdd({
+      helperResult: {
+        type: "inspectObject",
+        object: objectActor
+      }
+    }, true);
+    return this.hud.newConsoleOutput;
+  }
 
   /**
    * Execute a string. Execution happens asynchronously in the content process.
    *
    * @param string [executeString]
    *        The string you want to execute. If this is not provided, the current
    *        user input is used - taken from |this.getInputValue()|.
    * @param function [callback]
    *        Optional function to invoke when the result is displayed.
    *        This is deprecated - please use the promise return value instead.
    * @returns Promise
    *          Resolves with the message once the result is displayed.
    */
-  execute: async function(executeString, callback) {
+  async execute(executeString, callback) {
     let deferred = defer();
-    let resultCallback;
-    if (this.hud.NEW_CONSOLE_OUTPUT_ENABLED) {
-      resultCallback = (msg) => deferred.resolve(msg);
-    } else {
-      resultCallback = (msg) => {
-        deferred.resolve(msg);
-        if (callback) {
-          callback(msg);
-        }
-      };
-    }
+    let resultCallback = msg => deferred.resolve(msg);
 
     // attempt to execute the content of the inputNode
     executeString = executeString || this.getInputValue();
     if (!executeString) {
       return null;
     }
 
     // Append a new value in the history of executed code, or overwrite the most
@@ -474,67 +386,60 @@ JSTerm.prototype = {
     this.clearCompletion();
 
     let selectedNodeActor = null;
     let inspectorSelection = this.hud.owner.getInspectorSelection();
     if (inspectorSelection && inspectorSelection.nodeFront) {
       selectedNodeActor = inspectorSelection.nodeFront.actorID;
     }
 
-    if (this.hud.NEW_CONSOLE_OUTPUT_ENABLED) {
-      const { ConsoleCommand } = require("devtools/client/webconsole/types");
-      let message = new ConsoleCommand({
-        messageText: executeString,
-      });
-      this.hud.proxy.dispatchMessageAdd(message);
-    } else {
-      let message = new Messages.Simple(executeString, {
-        category: "input",
-        severity: "log",
-      });
-      this.hud.output.addMessage(message);
-    }
+    const { ConsoleCommand } = require("devtools/client/webconsole/types");
+    let message = new ConsoleCommand({
+      messageText: executeString,
+    });
+    this.hud.proxy.dispatchMessageAdd(message);
+
     let onResult = this._executeResultCallback.bind(this, resultCallback);
 
     let options = {
       frame: this.SELECTED_FRAME,
       selectedNodeActor: selectedNodeActor,
     };
 
     const mappedString = await this.hud.owner.getMappedExpression(executeString);
     this.requestEvaluation(mappedString, options).then(onResult, onResult);
 
     return deferred.promise;
-  },
+  }
 
   /**
    * Request a JavaScript string evaluation from the server.
    *
    * @param string str
    *        String to execute.
    * @param object [options]
    *        Options for evaluation:
    *        - bindObjectActor: tells the ObjectActor ID for which you want to do
    *        the evaluation. The Debugger.Object of the OA will be bound to
    *        |_self| during evaluation, such that it's usable in the string you
    *        execute.
    *        - frame: tells the stackframe depth to evaluate the string in. If
    *        the jsdebugger is paused, you can pick the stackframe to be used for
-   *        evaluation. Use |this.SELECTED_FRAME| to always pick the
+   *        evaluation. Use |this.SELECTED_FRAME| to always pick th;
    *        user-selected stackframe.
    *        If you do not provide a |frame| the string will be evaluated in the
    *        global content window.
    *        - selectedNodeActor: tells the NodeActor ID of the current selection
    *        in the Inspector, if such a selection exists. This is used by
    *        helper functions that can evaluate on the current selection.
    * @return object
    *         A promise object that is resolved when the server response is
    *         received.
    */
-  requestEvaluation: function(str, options = {}) {
+  requestEvaluation(str, options = {}) {
     let deferred = defer();
 
     function onResult(response) {
       if (!response.error) {
         deferred.resolve(response);
       } else {
         deferred.reject(response);
       }
@@ -549,563 +454,179 @@ JSTerm.prototype = {
       bindObjectActor: options.bindObjectActor,
       frameActor: frameActor,
       selectedNodeActor: options.selectedNodeActor,
       selectedObjectActor: options.selectedObjectActor,
     };
 
     this.webConsoleClient.evaluateJSAsync(str, onResult, evalOptions);
     return deferred.promise;
-  },
+  }
 
   /**
    * Copy the object/variable by invoking the server
    * which invokes the `copy(variable)` command and makes it
    * available in the clipboard
    * @param evalString - string which has the evaluation string to be copied
    * @param options - object - Options for evaluation
    * @return object
    *         A promise object that is resolved when the server response is
    *         received.
    */
-  copyObject: function(evalString, evalOptions) {
+  copyObject(evalString, evalOptions) {
     return this.webConsoleClient.evaluateJSAsync(`copy(${evalString})`,
       null, evalOptions);
-  },
+  }
 
   /**
    * Retrieve the FrameActor ID given a frame depth.
    *
    * @param number frame
    *        Frame depth.
    * @return string|null
    *         The FrameActor ID for the given frame depth.
    */
-  getFrameActor: function(frame) {
+  getFrameActor(frame) {
     let state = this.hud.owner.getDebuggerFrames();
     if (!state) {
       return null;
     }
 
     let grip;
     if (frame == this.SELECTED_FRAME) {
       grip = state.frames[state.selected];
     } else {
       grip = state.frames[frame];
     }
 
     return grip ? grip.actor : null;
-  },
-
-  /**
-   * Opens a new variables view that allows the inspection of the given object.
-   *
-   * @param object options
-   *        Options for the variables view:
-   *        - objectActor: grip of the ObjectActor you want to show in the
-   *        variables view.
-   *        - rawObject: the raw object you want to show in the variables view.
-   *        - label: label to display in the variables view for inspected
-   *        object.
-   *        - hideFilterInput: optional boolean, |true| if you want to hide the
-   *        variables view filter input.
-   *        - targetElement: optional nsIDOMElement to append the variables view
-   *        to. An iframe element is used as a container for the view. If this
-   *        option is not used, then the variables view opens in the sidebar.
-   *        - autofocus: optional boolean, |true| if you want to give focus to
-   *        the variables view window after open, |false| otherwise.
-   * @return object
-   *         A promise object that is resolved when the variables view has
-   *         opened. The new variables view instance is given to the callbacks.
-   */
-  openVariablesView: function(options) {
-    // Bail out if the side bar doesn't exist.
-    if (!this.hud.document.querySelector("#webconsole-sidebar")) {
-      return Promise.resolve(null);
-    }
-
-    let onContainerReady = (window) => {
-      let container = window.document.querySelector("#variables");
-      let view = this._variablesView;
-      if (!view || options.targetElement) {
-        let viewOptions = {
-          container: container,
-          hideFilterInput: options.hideFilterInput,
-        };
-        view = this._createVariablesView(viewOptions);
-        if (!options.targetElement) {
-          this._variablesView = view;
-          window.addEventListener("keypress", this._onKeypressInVariablesView);
-        }
-      }
-      options.view = view;
-      this._updateVariablesView(options);
-
-      if (!options.targetElement && options.autofocus) {
-        window.focus();
-      }
-
-      this.emit("variablesview-open", {view, options});
-      return view;
-    };
-
-    let openPromise;
-    if (options.targetElement) {
-      let deferred = defer();
-      openPromise = deferred.promise;
-      let document = options.targetElement.ownerDocument;
-      let iframe = document.createElementNS(XHTML_NS, "iframe");
-
-      iframe.addEventListener("load", function() {
-        iframe.style.visibility = "visible";
-        deferred.resolve(iframe.contentWindow);
-      }, {capture: true, once: true});
-
-      iframe.flex = 1;
-      iframe.style.visibility = "hidden";
-      iframe.setAttribute("src", VARIABLES_VIEW_URL);
-      options.targetElement.appendChild(iframe);
-    } else {
-      if (!this.sidebar) {
-        this._createSidebar();
-      }
-      openPromise = this._addVariablesViewSidebarTab();
-    }
-
-    return openPromise.then(onContainerReady);
-  },
-
-  /**
-   * Create the Web Console sidebar.
-   *
-   * @see devtools/framework/sidebar.js
-   * @private
-   */
-  _createSidebar: function() {
-    let tabbox = this.hud.document.querySelector("#webconsole-sidebar");
-    this.sidebar = new ToolSidebar(tabbox, this, "webconsole");
-    this.sidebar.show();
-    this.emit("sidebar-opened");
-  },
-
-  /**
-   * Add the variables view tab to the sidebar.
-   *
-   * @private
-   * @return object
-   *         A promise object for the adding of the new tab.
-   */
-  _addVariablesViewSidebarTab: function() {
-    let deferred = defer();
-
-    let onTabReady = () => {
-      let window = this.sidebar.getWindowForTab("variablesview");
-      deferred.resolve(window);
-    };
-
-    let tabPanel = this.sidebar.getTabPanel("variablesview");
-    if (tabPanel) {
-      if (this.sidebar.getCurrentTabID() == "variablesview") {
-        onTabReady();
-      } else {
-        this.sidebar.once("variablesview-selected", onTabReady);
-        this.sidebar.select("variablesview");
-      }
-    } else {
-      this.sidebar.once("variablesview-ready", onTabReady);
-      this.sidebar.addTab("variablesview", VARIABLES_VIEW_URL, {selected: true});
-    }
-
-    return deferred.promise;
-  },
-
-  /**
-   * The keypress event handler for the Variables View sidebar. Currently this
-   * is used for removing the sidebar when Escape is pressed.
-   *
-   * @private
-   * @param nsIDOMEvent event
-   *        The keypress DOM event object.
-   */
-  _onKeypressInVariablesView: function(event) {
-    let tag = event.target.nodeName;
-    if (event.keyCode != KeyCodes.DOM_VK_ESCAPE || event.shiftKey ||
-        event.altKey || event.ctrlKey || event.metaKey ||
-        ["input", "textarea", "select", "textbox"].indexOf(tag) > -1) {
-      return;
-    }
-
-    this._sidebarDestroy();
-    this.focus();
-    event.stopPropagation();
-  },
-
-  /**
-   * Create a variables view instance.
-   *
-   * @private
-   * @param object options
-   *        Options for the new Variables View instance:
-   *        - container: the DOM element where the variables view is inserted.
-   *        - hideFilterInput: boolean, if true the variables filter input is
-   *        hidden.
-   * @return object
-   *         The new Variables View instance.
-   */
-  _createVariablesView: function(options) {
-    let view = new VariablesView(options.container);
-    view.toolbox = gDevTools.getToolbox(this.hud.owner.target);
-    view.searchPlaceholder = l10n.getStr("propertiesFilterPlaceholder");
-    view.emptyText = l10n.getStr("emptyPropertiesList");
-    view.searchEnabled = !options.hideFilterInput;
-    view.lazyEmpty = this._lazyVariablesView;
-
-    VariablesViewController.attach(view, {
-      getEnvironmentClient: grip => {
-        return new EnvironmentClient(this.hud.proxy.client, grip);
-      },
-      getObjectClient: grip => {
-        return new ObjectClient(this.hud.proxy.client, grip);
-      },
-      getLongStringClient: grip => {
-        return this.webConsoleClient.longString(grip);
-      },
-      releaseActor: actor => {
-        this.hud._releaseObject(actor);
-      },
-      simpleValueEvalMacro: simpleValueEvalMacro,
-      overrideValueEvalMacro: overrideValueEvalMacro,
-      getterOrSetterEvalMacro: getterOrSetterEvalMacro,
-    });
-
-    // Relay events from the VariablesView.
-    view.on("fetched", (type, variableObject) => {
-      this.emit("variablesview-fetched", variableObject);
-    });
-
-    return view;
-  },
-
-  /**
-   * Update the variables view.
-   *
-   * @private
-   * @param object options
-   *        Options for updating the variables view:
-   *        - view: the view you want to update.
-   *        - objectActor: the grip of the new ObjectActor you want to show in
-   *        the view.
-   *        - rawObject: the new raw object you want to show.
-   *        - label: the new label for the inspected object.
-   */
-  _updateVariablesView: function(options) {
-    let view = options.view;
-    view.empty();
-
-    // We need to avoid pruning the object inspection starting point.
-    // That one is pruned when the console message is removed.
-    view.controller.releaseActors(actor => {
-      return view._consoleLastObjectActor != actor;
-    });
-
-    if (options.objectActor &&
-        (!this.hud.isBrowserConsole ||
-         Services.prefs.getBoolPref("devtools.chrome.enabled"))) {
-      // Make sure eval works in the correct context.
-      view.eval = this._variablesViewEvaluate.bind(this, options);
-      view.switch = this._variablesViewSwitch.bind(this, options);
-      view.delete = this._variablesViewDelete.bind(this, options);
-    } else {
-      view.eval = null;
-      view.switch = null;
-      view.delete = null;
-    }
-
-    let { variable, expanded } = view.controller.setSingleVariable(options);
-    variable.evaluationMacro = simpleValueEvalMacro;
-
-    if (options.objectActor) {
-      view._consoleLastObjectActor = options.objectActor.actor;
-    } else if (options.rawObject) {
-      view._consoleLastObjectActor = null;
-    } else {
-      throw new Error(
-        "Variables View cannot open without giving it an object display.");
-    }
-
-    expanded.then(() => {
-      this.emit("variablesview-updated", view, options);
-    });
-  },
-
-  /**
-   * The evaluation function used by the variables view when editing a property
-   * value.
-   *
-   * @private
-   * @param object options
-   *        The options used for |this._updateVariablesView()|.
-   * @param object variableObject
-   *        The Variable object instance for the edited property.
-   * @param string value
-   *        The value the edited property was changed to.
-   */
-  _variablesViewEvaluate: function(options, variableObject, value) {
-    let updater = this._updateVariablesView.bind(this, options);
-    let onEval = this._silentEvalCallback.bind(this, updater);
-    let string = variableObject.evaluationMacro(variableObject, value);
-
-    let evalOptions = {
-      frame: this.SELECTED_FRAME,
-      bindObjectActor: options.objectActor.actor,
-    };
-
-    this.requestEvaluation(string, evalOptions).then(onEval, onEval);
-  },
-
-  /**
-   * The property deletion function used by the variables view when a property
-   * is deleted.
-   *
-   * @private
-   * @param object options
-   *        The options used for |this._updateVariablesView()|.
-   * @param object variableObject
-   *        The Variable object instance for the deleted property.
-   */
-  _variablesViewDelete: function(options, variableObject) {
-    let onEval = this._silentEvalCallback.bind(this, null);
-
-    let evalOptions = {
-      frame: this.SELECTED_FRAME,
-      bindObjectActor: options.objectActor.actor,
-    };
-
-    this.requestEvaluation("delete _self" +
-      variableObject.symbolicName, evalOptions).then(onEval, onEval);
-  },
-
-  /**
-   * The property rename function used by the variables view when a property
-   * is renamed.
-   *
-   * @private
-   * @param object options
-   *        The options used for |this._updateVariablesView()|.
-   * @param object variableObject
-   *        The Variable object instance for the renamed property.
-   * @param string newName
-   *        The new name for the property.
-   */
-  _variablesViewSwitch: function(options, variableObject, newName) {
-    let updater = this._updateVariablesView.bind(this, options);
-    let onEval = this._silentEvalCallback.bind(this, updater);
-
-    let evalOptions = {
-      frame: this.SELECTED_FRAME,
-      bindObjectActor: options.objectActor.actor,
-    };
-
-    let newSymbolicName =
-      variableObject.ownerView.symbolicName + '["' + newName + '"]';
-    if (newSymbolicName == variableObject.symbolicName) {
-      return;
-    }
-
-    let code = "_self" + newSymbolicName + " = _self" +
-      variableObject.symbolicName + ";" + "delete _self" +
-      variableObject.symbolicName;
-
-    this.requestEvaluation(code, evalOptions).then(onEval, onEval);
-  },
-
-  /**
-   * A noop callback for JavaScript evaluation. This method releases any
-   * result ObjectActors that come from the server for evaluation requests. This
-   * is used for editing, renaming and deleting properties in the variables
-   * view.
-   *
-   * Exceptions are displayed in the output.
-   *
-   * @private
-   * @param function callback
-   *        Function to invoke once the response is received.
-   * @param object response
-   *        The response packet received from the server.
-   */
-  _silentEvalCallback: function(callback, response) {
-    if (response.error) {
-      console.error("Web Console evaluation failed. " + response.error + ":" +
-                    response.message);
-
-      callback && callback(response);
-      return;
-    }
-
-    if (response.exceptionMessage) {
-      let message = new Messages.Simple(response.exceptionMessage, {
-        category: "output",
-        severity: "error",
-        timestamp: response.timestamp,
-      });
-      this.hud.output.addMessage(message);
-      message._objectActors = new Set();
-      if (WebConsoleUtils.isActorGrip(response.exception)) {
-        message._objectActors.add(response.exception.actor);
-      }
-    }
-
-    let helper = response.helperResult || { type: null };
-    let helperGrip = null;
-    if (helper.type == "inspectObject") {
-      helperGrip = helper.object;
-    }
-
-    let grips = [response.result, helperGrip];
-    for (let grip of grips) {
-      if (WebConsoleUtils.isActorGrip(grip)) {
-        this.hud._releaseObject(grip.actor);
-      }
-    }
-
-    callback && callback(response);
-  },
+  }
 
   /**
    * Clear the Web Console output.
    *
    * This method emits the "messages-cleared" notification.
    *
    * @param boolean clearStorage
    *        True if you want to clear the console messages storage associated to
    *        this Web Console.
    */
-  clearOutput: function(clearStorage) {
-    let hud = this.hud;
+  clearOutput(clearStorage) {
+    if (this.hud && this.hud.newConsoleOutput) {
+      this.hud.newConsoleOutput.dispatchMessagesClear();
+    }
 
-    if (hud.NEW_CONSOLE_OUTPUT_ENABLED) {
-      hud.newConsoleOutput.dispatchMessagesClear();
-    } else {
-      let outputNode = hud.outputNode;
-      let node;
-      while ((node = outputNode.firstChild)) {
-        hud.removeOutputMessage(node);
-      }
-
-      hud.groupDepth = 0;
-      hud._outputQueue.forEach(hud._destroyItem, hud);
-      hud._outputQueue = [];
-      hud._repeatNodes = {};
-    }
     this.webConsoleClient.clearNetworkRequests();
     if (clearStorage) {
       this.webConsoleClient.clearMessagesCache();
     }
-    this._sidebarDestroy();
     this.focus();
     this.emit("messages-cleared");
-  },
+  }
 
   /**
    * Remove all of the private messages from the Web Console output.
    *
    * This method emits the "private-messages-cleared" notification.
    */
-  clearPrivateMessages: function() {
-    if (this.hud.NEW_CONSOLE_OUTPUT_ENABLED) {
+  clearPrivateMessages() {
+    if (this.hud && this.hud.newConsoleOutput) {
       this.hud.newConsoleOutput.dispatchPrivateMessagesClear();
-    } else {
-      let nodes = this.hud.outputNode.querySelectorAll(".message[private]");
-      for (let node of nodes) {
-        this.hud.removeOutputMessage(node);
-      }
+      this.emit("private-messages-cleared");
     }
-    this.emit("private-messages-cleared");
-  },
+  }
 
   /**
    * Updates the size of the input field (command line) to fit its contents.
    *
    * @returns void
    */
-  resizeInput: function() {
+  resizeInput() {
+    if (!this.inputNode) {
+      return;
+    }
+
     let inputNode = this.inputNode;
 
     // Reset the height so that scrollHeight will reflect the natural height of
     // the contents of the input field.
     inputNode.style.height = "auto";
 
     // Now resize the input field to fit its contents.
     // TODO: remove `inputNode.inputField.scrollHeight` when the old
     // console UI is removed. See bug 1381834
     let scrollHeight = inputNode.inputField ?
       inputNode.inputField.scrollHeight : inputNode.scrollHeight;
 
     if (scrollHeight > 0) {
       inputNode.style.height = (scrollHeight + this.inputBorderSize) + "px";
     }
-  },
+  }
 
   /**
    * Sets the value of the input field (command line), and resizes the field to
    * fit its contents. This method is preferred over setting "inputNode.value"
    * directly, because it correctly resizes the field.
    *
    * @param string newValue
    *        The new value to set.
    * @returns void
    */
-  setInputValue: function(newValue) {
+  setInputValue(newValue) {
+    if (!this.inputNode) {
+      return;
+    }
+
     this.inputNode.value = newValue;
     this.lastInputValue = newValue;
     this.completeNode.value = "";
     this.resizeInput();
     this._inputChanged = true;
     this.emit("set-input-value");
-  },
+  }
 
   /**
    * Gets the value from the input field
    * @returns string
    */
-  getInputValue: function() {
-    return this.inputNode.value || "";
-  },
+  getInputValue() {
+    return this.inputNode ? this.inputNode.value || "" : "";
+  }
 
   /**
    * The inputNode "input" and "keyup" event handler.
    * @private
    */
-  _inputEventHandler: function() {
+  _inputEventHandler() {
     if (this.lastInputValue != this.getInputValue()) {
       this.resizeInput();
       this.complete(this.COMPLETE_HINT_ONLY);
       this.lastInputValue = this.getInputValue();
       this._inputChanged = true;
     }
-  },
+  }
 
   /**
    * The window "blur" event handler.
    * @private
    */
-  _blurEventHandler: function() {
+  _blurEventHandler() {
     if (this.autocompletePopup) {
       this.clearCompletion();
     }
-  },
+  }
 
   /* eslint-disable complexity */
   /**
    * The inputNode "keypress" event handler.
    *
    * @private
    * @param nsIDOMEvent event
    */
-  _keyPress: function(event) {
+  _keyPress(event) {
     let inputNode = this.inputNode;
     let inputValue = this.getInputValue();
     let inputUpdated = false;
 
     if (event.ctrlKey) {
       switch (event.charCode) {
         case 101:
           // control-e
@@ -1173,20 +694,16 @@ JSTerm.prototype = {
     }
 
     switch (event.keyCode) {
       case KeyCodes.DOM_VK_ESCAPE:
         if (this.autocompletePopup.isOpen) {
           this.clearCompletion();
           event.preventDefault();
           event.stopPropagation();
-        } else if (this.sidebar) {
-          this._sidebarDestroy();
-          event.preventDefault();
-          event.stopPropagation();
         }
         break;
 
       case KeyCodes.DOM_VK_RETURN:
         if (this._autocompletePopupNavigated &&
             this.autocompletePopup.isOpen &&
             this.autocompletePopup.selectedIndex > -1) {
           this.acceptProposedCompletion();
@@ -1313,37 +830,37 @@ JSTerm.prototype = {
         } else if (this._inputChanged) {
           this.updateCompleteNode(l10n.getStr("Autocomplete.blank"));
           event.preventDefault();
         }
         break;
       default:
         break;
     }
-  },
+  }
   /* eslint-enable complexity */
 
   /**
    * The inputNode "focus" event handler.
    * @private
    */
-  _focusEventHandler: function() {
+  _focusEventHandler() {
     this._inputChanged = false;
-  },
+  }
 
   /**
    * Go up/down the history stack of input values.
    *
    * @param number direction
    *        History navigation direction: HISTORY_BACK or HISTORY_FORWARD.
    *
    * @returns boolean
    *          True if the input value changed, false otherwise.
    */
-  historyPeruse: function(direction) {
+  historyPeruse(direction) {
     if (!this.history.length) {
       return false;
     }
 
     // Up Arrow key
     if (direction == HISTORY_BACK) {
       if (this.historyPlaceHolder <= 0) {
         return false;
@@ -1367,67 +884,67 @@ JSTerm.prototype = {
 
       let inputVal = this.history[++this.historyPlaceHolder];
       this.setInputValue(inputVal);
     } else {
       throw new Error("Invalid argument 0");
     }
 
     return true;
-  },
+  }
 
   /**
    * Test for multiline input.
    *
    * @return boolean
    *         True if CR or LF found in node value; else false.
    */
-  hasMultilineInput: function() {
+  hasMultilineInput() {
     return /[\r\n]/.test(this.getInputValue());
-  },
+  }
 
   /**
    * Check if the caret is at a location that allows selecting the previous item
    * in history when the user presses the Up arrow key.
    *
    * @return boolean
    *         True if the caret is at a location that allows selecting the
    *         previous item in history when the user presses the Up arrow key,
    *         otherwise false.
    */
-  canCaretGoPrevious: function() {
+  canCaretGoPrevious() {
     let node = this.inputNode;
     if (node.selectionStart != node.selectionEnd) {
       return false;
     }
 
     let multiline = /[\r\n]/.test(node.value);
     return node.selectionStart == 0 ? true :
            node.selectionStart == node.value.length && !multiline;
-  },
+  }
 
   /**
    * Check if the caret is at a location that allows selecting the next item in
    * history when the user presses the Down arrow key.
    *
    * @return boolean
    *         True if the caret is at a location that allows selecting the next
    *         item in history when the user presses the Down arrow key, otherwise
    *         false.
    */
-  canCaretGoNext: function() {
+  canCaretGoNext() {
     let node = this.inputNode;
     if (node.selectionStart != node.selectionEnd) {
       return false;
     }
 
     let multiline = /[\r\n]/.test(node.value);
     return node.selectionStart == node.value.length ? true :
            node.selectionStart == 0 && !multiline;
-  },
+  }
 
   /**
    * Completes the current typed text in the inputNode. Completion is performed
    * only if the selection/cursor is at the end of the string. If no completion
    * is found, the current inputNode value and cursor/selection stay.
    *
    * @param int type possible values are
    *    - this.COMPLETE_FORWARD: If there is more than one possible completion
@@ -1453,33 +970,33 @@ JSTerm.prototype = {
    *          is set from the current cursor position to the end of the
    *          completed text.
    * @param function callback
    *        Optional function invoked when the autocomplete properties are
    *        updated.
    * @returns boolean true if there existed a completion for the current input,
    *          or false otherwise.
    */
-  complete: function(type, callback) {
+  complete(type, callback) {
     let inputNode = this.inputNode;
     let inputValue = this.getInputValue();
     let frameActor = this.getFrameActor(this.SELECTED_FRAME);
 
     // If the inputNode has no value, then don't try to complete on it.
     if (!inputValue) {
       this.clearCompletion();
       callback && callback(this);
       this.emit("autocomplete-updated");
       return false;
     }
 
     // Only complete if the selection is empty.
     if (inputNode.selectionStart != inputNode.selectionEnd) {
       this.clearCompletion();
-      callback && callback(this);
+      this.callback && callback(this);
       this.emit("autocomplete-updated");
       return false;
     }
 
     // Update the completion results.
     if (this.lastCompletion.value != inputValue ||
         frameActor != this._lastFrameActorId) {
       this._updateCompletionResult(type, callback);
@@ -1500,43 +1017,43 @@ JSTerm.prototype = {
       popup.selectPreviousPageItem();
     } else if (type == this.COMPLETE_PAGEDOWN) {
       popup.selectNextPageItem();
     }
 
     callback && callback(this);
     this.emit("autocomplete-updated");
     return accepted || popup.itemCount > 0;
-  },
+  }
 
   /**
    * Update the completion result. This operation is performed asynchronously by
    * fetching updated results from the content process.
    *
    * @private
    * @param int type
    *        Completion type. See this.complete() for details.
    * @param function [callback]
    *        Optional, function to invoke when completion results are received.
    */
-  _updateCompletionResult: function(type, callback) {
+  _updateCompletionResult(type, callback) {
     let frameActor = this.getFrameActor(this.SELECTED_FRAME);
     if (this.lastCompletion.value == this.getInputValue() &&
         frameActor == this._lastFrameActorId) {
       return;
     }
 
     let requestId = gSequenceId();
     let cursor = this.inputNode.selectionStart;
     let input = this.getInputValue().substring(0, cursor);
     let cache = this._autocompleteCache;
 
     // If the current input starts with the previous input, then we already
     // have a list of suggestions and we just need to filter the cached
-    // suggestions. When the current input ends with a non-alphanumeric
+    // suggestions. When the current input ends with a non-alphanumeri;
     // character we ask the server again for suggestions.
 
     // Check if last character is non-alphanumeric
     if (!/[a-zA-Z0-9]$/.test(input) || frameActor != this._lastFrameActorId) {
       this._autocompleteQuery = null;
       this._autocompleteCache = null;
     }
 
@@ -1573,32 +1090,32 @@ JSTerm.prototype = {
       value: null,
     };
 
     let autocompleteCallback =
       this._receiveAutocompleteProperties.bind(this, requestId, callback);
 
     this.webConsoleClient.autocomplete(
       input, cursor, autocompleteCallback, frameActor);
-  },
+  }
 
   /**
    * Handler for the autocompletion results. This method takes
    * the completion result received from the server and updates the UI
    * accordingly.
    *
    * @param number requestId
    *        Request ID.
    * @param function [callback=null]
    *        Optional, function to invoke when the completion result is received.
    * @param object message
    *        The JSON message which holds the completion results received from
    *        the content process.
    */
-  _receiveAutocompleteProperties: function(requestId, callback, message) {
+  _receiveAutocompleteProperties(requestId, callback, message) {
     let inputNode = this.inputNode;
     let inputValue = this.getInputValue();
     if (this.lastCompletion.value == inputValue ||
         requestId != this.lastCompletion.requestId) {
       return;
     }
     // Cache whatever came from the server if the last char is
     // alphanumeric or '.'
@@ -1652,62 +1169,65 @@ JSTerm.prototype = {
     } else if (completionType == this.COMPLETE_BACKWARD) {
       popup.selectPreviousItem();
     } else if (completionType == this.COMPLETE_FORWARD) {
       popup.selectNextItem();
     }
 
     callback && callback(this);
     this.emit("autocomplete-updated");
-  },
+  }
 
-  onAutocompleteSelect: function() {
+  onAutocompleteSelect() {
     // Render the suggestion only if the cursor is at the end of the input.
     if (this.inputNode.selectionStart != this.getInputValue().length) {
       return;
     }
 
     let currentItem = this.autocompletePopup.selectedItem;
     if (currentItem && this.lastCompletion.value) {
       let suffix =
         currentItem.label.substring(this.lastCompletion.matchProp.length);
       this.updateCompleteNode(suffix);
     } else {
       this.updateCompleteNode("");
     }
-  },
+  }
 
   /**
    * Clear the current completion information and close the autocomplete popup,
    * if needed.
    */
-  clearCompletion: function() {
-    this.autocompletePopup.clearItems();
+  clearCompletion() {
     this.lastCompletion = { value: null };
     this.updateCompleteNode("");
-    if (this.autocompletePopup.isOpen) {
-      // Trigger a blur/focus of the JSTerm input to force screen readers to read the
-      // value again.
-      this.inputNode.blur();
-      this.autocompletePopup.once("popup-closed", () => {
-        this.inputNode.focus();
-      });
-      this.autocompletePopup.hidePopup();
-      this._autocompletePopupNavigated = false;
+    if (this.autocompletePopup) {
+      this.autocompletePopup.clearItems();
+
+      if (this.autocompletePopup.isOpen) {
+        // Trigger a blur/focus of the JSTerm input to force screen readers to read the
+        // value again.
+        this.inputNode.blur();
+        this.autocompletePopup.once("popup-closed", () => {
+          this.inputNode.focus();
+        });
+        this.autocompletePopup.hidePopup();
+        this._autocompletePopupNavigated = false;
+      }
     }
-  },
+  }
 
   /**
    * Accept the proposed input completion.
    *
    * @return boolean
    *         True if there was a selected completion item and the input value
    *         was updated, false otherwise.
    */
-  acceptProposedCompletion: function() {
+  acceptProposedCompletion() {
     let updated = false;
 
     let currentItem = this.autocompletePopup.selectedItem;
     if (currentItem && this.lastCompletion.value) {
       let suffix =
         currentItem.label.substring(this.lastCompletion.matchProp.length);
       let cursor = this.inputNode.selectionStart;
       let value = this.getInputValue();
@@ -1716,152 +1236,138 @@ JSTerm.prototype = {
       let newCursor = cursor + suffix.length;
       this.inputNode.selectionStart = this.inputNode.selectionEnd = newCursor;
       updated = true;
     }
 
     this.clearCompletion();
 
     return updated;
-  },
+  }
 
   /**
    * Update the node that displays the currently selected autocomplete proposal.
    *
    * @param string suffix
    *        The proposed suffix for the inputNode value.
    */
-  updateCompleteNode: function(suffix) {
+  updateCompleteNode(suffix) {
+    if (!this.completeNode) {
+      return;
+    }
+
     // completion prefix = input, with non-control chars replaced by spaces
     let prefix = suffix ? this.getInputValue().replace(/[\S]/g, " ") : "";
     this.completeNode.value = prefix + suffix;
-  },
+  }
   /**
    * Calculates the width and height of a single character of the input box.
    * This will be used in opening the popup at the correct offset.
    *
    * @private
    */
-  _updateCharSize: function() {
+  _updateCharSize() {
     let doc = this.hud.document;
-    let tempLabel = doc.createElementNS(XHTML_NS, "span");
+    let tempLabel = doc.createElement("span");
     let style = tempLabel.style;
     style.position = "fixed";
     style.padding = "0";
     style.margin = "0";
     style.width = "auto";
     style.color = "transparent";
     WebConsoleUtils.copyTextStyles(this.inputNode, tempLabel);
     tempLabel.textContent = "x";
     doc.documentElement.appendChild(tempLabel);
     this._inputCharWidth = tempLabel.offsetWidth;
     tempLabel.remove();
     // Calculate the width of the chevron placed at the beginning of the input
     // box. Remove 4 more pixels to accomodate the padding of the popup.
     this._chevronWidth = +doc.defaultView.getComputedStyle(this.inputNode)
                              .paddingLeft.replace(/[^0-9.]/g, "") - 4;
-  },
+  }
 
   /**
    * Build the notification box as soon as needed.
    */
-  getNotificationBox: function() {
+  getNotificationBox() {
     if (this._notificationBox) {
       return this._notificationBox;
     }
 
     let box = this.hud.document.getElementById("webconsole-notificationbox");
-    if (box.tagName === "notificationbox") {
-      // Here we are in the old console frontend (e.g. browser console), so we
-      // can directly return the notificationbox.
-      return box;
-    }
-
     let toolbox = gDevTools.getToolbox(this.hud.owner.target);
 
     // Render NotificationBox and assign priority levels to it.
     this._notificationBox = Object.assign(
       toolbox.ReactDOM.render(toolbox.React.createElement(NotificationBox), box),
       PriorityLevels);
     return this._notificationBox;
-  },
+  }
+
+  destroy() {
+    this.clearCompletion();
 
-  /**
-   * Destroy the sidebar.
-   * @private
-   */
-  _sidebarDestroy: function() {
-    if (this._variablesView) {
-      this._variablesView.controller.releaseActors();
-      this._variablesView = null;
+    this.webConsoleClient.clearNetworkRequests();
+    if (this.hud.outputNode) {
+      // We do this because it's much faster than letting React handle the ConsoleOutput
+      // unmounting.
+      this.hud.outputNode.innerHTML = "";
     }
 
-    if (this.sidebar) {
-      this.sidebar.hide();
-      this.sidebar.destroy();
-      this.sidebar = null;
+    if (this.autocompletePopup) {
+      this.autocompletePopup.destroy();
+      this.autocompletePopup = null;
     }
 
-    this.emit("sidebar-closed");
-  },
+    if (this.inputNode) {
+      if (this._onPaste) {
+        this.inputNode.removeEventListener("paste", this._onPaste);
+        this.inputNode.removeEventListener("drop", this._onPaste);
+        this._onPaste = null;
+      }
 
-  /**
-   * Destroy the JSTerm object. Call this method to avoid memory leaks.
-   */
-  destroy: function() {
-    this._sidebarDestroy();
-
-    this.clearCompletion();
-
-    if (this.hud.NEW_CONSOLE_OUTPUT_ENABLED) {
-      this.webConsoleClient.clearNetworkRequests();
-      this.hud.outputNode.innerHTML = "";
-    } else {
-      this.clearOutput();
+      this.inputNode.removeEventListener("keypress", this._keyPress);
+      this.inputNode.removeEventListener("input", this._inputEventHandler);
+      this.inputNode.removeEventListener("keyup", this._inputEventHandler);
+      this.inputNode.removeEventListener("focus", this._focusEventHandler);
+      this.hud.window.removeEventListener("blur", this._blurEventHandler);
     }
 
-    this.autocompletePopup.destroy();
-    this.autocompletePopup = null;
+    this.hud = null;
+  }
 
-    if (this._onPaste) {
-      this.inputNode.removeEventListener("paste", this._onPaste);
-      this.inputNode.removeEventListener("drop", this._onPaste);
-      this._onPaste = null;
+  render() {
+    if (this.props.hud.isBrowserConsole &&
+        !Services.prefs.getBoolPref("devtools.chrome.enabled")) {
+      return null;
     }
 
-    this.inputNode.removeEventListener("keypress", this._keyPress);
-    this.inputNode.removeEventListener("input", this._inputEventHandler);
-    this.inputNode.removeEventListener("keyup", this._inputEventHandler);
-    this.inputNode.removeEventListener("focus", this._focusEventHandler);
-    this.hud.window.removeEventListener("blur", this._blurEventHandler);
-
-    this.hud = null;
-  },
-};
-
-function gSequenceId() {
-  return gSequenceId.n++;
-}
-gSequenceId.n = 0;
-exports.gSequenceId = gSequenceId;
-
-/**
- * @see VariablesView.simpleValueEvalMacro
- */
-function simpleValueEvalMacro(item, currentString) {
-  return VariablesView.simpleValueEvalMacro(item, currentString, "_self");
+    return [
+      dom.div({id: "webconsole-notificationbox", key: "notification"}),
+      dom.div({
+        className: "jsterm-input-container",
+        key: "jsterm-container",
+        style: {direction: "ltr"}
+      },
+        dom.textarea({
+          className: "jsterm-complete-node devtools-monospace",
+          key: "complete",
+          tabIndex: "-1",
+          ref: node => {
+            this.completeNode = node;
+          },
+        }),
+        dom.textarea({
+          className: "jsterm-input-node devtools-monospace",
+          key: "input",
+          tabIndex: "0",
+          rows: "1",
+          "aria-autocomplete": "list",
+          ref: node => {
+            this.inputNode = node;
+          },
+        })
+      ),
+    ];
+  }
 }
 
-/**
- * @see VariablesView.overrideValueEvalMacro
- */
-function overrideValueEvalMacro(item, currentString) {
-  return VariablesView.overrideValueEvalMacro(item, currentString, "_self");
-}
-
-/**
- * @see VariablesView.getterOrSetterEvalMacro
- */
-function getterOrSetterEvalMacro(item, currentString) {
-  return VariablesView.getterOrSetterEvalMacro(item, currentString, "_self");
-}
-
-exports.JSTerm = JSTerm;
+module.exports = JSTerm;
--- a/devtools/client/webconsole/components/moz.build
+++ b/devtools/client/webconsole/components/moz.build
@@ -10,15 +10,16 @@ DIRS += [
 DevToolsModules(
     'CollapseButton.js',
     'ConsoleOutput.js',
     'ConsoleTable.js',
     'FilterBar.js',
     'FilterButton.js',
     'FilterCheckbox.js',
     'GripMessageBody.js',
+    'JSTerm.js',
     'Message.js',
     'MessageContainer.js',
     'MessageIcon.js',
     'MessageIndent.js',
     'MessageRepeat.js',
     'SideBar.js'
 )
--- a/devtools/client/webconsole/new-console-output-wrapper.js
+++ b/devtools/client/webconsole/new-console-output-wrapper.js
@@ -13,42 +13,43 @@ const { createContextMenu } = require("d
 const { configureStore } = require("devtools/client/webconsole/store");
 const { isPacketPrivate } = require("devtools/client/webconsole/utils/messages");
 const { getAllMessagesById, getMessage } = require("devtools/client/webconsole/selectors/messages");
 
 const EventEmitter = require("devtools/shared/event-emitter");
 const ConsoleOutput = createFactory(require("devtools/client/webconsole/components/ConsoleOutput"));
 const FilterBar = createFactory(require("devtools/client/webconsole/components/FilterBar"));
 const SideBar = createFactory(require("devtools/client/webconsole/components/SideBar"));
+const JSTerm = createFactory(require("devtools/client/webconsole/components/JSTerm"));
 
 let store = null;
 
-function NewConsoleOutputWrapper(parentNode, jsterm, toolbox, owner, document) {
+function NewConsoleOutputWrapper(parentNode, hud, toolbox, owner, document) {
   EventEmitter.decorate(this);
 
   this.parentNode = parentNode;
-  this.jsterm = jsterm;
+  this.hud = hud;
   this.toolbox = toolbox;
   this.owner = owner;
   this.document = document;
 
   this.init = this.init.bind(this);
 
   this.queuedMessageAdds = [];
   this.queuedMessageUpdates = [];
   this.queuedRequestUpdates = [];
   this.throttledDispatchPromise = null;
 
-  store = configureStore(this.jsterm.hud);
+  store = configureStore(this.hud);
 }
 NewConsoleOutputWrapper.prototype = {
   init: function() {
     return new Promise((resolve) => {
       const attachRefToHud = (id, node) => {
-        this.jsterm.hud[id] = node;
+        this.hud[id] = node;
       };
       // Focus the input line whenever the output area is clicked.
       this.parentNode.addEventListener("click", (event) => {
         // Do not focus on middle/right-click or 2+ clicks.
         if (event.detail !== 1 || event.button !== 0) {
           return;
         }
 
@@ -70,25 +71,27 @@ NewConsoleOutputWrapper.prototype = {
         }
 
         // Do not focus if something is selected
         let selection = this.document.defaultView.getSelection();
         if (selection && !selection.isCollapsed) {
           return;
         }
 
-        this.jsterm.focus();
+        if (this.hud && this.hud.jsterm) {
+          this.hud.jsterm.focus();
+        }
       });
 
-      let { hud } = this.jsterm;
+      let { hud } = this;
 
       const serviceContainer = {
         attachRefToHud,
         emitNewMessage: (node, messageId, timeStamp) => {
-          this.jsterm.hud.emit("new-messages", new Set([{
+          hud.emit("new-messages", new Set([{
             node,
             messageId,
             timeStamp,
           }]));
         },
         hudProxy: hud.proxy,
         openLink: (url, e) => {
           hud.owner.openLink(url, e);
@@ -135,32 +138,38 @@ NewConsoleOutputWrapper.prototype = {
                         rootObjectInspector.querySelector("[data-link-actor-id]") : null;
         let rootActorId = rootActor ? rootActor.dataset.linkActorId : null;
 
         let sidebarTogglePref = store.getState().prefs.sidebarToggle;
         let openSidebar = sidebarTogglePref ? (messageId) => {
           store.dispatch(actions.showObjectInSidebar(rootActorId, messageId));
         } : null;
 
-        let menu = createContextMenu(this.jsterm, this.parentNode,
-          { actor, clipboardText, variableText, message,
-            serviceContainer, openSidebar, rootActorId });
+        let menu = createContextMenu(this.hud, this.parentNode, {
+          actor,
+          clipboardText,
+          variableText,
+          message,
+          serviceContainer,
+          openSidebar,
+          rootActorId
+        });
 
         // Emit the "menu-open" event for testing.
         menu.once("open", () => this.emit("menu-open"));
         menu.popup(screenX, screenY, { doc: this.owner.chromeWindow.document });
 
         return menu;
       };
 
       if (this.toolbox) {
         Object.assign(serviceContainer, {
           onViewSourceInDebugger: frame => {
             this.toolbox.viewSourceInDebugger(frame.url, frame.line).then(() =>
-              this.jsterm.hud.emit("source-in-debugger-opened")
+              this.hud.emit("source-in-debugger-opened")
             );
           },
           onViewSourceInScratchpad: frame => this.toolbox.viewSourceInScratchpad(
             frame.url,
             frame.line
           ),
           onViewSourceInStyleEditor: frame => this.toolbox.viewSourceInStyleEditor(
             frame.url,
@@ -194,70 +203,64 @@ NewConsoleOutputWrapper.prototype = {
             let onNodeFrontSet = this.toolbox.selection
               .setNodeFront(front, { reason: "console" });
 
             return Promise.all([onNodeFrontSet, onInspectorUpdated]);
           }
         });
       }
 
-      let consoleOutput = ConsoleOutput({
-        serviceContainer,
-        onFirstMeaningfulPaint: resolve
-      });
-
-      let filterBar = FilterBar({
-        hidePersistLogsCheckbox: this.jsterm.hud.isBrowserConsole,
-        serviceContainer: {
-          attachRefToHud
-        }
-      });
-
-      let sideBar = SideBar({
-        serviceContainer,
-      });
-
       let provider = createElement(
         Provider,
         { store },
         dom.div(
           {className: "webconsole-output-wrapper"},
-          filterBar,
-          consoleOutput,
-          sideBar
+          FilterBar({
+            hidePersistLogsCheckbox: this.hud.isBrowserConsole,
+            serviceContainer: {
+              attachRefToHud
+            }
+          }),
+          ConsoleOutput({
+            serviceContainer,
+            onFirstMeaningfulPaint: resolve
+          }),
+          SideBar({
+            serviceContainer,
+          }),
+          JSTerm({
+            hud: this.hud,
+          }),
         ));
       this.body = ReactDOM.render(provider, this.parentNode);
-
-      this.jsterm.focus();
     });
   },
 
   dispatchMessageAdd: function(packet, waitForResponse) {
     // Wait for the message to render to resolve with the DOM node.
     // This is just for backwards compatibility with old tests, and should
     // be removed once it's not needed anymore.
     // Can only wait for response if the action contains a valid message.
     let promise;
     // Also, do not expect any update while the panel is in background.
     if (waitForResponse && document.visibilityState === "visible") {
       const timeStampToMatch = packet.message
         ? packet.message.timeStamp
         : packet.timestamp;
 
       promise = new Promise(resolve => {
-        let jsterm = this.jsterm;
-        jsterm.hud.on("new-messages", function onThisMessage(messages) {
+        this.hud.on("new-messages", function onThisMessage(messages) {
           for (let m of messages) {
             if (m.timeStamp === timeStampToMatch) {
               resolve(m.node);
-              jsterm.hud.off("new-messages", onThisMessage);
+              this.hud.off("new-messages", onThisMessage);
               return;
             }
           }
-        });
+        }.bind(this));
       });
     } else {
       promise = Promise.resolve();
     }
 
     this.batchedMessagesAdd(packet);
     return promise;
   },
@@ -383,34 +386,34 @@ NewConsoleOutputWrapper.prototype = {
         this.throttledDispatchPromise = null;
 
         store.dispatch(actions.messagesAdd(this.queuedMessageAdds));
         this.queuedMessageAdds = [];
 
         if (this.queuedMessageUpdates.length > 0) {
           this.queuedMessageUpdates.forEach(({ message, res }) => {
             store.dispatch(actions.networkMessageUpdate(message, null, res));
-            this.jsterm.hud.emit("network-message-updated", res);
+            this.hud.emit("network-message-updated", res);
           });
           this.queuedMessageUpdates = [];
         }
         if (this.queuedRequestUpdates.length > 0) {
           this.queuedRequestUpdates.forEach(({ id, data}) => {
             store.dispatch(actions.networkUpdateRequest(id, data));
           });
           this.queuedRequestUpdates = [];
 
           // Fire an event indicating that all data fetched from
           // the backend has been received. This is based on
           // 'FirefoxDataProvider.isQueuePayloadReady', see more
           // comments in that method.
           // (netmonitor/src/connector/firefox-data-provider).
           // This event might be utilized in tests to find the right
           // time when to finish.
-          this.jsterm.hud.emit("network-request-payload-ready");
+          this.hud.emit("network-request-payload-ready");
         }
         done();
       }, 50);
     });
   },
 
   // Should be used for test purpose only.
   getStore: function() {
--- a/devtools/client/webconsole/new-webconsole.js
+++ b/devtools/client/webconsole/new-webconsole.js
@@ -7,17 +7,16 @@
 "use strict";
 
 const {Utils: WebConsoleUtils} = require("devtools/client/webconsole/utils");
 const EventEmitter = require("devtools/shared/event-emitter");
 const promise = require("promise");
 const defer = require("devtools/shared/defer");
 const Services = require("Services");
 const { gDevTools } = require("devtools/client/framework/devtools");
-const { JSTerm } = require("devtools/client/webconsole/jsterm");
 const { WebConsoleConnectionProxy } = require("devtools/client/webconsole/webconsole-connection-proxy");
 const KeyShortcuts = require("devtools/client/shared/key-shortcuts");
 const { l10n } = require("devtools/client/webconsole/utils/messages");
 
 loader.lazyRequireGetter(this, "AppConstants", "resource://gre/modules/AppConstants.jsm", true);
 
 const ZoomKeys = require("devtools/client/shared/zoom-keys");
 
@@ -77,40 +76,32 @@ NewWebConsoleFrame.prototype = {
 
   /**
    * Initialize the WebConsoleFrame instance.
    * @return object
    *         A promise object that resolves once the frame is ready to use.
    */
   async init() {
     this._initUI();
-    let connectionInited = this._initConnection();
-    // Don't reject if the history fails to load for some reason.
-    // This would be fine, the panel will just start with empty history.
-    let onJsTermHistoryLoaded = this.jsterm.historyLoaded
-      .catch(() => {});
-
-    await Promise.all([connectionInited, onJsTermHistoryLoaded]);
+    await this._initConnection();
     await this.newConsoleOutput.init();
 
     let id = WebConsoleUtils.supportsString(this.hudId);
     if (Services.obs) {
       Services.obs.notifyObservers(id, "web-console-created");
     }
   },
   destroy() {
     if (this._destroyer) {
       return this._destroyer.promise;
     }
     this._destroyer = defer();
     Services.prefs.removeObserver(PREF_MESSAGE_TIMESTAMP, this._onToolboxPrefChanged);
     this.React = this.ReactDOM = this.FrameView = null;
     if (this.jsterm) {
-      this.jsterm.off("sidebar-opened", this.resize);
-      this.jsterm.off("sidebar-closed", this.resize);
       this.jsterm.destroy();
       this.jsterm = null;
     }
 
     let toolbox = gDevTools.getToolbox(this.owner.target);
     if (toolbox) {
       toolbox.off("webconsole-selected", this._onPanelSelected);
     }
@@ -202,33 +193,23 @@ NewWebConsoleFrame.prototype = {
     return this._initDefer.promise;
   },
 
   _initUI: function() {
     this.document = this.window.document;
     this.rootElement = this.document.documentElement;
 
     this.outputNode = this.document.getElementById("output-container");
-    this.completeNode = this.document.querySelector(".jsterm-complete-node");
-    this.inputNode = this.document.querySelector(".jsterm-input-node");
-
-    this.jsterm = new JSTerm(this);
-    this.jsterm.init();
 
     let toolbox = gDevTools.getToolbox(this.owner.target);
 
-    // @TODO Remove this once JSTerm is handled with React/Redux.
-    this.window.jsterm = this.jsterm;
-    // @TODO Once the toolbox has been converted to React, see if passing
-    // in JSTerm is still necessary.
-
     // Handle both launchpad and toolbox loading
     let Wrapper = this.owner.NewConsoleOutputWrapper || this.window.NewConsoleOutput;
-    this.newConsoleOutput = new Wrapper(
-      this.outputNode, this.jsterm, toolbox, this.owner, this.document);
+    this.newConsoleOutput =
+      new Wrapper(this.outputNode, this, toolbox, this.owner, this.document);
     // Toggle the timestamp on preference change
     Services.prefs.addObserver(PREF_MESSAGE_TIMESTAMP, this._onToolboxPrefChanged);
     this._onToolboxPrefChanged();
 
     this._initShortcuts();
 
     if (toolbox) {
       toolbox.on("webconsole-selected", this._onPanelSelected);
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_hide_when_devtools_chrome_enabled_false.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_hide_when_devtools_chrome_enabled_false.js
@@ -99,12 +99,11 @@ async function testObjectInspectorProper
   let name = nameNode.textContent;
   let value = container.querySelector(".objectBox").textContent;
 
   is(name, "browser_console_hide_jsterm_test", "name is set correctly");
   is(value, "true", "value is set correctly");
 }
 
 function testJSTermIsNotVisible(hud) {
-  let inputContainer = hud.ui.window.document
-                                    .querySelector(".jsterm-input-container");
-  is(inputContainer.style.display, "none", "input is not visible");
+  let inputContainer = hud.ui.window.document.querySelector(".jsterm-input-container");
+  is(inputContainer, null, "input is not in dom");
 }
--- a/devtools/client/webconsole/utils/context-menu.js
+++ b/devtools/client/webconsole/utils/context-menu.js
@@ -15,33 +15,33 @@ const MenuItem = require("devtools/clien
 const { MESSAGE_SOURCE } = require("devtools/client/webconsole/constants");
 
 const clipboardHelper = require("devtools/shared/platform/clipboard");
 const { l10n } = require("devtools/client/webconsole/utils/messages");
 
 /**
  * Create a Menu instance for the webconsole.
  *
- * @param {Object} jsterm
- *        The JSTerm instance used by the webconsole.
+ * @param {Object} hud
+ *        The webConsoleFrame.
  * @param {Element} parentNode
  *        The container of the new console frontend output wrapper.
  * @param {Object} options
  *        - {String} actor (optional) actor id to use for context menu actions
  *        - {String} clipboardText (optional) text to "Copy" if no selection is available
  *        - {String} variableText (optional) which is the textual frontend
  *            representation of the variable
  *        - {Object} message (optional) message object containing metadata such as:
  *          - {String} source
  *          - {String} request
  *        - {Function} openSidebar (optional) function that will open the object
  *            inspector sidebar
  *        - {String} rootActorId (optional) actor id for the root object being clicked on
  */
-function createContextMenu(jsterm, parentNode, {
+function createContextMenu(hud, parentNode, {
   actor,
   clipboardText,
   variableText,
   message,
   serviceContainer,
   openSidebar,
   rootActorId,
 }) {
@@ -109,19 +109,19 @@ function createContextMenu(jsterm, paren
         }
         this["temp" + i] = _self;
         "temp" + i;
       }`;
       let options = {
         selectedObjectActor: actor,
       };
 
-      jsterm.requestEvaluation(evalString, options).then((res) => {
-        jsterm.focus();
-        jsterm.setInputValue(res.result);
+      hud.jsterm.requestEvaluation(evalString, options).then((res) => {
+        hud.jsterm.focus();
+        hud.jsterm.setInputValue(res.result);
       });
     },
   }));
 
   // Copy message or grip.
   menu.append(new MenuItem({
     id: "console-menu-copy",
     label: l10n.getStr("webconsole.menu.copyMessage.label"),
@@ -144,17 +144,17 @@ function createContextMenu(jsterm, paren
     id: "console-menu-copy-object",
     label: l10n.getStr("webconsole.menu.copyObject.label"),
     accesskey: l10n.getStr("webconsole.menu.copyObject.accesskey"),
     // Disabled if there is no actor and no variable text associated.
     disabled: (!actor && !variableText),
     click: () => {
       if (actor) {
         // The Debugger.Object of the OA will be bound to |_self| during evaluation,
-        jsterm.copyObject(`_self`, { selectedObjectActor: actor }).then((res) => {
+        hud.jsterm.copyObject(`_self`, { selectedObjectActor: actor }).then((res) => {
           clipboardHelper.copyString(res.helperResult.value);
         });
       } else {
         clipboardHelper.copyString(variableText);
       }
     },
   }));
 
--- a/devtools/client/webconsole/webconsole.html
+++ b/devtools/client/webconsole/webconsole.html
@@ -16,23 +16,11 @@
 
     <script src="chrome://devtools/content/shared/theme-switching.js"></script>
     <script type="application/javascript"
             src="resource://devtools/client/webconsole/main.js"></script>
   </head>
   <body class="theme-sidebar" role="application">
     <div id="app-wrapper" class="theme-body">
       <div id="output-container" role="document" aria-live="polite"></div>
-      <div id="jsterm-wrapper">
-        <div id="webconsole-notificationbox"></div>
-        <div class="jsterm-input-container" style="direction:ltr">
-          <div class="jsterm-stack-node">
-            <textarea class="jsterm-complete-node devtools-monospace"
-                      tabindex="-1"></textarea>
-            <textarea class="jsterm-input-node devtools-monospace"
-                      rows="1" tabindex="0"
-                      aria-autocomplete="list"></textarea>
-          </div>
-        </div>
-      </div>
     </div>
   </body>
 </html>