Bug 1326937 - Provide HTML page when running new console frontend; r=bgrins draft
authorJan Odvarko <odvarko@gmail.com>
Fri, 09 Jun 2017 09:42:34 -0700
changeset 610550 251e1cad00bdf397cf8fe2910692ea106b53129b
parent 610234 5e73b9798464c3f7106f0161dc9a49b234f42f9c
child 610551 ba8691941b5eb83641e61e5228e3829daa5e05e4
push id68920
push userjodvarko@mozilla.com
push dateTue, 18 Jul 2017 12:56:31 +0000
reviewersbgrins
bugs1326937
milestone56.0a1
Bug 1326937 - Provide HTML page when running new console frontend; r=bgrins MozReview-Commit-ID: 8IJAyAX2wpc
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_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
@@ -8,27 +8,25 @@ const {Cc, Ci, Cu} = require("chrome");
 
 var WebConsoleUtils = require("devtools/client/webconsole/utils").Utils;
 var { extend } = require("sdk/core/heritage");
 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
@@ -198,36 +196,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));
 
@@ -281,27 +274,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(
@@ -577,16 +579,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,
         };
@@ -935,41 +942,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) {
@@ -1579,28 +1584,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();
@@ -1684,16 +1687,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_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() {