Bug 1326937 - Provide HTML page when running new console frontend; r=bgrins
authorJan Odvarko <odvarko@gmail.com>
Fri, 09 Jun 2017 09:42:34 -0700
changeset 418883 2f2e14280fab8e585a7ccd3a52c7b59eaedfc586
parent 418882 9c8c36344fee1097adf411124ec637ecc2942be8
child 418884 5612970bf69821d43169f51b135bdfa54e4fa500
push id7566
push usermtabara@mozilla.com
push dateWed, 02 Aug 2017 08:25:16 +0000
treeherdermozilla-beta@86913f512c3c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbgrins
bugs1326937
milestone56.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 1326937 - Provide HTML page when running new console frontend; r=bgrins MozReview-Commit-ID: G2TzrRF0Vag
devtools/client/definitions.js
devtools/client/framework/toolbox-process-window.js
devtools/client/jar.mn
devtools/client/locales/en-US/webConsole.dtd
devtools/client/locales/en-US/webconsole.properties
devtools/client/webconsole/hudservice.js
devtools/client/webconsole/jsterm.js
devtools/client/webconsole/moz.build
devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_inspect.js
devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_console_dir.js
devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_console_group.js
devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_console_table.js
devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_filters.js
devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_filters_persist.js
devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_input_focus.js
devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_keyboard_accessibility.js
devtools/client/webconsole/new-console-output/test/mochitest/head.js
devtools/client/webconsole/new-webconsole.js
devtools/client/webconsole/test/browser_console_open_or_focus.js
devtools/client/webconsole/webconsole-connection-proxy.js
devtools/client/webconsole/webconsole.js
devtools/client/webconsole/webconsole.xhtml
devtools/client/webconsole/webconsole.xul
--- a/devtools/client/definitions.js
+++ b/devtools/client/definitions.js
@@ -88,26 +88,26 @@ Tools.inspector = {
   isTargetSupported: function (target) {
     return target.hasActor("inspector");
   },
 
   build: function (iframeWindow, toolbox) {
     return new InspectorPanel(iframeWindow, toolbox);
   }
 };
-
 Tools.webConsole = {
   id: "webconsole",
   key: l10n("cmd.commandkey"),
   accesskey: l10n("webConsoleCmd.accesskey"),
   modifiers: Services.appinfo.OS == "Darwin" ? "accel,alt" : "accel,shift",
   ordinal: 2,
+  oldWebConsoleURL: "chrome://devtools/content/webconsole/webconsole.xul",
+  newWebConsoleURL: "chrome://devtools/content/webconsole/webconsole.xhtml",
   icon: "chrome://devtools/skin/images/tool-webconsole.svg",
   invertIconForDarkTheme: true,
-  url: "chrome://devtools/content/webconsole/webconsole.xul",
   label: l10n("ToolboxTabWebconsole.label"),
   menuLabel: l10n("MenuWebconsole.label"),
   panelLabel: l10n("ToolboxWebConsole.panelLabel"),
   get tooltip() {
     return l10n("ToolboxWebconsole.tooltip2",
     (osString == "Darwin" ? "Cmd+Opt+" : "Ctrl+Shift+") + this.key);
   },
   inMenu: true,
@@ -121,21 +121,33 @@ Tools.webConsole = {
 
     panel.focusInput();
     return undefined;
   },
 
   isTargetSupported: function () {
     return true;
   },
-
   build: function (iframeWindow, toolbox) {
     return new WebConsolePanel(iframeWindow, toolbox);
   }
 };
