Bug 1425521 - Wrap JsTerm in a React component in new frontend; r=bgrins.
authorNicolas Chevobbe <nchevobbe@mozilla.com>
Fri, 30 Mar 2018 17:48:50 +0200
changeset 412970 18c6e8809c2bbcf0a96b5c11d86f5105d0754d4a
parent 412969 93c851a45ec74718a3fe6569e9ac3f2797820906
child 412971 06f67baf2aed4715a995bfd41a98833d984de27f
push id33828
push userarchaeopteryx@coole-files.de
push dateThu, 12 Apr 2018 19:19:41 +0000
treeherdermozilla-central@6e22c4a726c2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbgrins
bugs1425521
milestone61.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 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>