Bug 1291358 - Part 2: New console frontend: Add support for console.trace();r=linclark
authorNicolas Chevobbe <chevobbe.nicolas@gmail.com>
Thu, 18 Aug 2016 16:04:32 -0700
changeset 310173 70eb7533c06c6d1f698f0de6c5782ddd5dd174da
parent 310172 bb740199463f5947df757975ac0e8c23556dd1c3
child 310174 d3335ad9cec0f42443e98448f1a5c59539a4a30c
push id31532
push userryanvm@gmail.com
push dateFri, 19 Aug 2016 13:59:01 +0000
treeherderautoland@4f1e241f75a2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslinclark
bugs1291358
milestone51.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 1291358 - Part 2: New console frontend: Add support for console.trace();r=linclark MozReview-Commit-ID: G7Ghf07BiSl
devtools/client/themes/webconsole.css
devtools/client/webconsole/new-console-output/components/console-output.js
devtools/client/webconsole/new-console-output/components/message-container.js
devtools/client/webconsole/new-console-output/components/message-types/console-api-call.js
devtools/client/webconsole/new-console-output/main.js
devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
devtools/client/webconsole/new-console-output/test/fixtures/stubs.js
devtools/client/webconsole/new-console-output/types.js
devtools/client/webconsole/new-console-output/utils/messages.js
devtools/client/webconsole/webconsole.js
--- a/devtools/client/themes/webconsole.css
+++ b/devtools/client/themes/webconsole.css
@@ -555,17 +555,18 @@ a {
   background-color: rgba(255, 255, 255, 0.5);
 }
 
 .theme-dark .message[severity=error] .stacktrace,
 .theme-dark .message.error .stacktrace {
   background-color: rgba(0, 0, 0, 0.5);
 }
 