+function switchWebconsole() {
+  if (Services.prefs.getBoolPref("devtools.webconsole.new-frontend-enabled")) {
+    Tools.webConsole.url = Tools.webConsole.newWebConsoleURL;
+  } else {
+    Tools.webConsole.url = Tools.webConsole.oldWebConsoleURL;
+  }
+}
+switchWebconsole();
+
+Services.prefs.addObserver(
+  "devtools.webconsole.new-frontend-enabled",
+  { observe: switchWebconsole }
+);
 
 Tools.jsdebugger = {
   id: "jsdebugger",
   key: l10n("debuggerMenu.commandkey"),
   accesskey: l10n("debuggerMenu.accesskey"),
   modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift",
   ordinal: 3,
   icon: "chrome://devtools/skin/images/tool-debugger.svg",
--- a/devtools/client/framework/toolbox-process-window.js
+++ b/devtools/client/framework/toolbox-process-window.js
@@ -61,18 +61,18 @@ function setPrefDefaults() {
   Services.prefs.setBoolPref("devtools.inspector.showAllAnonymousContent", true);
   Services.prefs.setBoolPref("browser.dom.window.dump.enabled", true);
   Services.prefs.setBoolPref("devtools.command-button-noautohide.enabled", true);
   Services.prefs.setBoolPref("devtools.scratchpad.enabled", true);
   // Bug 1225160 - Using source maps with browser debugging can lead to a crash
   Services.prefs.setBoolPref("devtools.debugger.source-maps-enabled", false);
   Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", true);
   Services.prefs.setBoolPref("devtools.debugger.client-source-maps-enabled", true);
+  Services.prefs.setBoolPref("devtools.webconsole.new-frontend-enabled", false);
 }
-
 window.addEventListener("load", function () {
   let cmdClose = document.getElementById("toolbox-cmd-close");
   cmdClose.addEventListener("command", onCloseCommand);
   setPrefDefaults();
   connect().catch(e => {
     let errorMessageContainer = document.getElementById("error-message-container");
     let errorMessage = document.getElementById("error-message");
     errorMessage.value = e.message || e;
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -5,16 +5,17 @@
 devtools.jar:
 %   content devtools %content/
     content/shared/vendor/d3.js (shared/vendor/d3.js)
     content/shared/vendor/dagre-d3.js (shared/vendor/dagre-d3.js)
     content/shared/widgets/widgets.css (shared/widgets/widgets.css)
     content/netmonitor/src/assets/styles/netmonitor.css (netmonitor/src/assets/styles/netmonitor.css)
     content/shared/widgets/VariablesView.xul (shared/widgets/VariablesView.xul)
     content/netmonitor/index.html (netmonitor/index.html)
+    content/webconsole/webconsole.xhtml (webconsole/webconsole.xhtml)
     content/webconsole/webconsole.xul (webconsole/webconsole.xul)
     content/scratchpad/scratchpad.xul (scratchpad/scratchpad.xul)
     content/scratchpad/scratchpad.js (scratchpad/scratchpad.js)
     content/shared/splitview.css (shared/splitview.css)
     content/shared/theme-switching.js (shared/theme-switching.js)
     content/shared/frame-script-utils.js (shared/frame-script-utils.js)
     content/styleeditor/styleeditor.xul (styleeditor/styleeditor.xul)
     content/storage/storage.xul (storage/storage.xul)
@@ -145,16 +146,17 @@ devtools.jar:
     skin/images/command-eyedropper.svg (themes/images/command-eyedropper.svg)
     skin/images/command-rulers.svg (themes/images/command-rulers.svg)
     skin/images/command-measure.svg (themes/images/command-measure.svg)
     skin/images/command-noautohide.svg (themes/images/command-noautohide.svg)
     skin/markup.css (themes/markup.css)
     skin/images/editor-error.png (themes/images/editor-error.png)
     skin/images/breakpoint.svg (themes/images/breakpoint.svg)
     skin/webconsole.css (themes/webconsole.css)
+    skin/new-webconsole.css (themes/new-webconsole.css)
     skin/images/webconsole.svg (themes/images/webconsole.svg)
     skin/images/breadcrumbs-scrollbutton.png (themes/images/breadcrumbs-scrollbutton.png)
     skin/images/breadcrumbs-scrollbutton@2x.png (themes/images/breadcrumbs-scrollbutton@2x.png)
     skin/animationinspector.css (themes/animationinspector.css)
     skin/canvasdebugger.css (themes/canvasdebugger.css)
     skin/debugger.css (themes/debugger.css)
     skin/performance.css (themes/performance.css)
     skin/memory.css (themes/memory.css)
--- a/devtools/client/locales/en-US/webConsole.dtd
+++ b/devtools/client/locales/en-US/webConsole.dtd
@@ -1,26 +1,21 @@
 <!-- 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/. -->
-
 <!-- LOCALIZATION NOTE : FILE The correct localization of this file might be to
   - keep it in English, or another language commonly spoken among web developers.
   - You want to make that choice consistent across the developer tools.
   - A good criteria is the language in which you'd find the best
   - documentation on web development on the web. -->
-
 <!ENTITY window.title "Web Console">
-<!ENTITY browserConsole.title "Browser Console">
-
 <!-- LOCALIZATION NOTE (openURL.label): You can see this string in the Web
    - Console context menu. -->
 <!ENTITY openURL.label     "Open URL in New Tab">
 <!ENTITY openURL.accesskey "T">
-
 <!-- LOCALIZATION NOTE (btnPageNet.label): This string is used for the menu
   -  button that allows users to toggle the network logging output.
   -  This string and the following strings toggle various kinds of output
   -  filters. -->
 <!ENTITY btnPageNet.label   "Net">
 <!ENTITY btnPageNet.tooltip "Log network access">
 <!ENTITY btnPageNet.accesskey "N">
 <!-- LOCALIZATION NOTE (btnPageNet.accesskeyMacOSX): This string is used as
--- a/devtools/client/locales/en-US/webconsole.properties
+++ b/devtools/client/locales/en-US/webconsole.properties
@@ -1,26 +1,24 @@
 # 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/.
-
 # LOCALIZATION NOTE
 # The correct localization of this file might be to keep it in
 # English, or another language commonly spoken among web developers.
 # You want to make that choice consistent across the developer tools.
 # A good criteria is the language in which you'd find the best
 # documentation on web development on the web.
-
-
+# LOCALIZATION NOTE (browserConsole.title): shown as the
+# title when opening the browser console popup
+browserConsole.title=Browser Console
 # LOCALIZATION NOTE (timestampFormat): %1$02S = hours (24-hour clock),
 # %2$02S = minutes, %3$02S = seconds, %4$03S = milliseconds.
 timestampFormat=%02S:%02S:%02S.%03S
-
 helperFuncUnsupportedTypeError=Can’t call pprint on this type of object.
-
 # LOCALIZATION NOTE (NetworkPanel.deltaDurationMS): this string is used to
 # show the duration between two network events (e.g request and response
 # header or response header and response body). Parameters: %S is the duration.
 NetworkPanel.durationMS=%Sms
 
 ConsoleAPIDisabled=The Web Console logging API (console.log, console.info, console.warn, console.error) has been disabled by a script on this page.
 
 # LOCALIZATION NOTE (webConsoleWindowTitleAndURL): the Web Console floating
--- a/devtools/client/webconsole/hudservice.js
+++ b/devtools/client/webconsole/hudservice.js
@@ -6,27 +6,25 @@
 
 var WebConsoleUtils = require("devtools/client/webconsole/utils").Utils;
 const {extend} = require("devtools/shared/extend");
 var {TargetFactory} = require("devtools/client/framework/target");
 var {Tools} = require("devtools/client/definitions");
 const { Task } = require("devtools/shared/task");
 var promise = require("promise");
 var Services = require("Services");
-
 loader.lazyRequireGetter(this, "Telemetry", "devtools/client/shared/telemetry");
 loader.lazyRequireGetter(this, "WebConsoleFrame", "devtools/client/webconsole/webconsole", true);
+loader.lazyRequireGetter(this, "NewWebConsoleFrame", "devtools/client/webconsole/new-webconsole", true);
 loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
 loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true);
 loader.lazyRequireGetter(this, "DebuggerClient", "devtools/shared/client/main", true);
 loader.lazyRequireGetter(this, "showDoorhanger", "devtools/client/shared/doorhanger", true);
 loader.lazyRequireGetter(this, "viewSource", "devtools/client/shared/view-source");
-
 const l10n = require("devtools/client/webconsole/webconsole-l10n");
-
 const BROWSER_CONSOLE_WINDOW_FEATURES = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
 
 // The preference prefix for all of the Browser Console filters.
 const BROWSER_CONSOLE_FILTER_PREFS_PREFIX = "devtools.browserconsole.filter.";
 
 var gHudId = 0;
 
 // The HUD service
@@ -196,36 +194,31 @@ HUD_SERVICE.prototype =
         });
     }
 
     let target;
     function getTarget(aConnection)
     {
       return TargetFactory.forRemoteTab(aConnection);
     }
-
     function openWindow(aTarget)
     {
       target = aTarget;
-
       let deferred = promise.defer();
-
-      let win = Services.ww.openWindow(null, Tools.webConsole.url, "_blank",
+      // Using the old frontend for now in the browser console.  This can be switched to
+      // Tools.webConsole.url to use whatever is preffed on.
+      let url = Tools.webConsole.oldWebConsoleURL;
+      let win = Services.ww.openWindow(null, url, "_blank",
                                        BROWSER_CONSOLE_WINDOW_FEATURES, null);
       win.addEventListener("DOMContentLoaded", function () {
-        // Set the correct Browser Console title.
-        let root = win.document.documentElement;
-        root.setAttribute("title", root.getAttribute("browserConsoleTitle"));
-
+          win.document.title = l10n.getStr("browserConsole.title");
         deferred.resolve(win);
       }, {once: true});
-
       return deferred.promise;
     }
-
     connect().then(getTarget).then(openWindow).then((aWindow) => {
       return this.openBrowserConsole(target, aWindow, aWindow)
         .then((aBrowserConsole) => {
           this._browserConsoleDefer.resolve(aBrowserConsole);
           this._browserConsoleDefer = null;
         });
     }, console.error.bind(console));
 
@@ -279,27 +272,27 @@ HUD_SERVICE.prototype =
  *        The window of the web console owner.
  */
 function WebConsole(aTarget, aIframeWindow, aChromeWindow)
 {
   this.iframeWindow = aIframeWindow;
   this.chromeWindow = aChromeWindow;
   this.hudId = "hud_" + ++gHudId;
   this.target = aTarget;
-
   this.browserWindow = this.chromeWindow.top;
-
   let element = this.browserWindow.document.documentElement;
   if (element.getAttribute("windowtype") != gDevTools.chromeWindowType) {
     this.browserWindow = HUDService.currentContext();
   }
-
-  this.ui = new WebConsoleFrame(this);
+  if (aIframeWindow.location.href === Tools.webConsole.newWebConsoleURL) {
+    this.ui = new NewWebConsoleFrame(this);
+  } else {
+    this.ui = new WebConsoleFrame(this);
+  }
 }
-
 WebConsole.prototype = {
   iframeWindow: null,
   chromeWindow: null,
   browserWindow: null,
   hudId: null,
   target: null,
   ui: null,
   _browserConsole: false,
--- a/devtools/client/webconsole/jsterm.js
+++ b/devtools/client/webconsole/jsterm.js
@@ -251,20 +251,22 @@ JSTerm.prototype = {
     };
 
     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");
+    // 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 {
       let okstring = l10n.getStr("selfxss.okstring");
       let msg = l10n.getFormatStr("selfxss.msg", [okstring]);
       this._onPaste = WebConsoleUtils.pasteHandlerGen(
@@ -587,16 +589,21 @@ JSTerm.prototype = {
    *        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,
         };
@@ -945,41 +952,39 @@ JSTerm.prototype = {
    * 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;
-    let outputNode = hud.outputNode;
-    let node;
-    while ((node = outputNode.firstChild)) {
-      hud.removeOutputMessage(node);
-    }
 
-    hud.groupDepth = 0;
-    hud._outputQueue.forEach(hud._destroyItem, hud);
-    hud._outputQueue = [];
+    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();
-    hud._repeatNodes = {};
-
     if (clearStorage) {
       this.webConsoleClient.clearMessagesCache();
     }
-
     this._sidebarDestroy();
-
-    if (hud.NEW_CONSOLE_OUTPUT_ENABLED) {
-      hud.newConsoleOutput.dispatchMessagesClear();
-    }
-
+    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 () {
     let nodes = this.hud.outputNode.querySelectorAll(".message[private]");
     for (let node of nodes) {
@@ -1589,28 +1594,26 @@ JSTerm.prototype = {
     let popup = this.autocompletePopup;
     popup.setItems(items);
 
     let completionType = this.lastCompletion.completionType;
     this.lastCompletion = {
       value: inputValue,
       matchProp: lastPart,
     };
-
     if (items.length > 1 && !popup.isOpen) {
       let str = this.getInputValue().substr(0, this.inputNode.selectionStart);
       let offset = str.length - (str.lastIndexOf("\n") + 1) - lastPart.length;
-      let x = offset * this.hud._inputCharWidth;
-      popup.openPopup(inputNode, x + this.hud._chevronWidth);
+      let x = offset * this._inputCharWidth;
+      popup.openPopup(inputNode, x + this._chevronWidth);
       this._autocompletePopupNavigated = false;
     } else if (items.length < 2 && popup.isOpen) {
       popup.hidePopup();
       this._autocompletePopupNavigated = false;
     }
-
     if (items.length == 1) {
       popup.selectedIndex = 0;
     }
 
     this.onAutocompleteSelect();
 
     if (completionType != this.COMPLETE_HINT_ONLY && popup.itemCount == 1) {
       this.acceptProposedCompletion();
@@ -1694,16 +1697,41 @@ JSTerm.prototype = {
    * @param string suffix
    *        The proposed suffix for the inputNode value.
    */
   updateCompleteNode: function (suffix) {
     // 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 () {
+    let doc = this.hud.document;
+    let tempLabel = doc.createElementNS(XHTML_NS, "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;
+  },
 
   /**
    * Destroy the sidebar.
    * @private
    */
   _sidebarDestroy: function () {
     if (this._variablesView) {
       this._variablesView.controller.releaseActors();
--- a/devtools/client/webconsole/moz.build
+++ b/devtools/client/webconsole/moz.build
@@ -5,23 +5,22 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
 
 DIRS += [
     'net',
     'new-console-output',
 ]
-
 DevToolsModules(
     'console-commands.js',
     'console-output.js',
     'hudservice.js',
     'jsterm.js',
+    'new-webconsole.js',
     'panel.js',
     'utils.js',
     'webconsole-connection-proxy.js',
     'webconsole-l10n.js',
     'webconsole.js',
 )
-
 with Files('**'):
     BUG_COMPONENT = ('Firefox', 'Developer Tools: Console')
--- a/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
+++ b/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
@@ -29,22 +29,42 @@ function NewConsoleOutputWrapper(parentN
   this.toolbox = toolbox;
   this.owner = owner;
   this.document = document;
 
   this.init = this.init.bind(this);
 
   store = configureStore(this.jsterm.hud);
 }
-
 NewConsoleOutputWrapper.prototype = {
   init: function () {
     const attachRefToHud = (id, node) => {
       this.jsterm.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;
+      }
+
+      // Do not focus if something is selected
+      let selection = this.document.defaultView.getSelection();
+      if (selection && !selection.isCollapsed) {
+        return;
+      }
+
+      // Do not focus if a link was clicked
+      if (event.target.nodeName.toLowerCase() === "a" ||
+          event.target.parentNode.nodeName.toLowerCase() === "a") {
+        return;
+      }
+
+      this.jsterm.focus();
+    });
 
     const serviceContainer = {
       attachRefToHud,
       emitNewMessage: (node, messageId) => {
         this.jsterm.hud.emit("new-messages", new Set([{
           node,
           messageId,
         }]));
@@ -135,24 +155,23 @@ NewConsoleOutputWrapper.prototype = {
     let provider = React.createElement(
       Provider,
       { store },
       React.DOM.div(
         {className: "webconsole-output-wrapper"},
         filterBar,
         childComponent
     ));
+    this.body = ReactDOM.render(provider, this.parentNode);
 
-    this.body = ReactDOM.render(provider, this.parentNode);
+    this.jsterm.focus();
   },
-
   dispatchMessageAdd: function (message, waitForResponse) {
     let action = actions.messageAdd(message);
     batchedMessageAdd(action);
-
     // 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.
     if (waitForResponse && action.message) {
       let messageId = action.message.id;
       return new Promise(resolve => {
         let jsterm = this.jsterm;
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_inspect.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_inspect.js
@@ -16,17 +16,17 @@ add_task(async function () {
   let jsterm = hud.jsterm;
 
   info("Test `inspect(window)`");
   // Add a global value so we can check it later.
   await jsterm.execute("testProp = 'testValue'");
   await jsterm.execute("inspect(window)");
 
   const inspectWindowNode = await waitFor(() =>
-    findInspectResultMessage(hud.ui.experimentalOutputNode, 1));
+    findInspectResultMessage(hud.ui.outputNode, 1));
 
   let objectInspectors = [...inspectWindowNode.querySelectorAll(".tree")];
   is(objectInspectors.length, 1, "There is the expected number of object inspectors");
 
   const [windowOi] = objectInspectors;
   let windowOiNodes = windowOi.querySelectorAll(".node");
 
   // The tree can be collapsed since the properties are fetched asynchronously.
@@ -48,15 +48,15 @@ add_task(async function () {
   is(testPropertyValueNode.textContent, '"testValue"',
     "The testProp property value is displayed as expected");
 
   /* Check that a primitive value can be inspected, too */
   info("Test `inspect(1)`");
   await jsterm.execute("inspect(1)");
 
   const inspectPrimitiveNode = await waitFor(() =>
-    findInspectResultMessage(hud.ui.experimentalOutputNode, 2));
+    findInspectResultMessage(hud.ui.outputNode, 2));
   is(inspectPrimitiveNode.textContent, 1, "The primitive is displayed as expected");
 });
 
 function findInspectResultMessage(node, index) {
   return node.querySelectorAll(".message.result")[index];
 }
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_console_dir.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_console_dir.js
@@ -25,25 +25,22 @@ add_task(async function () {
   });
 
   info("console.dir on an array");
   await ContentTask.spawn(gBrowser.selectedBrowser, null, function () {
     content.wrappedJSObject.console.dir(
       [1, 2, {a: "a", b: "b"}],
     );
   });
-
   let dirMessageNode = await waitFor(() =>
-    findConsoleDir(hud.ui.experimentalOutputNode, 0));
+    findConsoleDir(hud.ui.outputNode, 0));
   let objectInspectors = [...dirMessageNode.querySelectorAll(".tree")];
   is(objectInspectors.length, 1, "There is the expected number of object inspectors");
-
   const [arrayOi] = objectInspectors;
   let arrayOiNodes = arrayOi.querySelectorAll(".node");
-
   // The tree can be collapsed since the properties are fetched asynchronously.
   if (arrayOiNodes.length === 1) {
     // If this is the case, we wait for the properties to be fetched and displayed.
     await waitForNodeMutation(arrayOi, {
       childList: true
     });
     arrayOiNodes = arrayOi.querySelectorAll(".node");
   }
@@ -55,28 +52,24 @@ add_task(async function () {
   const arrayPropertiesNames = ["0", "1", "2", "length", "__proto__"];
   is(JSON.stringify(propertiesNodes), JSON.stringify(arrayPropertiesNames));
 
   info("console.dir on a long object");
   const obj = Array.from({length: 100}).reduce((res, _, i) => {
     res["item-" + (i + 1).toString().padStart(3, "0")] = i + 1;
     return res;
   }, {});
-
   await ContentTask.spawn(gBrowser.selectedBrowser, obj, function (data) {
     content.wrappedJSObject.console.dir(data);
   });
-
-  dirMessageNode = await waitFor(() => findConsoleDir(hud.ui.experimentalOutputNode, 1));
+  dirMessageNode = await waitFor(() => findConsoleDir(hud.ui.outputNode, 1));
   objectInspectors = [...dirMessageNode.querySelectorAll(".tree")];
   is(objectInspectors.length, 1, "There is the expected number of object inspectors");
-
   const [objectOi] = objectInspectors;
   let objectOiNodes = objectOi.querySelectorAll(".node");
-
   // The tree can be collapsed since the properties are fetched asynchronously.
   if (objectOiNodes.length === 1) {
     // If this is the case, we wait for the properties to be fetched and displayed.
     await waitForNodeMutation(objectOi, {
       childList: true
     });
     objectOiNodes = objectOi.querySelectorAll(".node");
   }
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_console_group.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_console_group.js
@@ -65,27 +65,23 @@ add_task(function* () {
   node = yield waitFor(() => findMessage(hud, "log-4"));
   testClass(node, "log");
   testIndent(node, 0);
 
   info("Test a collapsed group at root level");
   node = yield waitFor(() => findMessage(hud, "group-3"));
   testClass(node, "startGroupCollapsed");
   testIndent(node, 0);
-
   info("Test a message at root level, after closing a collapsed group");
   node = yield waitFor(() => findMessage(hud, "log-6"));
   testClass(node, "log");
   testIndent(node, 0);
-
-  let nodes = hud.ui.experimentalOutputNode.querySelectorAll(".message");
+  let nodes = hud.ui.outputNode.querySelectorAll(".message");
   is(nodes.length, 8, "expected number of messages are displayed");
 });
-
 function testClass(node, className) {
   ok(node.classList.contains(className), `message has the expected "${className}" class`);
 }
-
 function testIndent(node, indent) {
   indent = `${indent * INDENT_WIDTH}px`;
   is(node.querySelector(".indent").style.width, indent,
     "message has the expected level of indentation");
 }
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_console_table.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_console_table.js
@@ -117,34 +117,29 @@ add_task(function* () {
     }
   }];
 
   yield ContentTask.spawn(gBrowser.selectedBrowser, testCases, function (tests) {
     tests.forEach((test) => {
       content.wrappedJSObject.doConsoleTable(test.input, test.headers);
     });
   });
-
   let nodes = [];
   for (let testCase of testCases) {
     let node = yield waitFor(
-      () => findConsoleTable(hud.ui.experimentalOutputNode, testCases.indexOf(testCase))
+      () => findConsoleTable(hud.ui.outputNode, testCases.indexOf(testCase))
     );
     nodes.push(node);
   }
-
-  let consoleTableNodes = hud.ui.experimentalOutputNode.querySelectorAll(
+  let consoleTableNodes = hud.ui.outputNode.querySelectorAll(
     ".message .new-consoletable");
-
   is(consoleTableNodes.length, testCases.length,
     "console has the expected number of consoleTable items");
-
   testCases.forEach((testCase, index) => testItem(testCase, nodes[index]));
 });
-
 function testItem(testCase, node) {
   info(testCase.info);
 
   let columns = Array.from(node.querySelectorAll("thead th"));
   let rows = Array.from(node.querySelectorAll("tbody tr"));
 
   is(
     JSON.stringify(testCase.expected.columns),
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_filters.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_filters.js
@@ -1,30 +1,25 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests filters.
 
 "use strict";
-
 const { MESSAGE_LEVEL } = require("devtools/client/webconsole/new-console-output/constants");
-
 const TEST_URI = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/mochitest/test-console-filters.html";
-
 add_task(function* () {
   let hud = yield openNewTabAndConsole(TEST_URI);
-  const outputNode = hud.ui.experimentalOutputNode;
-
+  const outputNode = hud.ui.outputNode;
   const toolbar = yield waitFor(() => {
     return outputNode.querySelector(".webconsole-filterbar-primary");
   });
   ok(toolbar, "Toolbar found");
-
   // Show the filter bar
   toolbar.querySelector(".devtools-filter-icon").click();
   const filterBar = yield waitFor(() => {
     return outputNode.querySelector(".webconsole-filterbar-secondary");
   });
   ok(filterBar, "Filter bar is shown when filter icon is clicked.");
 
   // Check defaults.
@@ -46,27 +41,24 @@ add_task(function* () {
   filterBar.querySelector(".error").click();
   yield waitFor(() => findMessages(hud, "").length == 4);
   ok(true, "When a filter is turned off, its messages are not shown.");
 
   // Check that the ui settings were persisted.
   yield closeTabAndToolbox();
   yield testFilterPersistence();
 });
-
 function filterIsEnabled(button) {
   return button.classList.contains("checked");
 }
-
 function* testFilterPersistence() {
   let hud = yield openNewTabAndConsole(TEST_URI);
-  const outputNode = hud.ui.experimentalOutputNode;
+  const outputNode = hud.ui.outputNode;
   const filterBar = yield waitFor(() => {
     return outputNode.querySelector(".webconsole-filterbar-secondary");
   });
   ok(filterBar, "Filter bar ui setting is persisted.");
-
   // Check that the filter settings were persisted.
   ok(!filterIsEnabled(filterBar.querySelector(".error")),
     "Filter button setting is persisted");
   ok(findMessages(hud, "").length == 4,
     "Messages of all levels shown when filters are on.");
 }
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_filters_persist.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_filters_persist.js
@@ -35,29 +35,25 @@ add_task(function* () {
   yield closeTabAndToolbox();
   hud = yield openNewTabAndConsole(TEST_URI);
 
   info("Check that all filters are enabled");
   filterButtons = yield getFilterButtons(hud);
   filterButtons.forEach(filterButton => {
     ok(filterIsEnabled(filterButton), "filter is enabled");
   });
-
   // Check that the ui settings were persisted.
   yield closeTabAndToolbox();
 });
-
 function* getFilterButtons(hud) {
-  const outputNode = hud.ui.experimentalOutputNode;
-
+  const outputNode = hud.ui.outputNode;
   info("Wait for console toolbar to appear");
   const toolbar = yield waitFor(() => {
     return outputNode.querySelector(".webconsole-filterbar-primary");
   });
-
   // Show the filter bar if it is hidden
   if (!outputNode.querySelector(".webconsole-filterbar-secondary")) {
     toolbar.querySelector(".devtools-filter-icon").click();
   }
 
   info("Wait for console filterbar to appear");
   const filterBar = yield waitFor(() => {
     return outputNode.querySelector(".webconsole-filterbar-secondary");
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_input_focus.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_input_focus.js
@@ -20,31 +20,26 @@ add_task(function* () {
   let inputNode = hud.jsterm.inputNode;
   ok(inputNode.getAttribute("focused"), "input node is focused after output is cleared");
 
   ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
     content.wrappedJSObject.console.log("console message 2");
   });
   let msg = yield waitFor(() => findMessage(hud, "console message 2"));
   let outputItem = msg.querySelector(".message-body");
-
   inputNode = hud.jsterm.inputNode;
   ok(inputNode.getAttribute("focused"), "input node is focused, first");
-
   yield waitForBlurredInput(inputNode);
-
   EventUtils.sendMouseEvent({type: "click"}, hud.outputNode);
+
   ok(inputNode.getAttribute("focused"), "input node is focused, second time");
-
   yield waitForBlurredInput(inputNode);
-
   info("Setting a text selection and making sure a click does not re-focus");
   let selection = hud.iframeWindow.getSelection();
   selection.selectAllChildren(outputItem);
-
   EventUtils.sendMouseEvent({type: "click"}, hud.outputNode);
   ok(!inputNode.getAttribute("focused"),
     "input node focused after text is selected");
 });
 
 function waitForBlurredInput(inputNode) {
   return new Promise(resolve => {
     let lostFocus = () => {
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_keyboard_accessibility.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_keyboard_accessibility.js
@@ -14,31 +14,25 @@ const TEST_URI =
       console.log("console message " + i);
     }
   </script>
   `;
 
 add_task(function* () {
   let hud = yield openNewTabAndConsole(TEST_URI);
   info("Web Console opened");
-
   const outputScroller = hud.ui.outputScroller;
-
   yield waitFor(() => findMessages(hud, "").length == 100);
-
   let currentPosition = outputScroller.scrollTop;
   const bottom = currentPosition;
-
-  EventUtils.sendMouseEvent({type: "click"}, hud.jsterm.inputNode);
-
+  hud.jsterm.inputNode.focus();
   // Page up.
   EventUtils.synthesizeKey("VK_PAGE_UP", {});
   isnot(outputScroller.scrollTop, currentPosition,
     "scroll position changed after page up");
-
   // Page down.
   currentPosition = outputScroller.scrollTop;
   EventUtils.synthesizeKey("VK_PAGE_DOWN", {});
   ok(outputScroller.scrollTop > currentPosition,
      "scroll position now at bottom");
 
   // Home
   EventUtils.synthesizeKey("VK_HOME", {});
--- a/devtools/client/webconsole/new-console-output/test/mochitest/head.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/head.js
@@ -131,24 +131,23 @@ function findMessage(hud, text, selector
  * @param object hud
  *        The web console.
  * @param string text
  *        A substring that can be found in the message.
  * @param selector [optional]
  *        The selector to use in finding the message.
  */
 function findMessages(hud, text, selector = ".message") {
-  const messages = hud.ui.experimentalOutputNode.querySelectorAll(selector);
+  const messages = hud.ui.outputNode.querySelectorAll(selector);
   const elements = Array.prototype.filter.call(
     messages,
     (el) => el.textContent.includes(text)
   );
   return elements;
 }
-
 /**
  * Simulate a context menu event on the provided element, and wait for the console context
  * menu to open. Returns a promise that resolves the menu popup element.
  *
  * @param object hud
  *        The web console.
  * @param element element
  *        The dom element on which the context menu event should be synthesized.
--- a/devtools/client/webconsole/new-webconsole.js
+++ b/devtools/client/webconsole/new-webconsole.js
@@ -9,23 +9,24 @@
 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/new-console-output/utils/messages");
+const system = require("devtools/shared/system");
+const { ZoomKeys } = require("devtools/client/shared/zoom-keys");
 const PREF_MESSAGE_TIMESTAMP = "devtools.webconsole.timestampMessages";
-
 // XXX: This file is incomplete (see bug 1326937).
 // It's used when loading the webconsole with devtools-launchpad, but will ultimately be
 // the entry point for the new frontend
-
 /**
  * A WebConsoleFrame instance is an interactive console initialized *per target*
  * that displays console log data as well as provides an interactive terminal to
  * manipulate the target's document content.
  *
  * The WebConsoleFrame is responsible for the actual Web Console UI
  * implementation.
  *
@@ -77,26 +78,36 @@ NewWebConsoleFrame.prototype = {
         Services.obs.notifyObservers(id, "web-console-created");
       }
     };
     allReady.then(notifyObservers, notifyObservers)
             .then(this.newConsoleOutput.init);
 
     return allReady;
   },
-
   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;
+    }
 
-    Services.prefs.addObserver(PREF_MESSAGE_TIMESTAMP, this._onToolboxPrefChanged);
-    this.React = this.ReactDOM = this.FrameView = null;
+    let toolbox = gDevTools.getToolbox(this.owner.target);
+    if (toolbox) {
+      toolbox.off("webconsole-selected", this._onPanelSelected);
+    }
+
+    this.window = this.owner = this.newConsoleOutput = null;
 
     let onDestroy = () => {
       this._destroyer.resolve(null);
     };
     if (this.proxy) {
       this.proxy.disconnect().then(onDestroy);
       this.proxy = null;
     } else {
@@ -194,22 +205,50 @@ NewWebConsoleFrame.prototype = {
     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);
-
     // Toggle the timestamp on preference change
     Services.prefs.addObserver(PREF_MESSAGE_TIMESTAMP, this._onToolboxPrefChanged);
     this._onToolboxPrefChanged();
+
+    this._initShortcuts();
   },
 
+  _initShortcuts: function () {
+    let shortcuts = new KeyShortcuts({
+      window: this.window
+    });
+
+    shortcuts.on(l10n.getStr("webconsole.find.key"),
+                 (name, event) => {
+                   this.filterBox.focus();
+                   event.preventDefault();
+                 });
+
+    let clearShortcut;
+    if (system.constants.platform === "macosx") {
+      clearShortcut = l10n.getStr("webconsole.clear.keyOSX");
+    } else {
+      clearShortcut = l10n.getStr("webconsole.clear.key");
+    }
+
+    shortcuts.on(clearShortcut, () => this.jsterm.clearOutput(true));
+
+    if (this.isBrowserConsole) {
+      shortcuts.on(l10n.getStr("webconsole.close.key"),
+                   this.window.close.bind(this.window));
+
+      ZoomKeys.register(this.window);
+    }
+  },
   /**
    * Handler for page location changes.
    *
    * @param string uri
    *        New page location.
    * @param string title
    *        New page title.
    */
--- a/devtools/client/webconsole/test/browser_console_open_or_focus.js
+++ b/devtools/client/webconsole/test/browser_console_open_or_focus.js
@@ -21,26 +21,20 @@ add_task(function* () {
 
   console.log("testmessage");
   yield waitForMessages({
     webconsole: hud,
     messages: [{
       text: "testmessage"
     }],
   });
-
   currWindow = Services.wm.getMostRecentWindow(null);
-  is(currWindow.document.documentURI, Tools.webConsole.url,
+  is(currWindow.document.documentURI, Tools.webConsole.oldWebConsoleURL,
      "The Browser Console is open and has focus");
-
   mainWindow.focus();
-
   yield HUDService.openBrowserConsoleOrFocus();
-
   currWindow = Services.wm.getMostRecentWindow(null);
-  is(currWindow.document.documentURI, Tools.webConsole.url,
+  is(currWindow.document.documentURI, Tools.webConsole.oldWebConsoleURL,
      "The Browser Console is open and has focus");
-
   yield HUDService.toggleBrowserConsole();
-
   hud = HUDService.getBrowserConsole();
   ok(!hud, "Browser Console has been closed");
 });
--- a/devtools/client/webconsole/webconsole-connection-proxy.js
+++ b/devtools/client/webconsole/webconsole-connection-proxy.js
@@ -282,145 +282,156 @@ WebConsoleConnectionProxy.prototype = {
    *
    * @private
    * @param string type
    *        Message type.
    * @param object packet
    *        The message received from the server.
    */
   _onPageError: function (type, packet) {
-    if (this.webConsoleFrame && packet.from == this._consoleActor) {
-      if (this.webConsoleFrame.NEW_CONSOLE_OUTPUT_ENABLED) {
-        this.dispatchMessageAdd(packet);
-        return;
-      }
+    if (!this.webConsoleFrame || packet.from != this._consoleActor) {
+      return;
+    }
+    if (this.webConsoleFrame.NEW_CONSOLE_OUTPUT_ENABLED) {
+      this.dispatchMessageAdd(packet);
+    } else {
       this.webConsoleFrame.handlePageError(packet.pageError);
     }
   },
-
   /**
    * The "logMessage" message type handler. We redirect any message to the UI
    * for displaying.
    *
    * @private
    * @param string type
    *        Message type.
    * @param object packet
    *        The message received from the server.
    */
   _onLogMessage: function (type, packet) {
     if (!this.webConsoleFrame || packet.from != this._consoleActor) {
       return;
     }
-
     if (this.webConsoleFrame.NEW_CONSOLE_OUTPUT_ENABLED) {
       this.dispatchMessageAdd(packet);
     } else {
       this.webConsoleFrame.handleLogMessage(packet);
     }
   },
-
   /**
    * The "consoleAPICall" message type handler. We redirect any message to
    * the UI for displaying.
    *
    * @private
    * @param string type
    *        Message type.
    * @param object packet
    *        The message received from the server.
    */
   _onConsoleAPICall: function (type, packet) {
-    if (this.webConsoleFrame && packet.from == this._consoleActor) {
-      if (this.webConsoleFrame.NEW_CONSOLE_OUTPUT_ENABLED) {
-        this.dispatchMessageAdd(packet);
-      } else {
-        this.webConsoleFrame.handleConsoleAPICall(packet.message);
-      }
+    if (!this.webConsoleFrame || packet.from != this._consoleActor) {
+      return;
+    }
+    if (this.webConsoleFrame.NEW_CONSOLE_OUTPUT_ENABLED) {
+      this.dispatchMessageAdd(packet);
+    } else {
+      this.webConsoleFrame.handleConsoleAPICall(packet.message);
     }
   },
-
   /**
    * The "networkEvent" message type handler. We redirect any message to
    * the UI for displaying.
    *
    * @private
    * @param string type
    *        Message type.
    * @param object networkInfo
    *        The network request information.
    */
   _onNetworkEvent: function (type, networkInfo) {
-    if (this.webConsoleFrame) {
-      if (this.webConsoleFrame.NEW_CONSOLE_OUTPUT_ENABLED) {
-        this.dispatchMessageAdd(networkInfo);
-      } else {
-        this.webConsoleFrame.handleNetworkEvent(networkInfo);
-      }
+    if (!this.webConsoleFrame) {
+      return;
+    }
+    if (this.webConsoleFrame.NEW_CONSOLE_OUTPUT_ENABLED) {
+      this.dispatchMessageAdd(networkInfo);
+    } else {
+      this.webConsoleFrame.handleNetworkEvent(networkInfo);
     }
   },
-
   /**
    * The "networkEventUpdate" message type handler. We redirect any message to
    * the UI for displaying.
    *
    * @private
    * @param string type
    *        Message type.
    * @param object response
    *        The update response received from the server.
    */
   _onNetworkEventUpdate: function (type, response) {
+    if (!this.webConsoleFrame) {
+      return;
+    }
     let { packet, networkInfo } = response;
-    if (this.webConsoleFrame) {
-      if (this.webConsoleFrame.NEW_CONSOLE_OUTPUT_ENABLED) {
-        this.dispatchMessageUpdate(networkInfo, response);
-      }
+    if (this.webConsoleFrame.NEW_CONSOLE_OUTPUT_ENABLED) {
+      this.dispatchMessageUpdate(networkInfo, response);
+    } else {
       this.webConsoleFrame.handleNetworkEventUpdate(networkInfo, packet);
     }
   },
-
   /**
    * The "fileActivity" message type handler. We redirect any message to
    * the UI for displaying.
    *
    * @private
    * @param string type
    *        Message type.
    * @param object packet
    *        The message received from the server.
    */
   _onFileActivity: function (type, packet) {
-    if (this.webConsoleFrame && packet.from == this._consoleActor) {
+    if (!this.webConsoleFrame || packet.from != this._consoleActor) {
+      return;
+    }
+    if (this.webConsoleFrame.NEW_CONSOLE_OUTPUT_ENABLED) {
+      // TODO: Implement for new console
+    } else {
       this.webConsoleFrame.handleFileActivity(packet.uri);
     }
   },
-
   _onReflowActivity: function (type, packet) {
-    if (this.webConsoleFrame && packet.from == this._consoleActor) {
+    if (!this.webConsoleFrame || packet.from != this._consoleActor) {
+      return;
+    }
+    if (this.webConsoleFrame.NEW_CONSOLE_OUTPUT_ENABLED) {
+      // TODO: Implement for new console
+    } else {
       this.webConsoleFrame.handleReflowActivity(packet);
     }
   },
-
   /**
    * The "serverLogCall" message type handler. We redirect any message to
    * the UI for displaying.
    *
    * @private
    * @param string type
    *        Message type.
    * @param object packet
    *        The message received from the server.
    */
   _onServerLogCall: function (type, packet) {
-    if (this.webConsoleFrame && packet.from == this._consoleActor) {
+    if (!this.webConsoleFrame || packet.from != this._consoleActor) {
+      return;
+    }
+    if (this.webConsoleFrame.NEW_CONSOLE_OUTPUT_ENABLED) {
+      // TODO: Implement for new console
+    } else {
       this.webConsoleFrame.handleConsoleAPICall(packet.message);
     }
   },
-
   /**
    * The "lastPrivateContextExited" message type handler. When this message is
    * received the Web Console UI is cleared.
    *
    * @private
    * @param string type
    *        Message type.
    * @param object packet
--- a/devtools/client/webconsole/webconsole.js
+++ b/devtools/client/webconsole/webconsole.js
@@ -485,24 +485,18 @@ WebConsoleFrame.prototype = {
     // This notification is only used in tests. Don't chain it onto
     // the returned promise because the console panel needs to be attached
     // to the toolbox before the web-console-created event is receieved.
     let notifyObservers = () => {
       let id = WebConsoleUtils.supportsString(this.hudId);
       Services.obs.notifyObservers(id, "web-console-created");
     };
     allReady.then(notifyObservers, notifyObservers);
-
-    if (this.NEW_CONSOLE_OUTPUT_ENABLED) {
-      allReady.then(this.newConsoleOutput.init);
-    }
-
     return allReady;
   },
-
   /**
    * Connect to the server using the remote debugging protocol.
    *
    * @private
    * @return object
    *         A promise object that is resolved/reject based on the connection
    *         result.
    */
@@ -522,92 +516,54 @@ WebConsoleFrame.prototype = {
       let node = this.createMessageNode(CATEGORY_JS, SEVERITY_ERROR,
                                         reason.error + ": " + reason.message);
       this.outputMessage(CATEGORY_JS, node, [reason]);
       this._initDefer.reject(reason);
     });
 
     return this._initDefer.promise;
   },
-
   /**
    * Find the Web Console UI elements and setup event listeners as needed.
    * @private
    */
   _initUI: function () {
     this.document = this.window.document;
     this.rootElement = this.document.documentElement;
-    this.NEW_CONSOLE_OUTPUT_ENABLED = !this.isBrowserConsole
-      && !this.owner.target.chrome
-      && Services.prefs.getBoolPref(PREF_NEW_FRONTEND_ENABLED);
-
     this.outputNode = this.document.getElementById("output-container");
     this.outputWrapper = this.document.getElementById("output-wrapper");
     this.completeNode = this.document.querySelector(".jsterm-complete-node");
     this.inputNode = this.document.querySelector(".jsterm-input-node");
-
     // In the old frontend, the area that scrolls is outputWrapper, but in the new
     // frontend this will be reassigned.
     this.outputScroller = this.outputWrapper;
-
-    // Update the character width and height needed for the popup offset
-    // calculations.
-    this._updateCharSize();
-
     this.jsterm = new JSTerm(this);
     this.jsterm.init();
-
     let toolbox = gDevTools.getToolbox(this.owner.target);
-
-    if (this.NEW_CONSOLE_OUTPUT_ENABLED) {
-      // @TODO Remove this once JSTerm is handled with React/Redux.
-      this.window.jsterm = this.jsterm;
-
-      // Remove context menu for now (see Bug 1307239).
-      this.outputWrapper.removeAttribute("context");
-
-      // XXX: We should actually stop output from happening on old output
-      // panel, but for now let's just hide it.
-      this.experimentalOutputNode = this.outputNode.cloneNode();
-      this.experimentalOutputNode.removeAttribute("tabindex");
-      this.outputNode.hidden = true;
-      this.outputNode.parentNode.appendChild(this.experimentalOutputNode);
-      // @TODO Once the toolbox has been converted to React, see if passing
-      // in JSTerm is still necessary.
-
-      this.newConsoleOutput = new this.window.NewConsoleOutput(
-        this.experimentalOutputNode, this.jsterm, toolbox, this.owner, this.document);
-
-      let filterToolbar = this.document.querySelector(".hud-console-filter-toolbar");
-      filterToolbar.hidden = true;
-    } else {
-      // Register the controller to handle "select all" properly.
-      this._commandController = new CommandController(this);
-      this.window.controllers.insertControllerAt(0, this._commandController);
-
-      this._contextMenuHandler = new ConsoleContextMenu(this);
-
-      this._initDefaultFilterPrefs();
-      this.filterBox = this.document.querySelector(".hud-filter-box");
-      this._setFilterTextBoxEvents();
-      this._initFilterButtons();
-      let clearButton =
-        this.document.getElementsByClassName("webconsole-clear-console-button")[0];
-      clearButton.addEventListener("command", () => {
-        this.owner._onClearButton();
-        this.jsterm.clearOutput(true);
-      });
-
-    }
-
+    // Register the controller to handle "select all" properly.
+    this._commandController = new CommandController(this);
+    this.window.controllers.insertControllerAt(0, this._commandController);
+
+    this._contextMenuHandler = new ConsoleContextMenu(this);
+
+    this._initDefaultFilterPrefs();
+    this.filterBox = this.document.querySelector(".hud-filter-box");
+    this._setFilterTextBoxEvents();
+    this._initFilterButtons();
+
+    let clearButton =
+      this.document.getElementsByClassName("webconsole-clear-console-button")[0];
+    clearButton.addEventListener("command", () => {
+      this.owner._onClearButton();
+      this.jsterm.clearOutput(true);
+    });
     this.resize();
     this.window.addEventListener("resize", this.resize, true);
     this.jsterm.on("sidebar-opened", this.resize);
     this.jsterm.on("sidebar-closed", this.resize);
-
     if (toolbox) {
       toolbox.on("webconsole-selected", this._onPanelSelected);
     }
 
     /*
      * Focus the input line whenever the output area is clicked.
      */
     this.outputWrapper.addEventListener("click", (event) => {
@@ -616,58 +572,40 @@ WebConsoleFrame.prototype = {
         return;
       }
 
       // Do not focus if something is selected
       let selection = this.window.getSelection();
       if (selection && !selection.isCollapsed) {
         return;
       }
-
       // Do not focus if a link was clicked
       if (event.target.nodeName.toLowerCase() === "a" ||
           event.target.parentNode.nodeName.toLowerCase() === "a") {
         return;
       }
-
-      // Do not focus if a search input was clicked on the new frontend
-      if (this.NEW_CONSOLE_OUTPUT_ENABLED &&
-          event.target.nodeName.toLowerCase() === "input" &&
-          event.target.getAttribute("type").toLowerCase() === "search") {
-        return;
-      }
-
       this.jsterm.focus();
     });
-
     // Toggle the timestamp on preference change
     this._prefObserver = new PrefObserver("");
     this._prefObserver.on(PREF_MESSAGE_TIMESTAMP, this._onToolboxPrefChanged);
     this._onToolboxPrefChanged();
-
     this._initShortcuts();
 
     // focus input node
     this.jsterm.focus();
   },
-
   /**
    * Resizes the output node to fit the output wrapped.
    * We need this because it makes the layout a lot faster than
    * using -moz-box-flex and 100% width.  See Bug 1237368.
    */
   resize: function () {
-    if (this.NEW_CONSOLE_OUTPUT_ENABLED) {
-      this.experimentalOutputNode.style.width =
-        this.outputWrapper.clientWidth + "px";
-    } else {
-      this.outputNode.style.width = this.outputWrapper.clientWidth + "px";
-    }
+    this.outputNode.style.width = this.outputWrapper.clientWidth + "px";
   },
-
   /**
    * Sets the focus to JavaScript input field when the web console tab is
    * selected or when there is a split console present.
    * @private
    */
   _onPanelSelected: function () {
     this.jsterm.focus();
   },
@@ -832,49 +770,21 @@ WebConsoleFrame.prototype = {
     if (Services.appinfo.OS == "Darwin") {
       let net = this.document.querySelector("toolbarbutton[category=net]");
       let accesskey = net.getAttribute("accesskeyMacOSX");
       net.setAttribute("accesskey", accesskey);
 
       let logging =
         this.document.querySelector("toolbarbutton[category=logging]");
       logging.removeAttribute("accesskey");
-
       let serverLogging =
         this.document.querySelector("toolbarbutton[category=server]");
       serverLogging.removeAttribute("accesskey");
     }
   },
-
-  /**
-   * 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 () {
-    let doc = this.document;
-    let tempLabel = doc.createElementNS(XHTML_NS, "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;
-  },
-
   /**
    * The event handler that is called whenever a user switches a filter on or
    * off.
    *
    * @private
    * @param nsIDOMEvent event
    *        The event that triggered the filter change.
    */
@@ -1969,29 +1879,22 @@ WebConsoleFrame.prototype = {
    * @param string event
    *        Event name.
    * @param object packet
    *        Notification packet received from the server.
    */
   handleTabNavigated: function (event, packet) {
     if (event == "will-navigate") {
       if (this.persistLog) {
-        if (this.NEW_CONSOLE_OUTPUT_ENABLED) {
-          // Add a _type to hit convertCachedPacket.
-          packet._type = true;
-          this.newConsoleOutput.dispatchMessageAdd(packet);
-        } else {
-          let marker = new Messages.NavigationMarker(packet, Date.now());
-          this.output.addMessage(marker);
-        }
+        let marker = new Messages.NavigationMarker(packet, Date.now());
+        this.output.addMessage(marker);
       } else {
         this.jsterm.clearOutput();
       }
     }
-
     if (packet.url) {
       this.onLocationChange(packet.url, packet.title);
     }
 
     if (event == "navigate" && !packet.nativeConsoleAPI) {
       this.logWarningAboutReplacedAPI();
     }
   },
@@ -2693,31 +2596,27 @@ WebConsoleFrame.prototype = {
         return;
       }
 
       this._startX = this._startY = undefined;
 
       callback.call(this, event);
     });
   },
-
   /**
    * Called when the message timestamp pref changes.
    */
   _onToolboxPrefChanged: function () {
     let newValue = Services.prefs.getBoolPref(PREF_MESSAGE_TIMESTAMP);
-    if (this.NEW_CONSOLE_OUTPUT_ENABLED) {
-      this.newConsoleOutput.dispatchTimestampsToggle(newValue);
-    } else if (newValue) {
+    if (newValue) {
       this.outputNode.classList.remove("hideTimestamps");
     } else {
       this.outputNode.classList.add("hideTimestamps");
     }
   },
-
   /**
    * Copies the selected items to the system clipboard.
    *
    * @param object options
    *        - linkOnly:
    *        An optional flag to copy only URL without other meta-information.
    *        Default is false.
    *        - contextmenu:
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/webconsole.xhtml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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/. -->
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" dir="">
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+    <link rel="stylesheet" href="chrome://devtools/skin/new-webconsole.css"/>
+    <script src="chrome://devtools/content/shared/theme-switching.js"></script>
+    <script type="application/javascript"
+            src="resource://devtools/client/webconsole/new-console-output/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 id="jsterm-wrapper">
+        <xul:notificationbox id="webconsole-notificationbox">
+          <div class="jsterm-input-container" style="direction:ltr">
+            <xul:stack class="jsterm-stack-node" flex="1">
+              <xul:textbox class="jsterm-complete-node devtools-monospace"
+                       multiline="true" rows="1" tabindex="-1"/>
+              <xul:textbox class="jsterm-input-node devtools-monospace"
+                       multiline="true" rows="1" tabindex="0"
+                       aria-autocomplete="list"/>
+            </xul:stack>
+          </div>
+        </xul:notificationbox>
+      </div>
+    </div>
+  </body>
+</html>
--- a/devtools/client/webconsole/webconsole.xul
+++ b/devtools/client/webconsole/webconsole.xul
@@ -14,21 +14,19 @@
 <?xml-stylesheet href="chrome://devtools/skin/components-frame.css"
                  type="text/css"?>
 <?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         id="devtools-webconsole"
         macanimationtype="document"
         fullscreenbutton="true"
         title="&window.title;"
-        browserConsoleTitle="&browserConsole.title;"
         windowtype="devtools:webconsole"
         width="900" height="350"
         persist="screenX screenY width height sizemode">
-
   <script type="application/javascript"
           src="chrome://devtools/content/shared/theme-switching.js"/>
   <script type="application/javascript"
           src="resource://devtools/client/webconsole/new-console-output/main.js"/>
   <script type="text/javascript" src="chrome://global/content/globalOverlay.js"/>
   <script type="text/javascript" src="resource://devtools/client/webconsole/net/main.js"/>
   <script type="text/javascript"><![CDATA[
 function goUpdateConsoleCommands() {