-.message[open] .stacktrace {
+.message[open] .stacktrace,
+.message.open .stacktrace {
   display: block;
 }
 
 .message .theme-twisty {
   display: inline-block;
   vertical-align: middle;
   margin: 3px 0 0 0;
   flex-shrink: 0;
--- a/devtools/client/webconsole/new-console-output/components/console-output.js
+++ b/devtools/client/webconsole/new-console-output/components/console-output.js
@@ -14,17 +14,18 @@ const { connect } = require("devtools/cl
 
 const { getAllMessages } = require("devtools/client/webconsole/new-console-output/selectors/messages");
 const MessageContainer = createFactory(require("devtools/client/webconsole/new-console-output/components/message-container").MessageContainer);
 
 const ConsoleOutput = createClass({
 
   propTypes: {
     jsterm: PropTypes.object.isRequired,
-    messages: PropTypes.object.isRequired
+    messages: PropTypes.object.isRequired,
+    onViewSourceInDebugger: PropTypes.func.isRequired,
   },
 
   displayName: "ConsoleOutput",
 
   componentWillUpdate() {
     let node = ReactDOM.findDOMNode(this);
     if (node.lastChild) {
       this.shouldScrollBottom = isScrolledToBottom(node.lastChild, node);
@@ -34,19 +35,20 @@ const ConsoleOutput = createClass({
   componentDidUpdate() {
     if (this.shouldScrollBottom) {
       let node = ReactDOM.findDOMNode(this);
       node.scrollTop = node.scrollHeight;
     }
   },
 
   render() {
-    let messageNodes = this.props.messages.map(function (message) {
+    let {messages, onViewSourceInDebugger} = this.props;
+    let messageNodes = messages.map(function (message) {
       return (
-        MessageContainer({ message, key: message.id })
+        MessageContainer({ message, key: message.id, onViewSourceInDebugger })
       );
     });
     return (
       dom.div({className: "webconsole-output"}, messageNodes)
     );
   }
 });
 
--- a/devtools/client/webconsole/new-console-output/components/message-container.js
+++ b/devtools/client/webconsole/new-console-output/components/message-container.js
@@ -25,27 +25,28 @@ const componentMap = new Map([
   ["EvaluationResult", require("./message-types/evaluation-result").EvaluationResult],
   ["PageError", require("./message-types/page-error").PageError]
 ]);
 
 const MessageContainer = createClass({
   displayName: "MessageContainer",
 
   propTypes: {
-    message: PropTypes.object.isRequired
+    message: PropTypes.object.isRequired,
+    onViewSourceInDebugger: PropTypes.func.isRequired,
   },
 
   shouldComponentUpdate(nextProps, nextState) {
     return this.props.message.repeat !== nextProps.message.repeat;
   },
 
   render() {
-    const { message } = this.props;
+    const { message, onViewSourceInDebugger } = this.props;
     let MessageComponent = createFactory(getMessageComponent(message));
-    return MessageComponent({ message });
+    return MessageComponent({ message, onViewSourceInDebugger });
   }
 });
 
 function getMessageComponent(message) {
   switch (message.source) {
     case MESSAGE_SOURCE.CONSOLE_API:
       return componentMap.get("ConsoleApiCall");
     case MESSAGE_SOURCE.JAVASCRIPT:
--- a/devtools/client/webconsole/new-console-output/components/message-types/console-api-call.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/console-api-call.js
@@ -7,59 +7,86 @@
 "use strict";
 
 // React & Redux
 const {
   createFactory,
   DOM: dom,
   PropTypes
 } = require("devtools/client/shared/vendor/react");
+const StackTrace = createFactory(require("devtools/client/shared/components/stack-trace"));
 const GripMessageBody = createFactory(require("devtools/client/webconsole/new-console-output/components/grip-message-body").GripMessageBody);
 const MessageRepeat = createFactory(require("devtools/client/webconsole/new-console-output/components/message-repeat").MessageRepeat);
 const MessageIcon = createFactory(require("devtools/client/webconsole/new-console-output/components/message-icon").MessageIcon);
 
 ConsoleApiCall.displayName = "ConsoleApiCall";
 
 ConsoleApiCall.propTypes = {
   message: PropTypes.object.isRequired,
+  onViewSourceInDebugger: PropTypes.func.isRequired,
 };
 
 function ConsoleApiCall(props) {
-  const { message } = props;
-  const {category, severity} = message;
+  const { message, onViewSourceInDebugger } = props;
+  const {category, severity, stacktrace, type} = message;
 
-  const messageBody = message.parameters ?
-    message.parameters.map((grip) => GripMessageBody({grip})) :
-    message.messageText;
+  let messageBody;
+  if (type === "trace") {
+    messageBody = [
+      dom.span({className: "cm-variable"}, "console"),
+      ".",
+      dom.span({className: "cm-property"}, "trace"),
+      "():"
+    ];
+  } else if (message.parameters) {
+    messageBody = message.parameters.map((grip) => GripMessageBody({grip}));
+  } else {
+    messageBody = message.messageText;
+  }
 
   const icon = MessageIcon({severity: severity});
   const repeat = MessageRepeat({repeat: message.repeat});
 
+  let attachment = "";
+  if (stacktrace) {
+    attachment = dom.div({className: "stacktrace devtools-monospace"},
+      StackTrace({
+        stacktrace: stacktrace,
+        onViewSourceInDebugger: onViewSourceInDebugger
+      })
+    );
+  }
+
   const classes = ["message", "cm-s-mozilla"];
 
   if (category) {
     classes.push(category);
   }
 
   if (severity) {
     classes.push(severity);
   }
 
+  if (type === "trace") {
+    classes.push("open");
+  }
+
   return dom.div({
     className: classes.join(" ")
   },
     // @TODO add timestamp
     // @TODO add indent if necessary
     icon,
     dom.span({className: "message-body-wrapper"},
       dom.span({},
         dom.span({className: "message-flex-body"},
           dom.span({className: "message-body devtools-monospace"},
             messageBody
           ),
           repeat
-        )
+        ),
+        attachment
       )
     )
   );
 }
 
 module.exports.ConsoleApiCall = ConsoleApiCall;
--- a/devtools/client/webconsole/new-console-output/main.js
+++ b/devtools/client/webconsole/new-console-output/main.js
@@ -13,12 +13,12 @@ const { BrowserLoader } = Cu.import("res
 
 // Initialize module loader and load all modules of the new inline
 // preview feature. The entire code-base doesn't need any extra
 // privileges and runs entirely in content scope.
 const NewConsoleOutputWrapper = BrowserLoader({
   baseURI: "resource://devtools/client/webconsole/new-console-output/",
   window: this}).require("./new-console-output-wrapper");
 
-this.NewConsoleOutput = function (parentNode, jsterm) {
+this.NewConsoleOutput = function (parentNode, jsterm, toolbox) {
   console.log("Creating NewConsoleOutput", parentNode, NewConsoleOutputWrapper);
-  return new NewConsoleOutputWrapper(parentNode, jsterm);
+  return new NewConsoleOutputWrapper(parentNode, jsterm, toolbox);
 };
--- a/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
+++ b/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
@@ -11,18 +11,25 @@ const { Provider } = require("devtools/c
 const actions = require("devtools/client/webconsole/new-console-output/actions/messages");
 const { configureStore } = require("devtools/client/webconsole/new-console-output/store");
 
 const ConsoleOutput = React.createFactory(require("devtools/client/webconsole/new-console-output/components/console-output"));
 const FilterBar = React.createFactory(require("devtools/client/webconsole/new-console-output/components/filter-bar"));
 
 const store = configureStore();
 
-function NewConsoleOutputWrapper(parentNode, jsterm) {
-  let childComponent = ConsoleOutput({ jsterm });
+function NewConsoleOutputWrapper(parentNode, jsterm, toolbox) {
+  let childComponent = ConsoleOutput({
+    jsterm,
+    onViewSourceInDebugger: frame => toolbox.viewSourceInDebugger.call(
+      toolbox,
+      frame.url,
+      frame.line
+    )
+  });
   let filterBar = FilterBar({});
   let provider = React.createElement(
     Provider,
     { store },
     React.DOM.div(
       {className: "webconsole-output-wrapper"},
       filterBar,
       childComponent
--- a/devtools/client/webconsole/new-console-output/test/fixtures/stubs.js
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stubs.js
@@ -23,16 +23,17 @@ exports.stubConsoleMessages = new Map([
       type: MESSAGE_TYPE.LOG,
       level: MESSAGE_LEVEL.LOG,
       messageText: null,
       parameters: ["foobar", "test"],
       repeat: 1,
       repeatId: null,
       category: CATEGORY_WEBDEV,
       severity: SEVERITY_LOG,
+      stacktrace: undefined
     })
   ],
   [
     "console.warn('danger, will robinson!')",
     new ConsoleMessage({
       allowRepeating: true,
       source: MESSAGE_SOURCE.CONSOLE_API,
       type: MESSAGE_TYPE.LOG,
--- a/devtools/client/webconsole/new-console-output/types.js
+++ b/devtools/client/webconsole/new-console-output/types.js
@@ -31,9 +31,10 @@ exports.ConsoleMessage = Immutable.Recor
   type: null,
   level: null,
   messageText: null,
   parameters: null,
   repeat: 1,
   repeatId: null,
   category: "output",
   severity: "log",
+  stacktrace: null,
 });
--- a/devtools/client/webconsole/new-console-output/utils/messages.js
+++ b/devtools/client/webconsole/new-console-output/utils/messages.js
@@ -72,16 +72,17 @@ function transformPacket(packet) {
       return new ConsoleMessage({
         source: MESSAGE_SOURCE.CONSOLE_API,
         type,
         level,
         parameters,
         messageText,
         category: CATEGORY_WEBDEV,
         severity: level,
+        stacktrace: message.stacktrace,
       });
     }
 
     case "pageError": {
       let { pageError } = packet;
       let level = MESSAGE_LEVEL.ERROR;
       if (pageError.warning || pageError.strict) {
         level = MESSAGE_LEVEL.WARN;
--- a/devtools/client/webconsole/webconsole.js
+++ b/devtools/client/webconsole/webconsole.js
@@ -534,41 +534,42 @@ WebConsoleFrame.prototype = {
     clearButton.addEventListener("command", () => {
       this.owner._onClearButton();
       this.jsterm.clearOutput(true);
     });
 
     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;
       console.log("Entering experimental mode for console frontend");
 
       // 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.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);
+      this.newConsoleOutput = new this.window.NewConsoleOutput(this.experimentalOutputNode, this.jsterm, toolbox);
       console.log("Created newConsoleOutput", this.newConsoleOutput);
 
       let filterToolbar = doc.querySelector(".hud-console-filter-toolbar");
       filterToolbar.hidden = true;
     }
 
     this.resize();
     this.window.addEventListener("resize", this.resize, true);
     this.jsterm.on("sidebar-opened", this.resize);
     this.jsterm.on("sidebar-closed", this.resize);
 
-    let toolbox = gDevTools.getToolbox(this.owner.target);
     if (toolbox) {
       toolbox.on("webconsole-selected", this._onPanelSelected);
     }
 
     /*
      * Focus the input line whenever the output area is clicked.
      */
     this.outputWrapper.addEventListener("click", (event) => {