Bug 1307892 - Add support for network event update message r?nchevobbe draft
authorRicky Chien <rchien@mozilla.com>
Sun, 29 Jan 2017 13:07:58 +0800
changeset 467951 d6f072aea3d9a429d44c9c8b9653c46860cab65a
parent 467834 71224049c0b52ab190564d3ea0eab089a159a4cf
child 543808 f04fe458a70fdf74878bbe18989fcfdb5f835346
push id43311
push userbmo:rchien@mozilla.com
push dateMon, 30 Jan 2017 12:07:59 +0000
reviewersnchevobbe
bugs1307892
milestone54.0a1
Bug 1307892 - Add support for network event update message r?nchevobbe MozReview-Commit-ID: 4lat3RYa4YN
devtools/client/themes/webconsole.css
devtools/client/webconsole/new-console-output/actions/messages.js
devtools/client/webconsole/new-console-output/components/message-container.js
devtools/client/webconsole/new-console-output/components/message-types/network-event-message.js
devtools/client/webconsole/new-console-output/constants.js
devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
devtools/client/webconsole/new-console-output/reducers/messages.js
devtools/client/webconsole/new-console-output/test/components/network-event-message.test.js
devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_network_event.js
devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/head.js
devtools/client/webconsole/new-console-output/test/fixtures/stubs/networkEvent.js
devtools/client/webconsole/new-console-output/types.js
devtools/client/webconsole/new-console-output/utils/id-generator.js
devtools/client/webconsole/new-console-output/utils/messages.js
devtools/client/webconsole/webconsole-connection-proxy.js
--- a/devtools/client/themes/webconsole.css
+++ b/devtools/client/themes/webconsole.css
@@ -739,30 +739,34 @@ a.learn-more-link.webconsole-learn-more-
 .message.info > .icon::before {
   background-position: -36px -36px;
 }
 
 .message.network .method {
   margin-inline-end: 5px;
 }
 
+.network .message-flex-body > .message-body {
+  display: flex;
+}
+
 .webconsole-output-wrapper .message .indent {
   display: inline-block;
   border-inline-end: solid 1px var(--theme-splitter-color);
 }
 
 .message.startGroup .indent,
 .message.startGroupCollapsed .indent {
   border-inline-end-color: transparent;
   margin-inline-end: 5px;
 }
 
 .message.startGroup .icon,
 .message.startGroupCollapsed .icon {
-    display: none;
+  display: none;
 }
 
 /* console.table() */
 .new-consoletable {
   width: 100%;
   border-collapse: collapse;
   --consoletable-border: 1px solid var(--table-splitter-color);
 }
--- a/devtools/client/webconsole/new-console-output/actions/messages.js
+++ b/devtools/client/webconsole/new-console-output/actions/messages.js
@@ -8,16 +8,17 @@
 
 const {
   prepareMessage
 } = require("devtools/client/webconsole/new-console-output/utils/messages");
 const { IdGenerator } = require("devtools/client/webconsole/new-console-output/utils/id-generator");
 const { batchActions } = require("devtools/client/webconsole/new-console-output/actions/enhancers");
 const {
   MESSAGE_ADD,
+  NETWORK_MESSAGE_UPDATE,
   MESSAGES_CLEAR,
   MESSAGE_OPEN,
   MESSAGE_CLOSE,
   MESSAGE_TYPE,
   MESSAGE_TABLE_RECEIVE,
 } = require("../constants");
 
 const defaultIdGenerator = new IdGenerator();
@@ -85,15 +86,30 @@ function messageTableDataGet(id, client,
 function messageTableDataReceive(id, data) {
   return {
     type: MESSAGE_TABLE_RECEIVE,
     id,
     data
   };
 }
 
+function networkMessageUpdate(packet, idGenerator = null) {
+  if (idGenerator == null) {
+    idGenerator = defaultIdGenerator;
+  }
+
+  let message = prepareMessage(packet, idGenerator);
+
+  return {
+    type: NETWORK_MESSAGE_UPDATE,
+    message,
+  };
+}
+
 module.exports = {
   messageAdd,
   messagesClear,
   messageOpen,
   messageClose,
   messageTableDataGet,
+  networkMessageUpdate,
 };
+
--- a/devtools/client/webconsole/new-console-output/components/message-container.js
+++ b/devtools/client/webconsole/new-console-output/components/message-container.js
@@ -45,17 +45,20 @@ const MessageContainer = createClass({
       indent: 0,
     };
   },
 
   shouldComponentUpdate(nextProps, nextState) {
     const repeatChanged = this.props.message.repeat !== nextProps.message.repeat;
     const openChanged = this.props.open !== nextProps.open;
     const tableDataChanged = this.props.tableData !== nextProps.tableData;
-    return repeatChanged || openChanged || tableDataChanged;
+    const responseChanged = this.props.message.response !== nextProps.message.response;
+    const totalTimeChanged = this.props.message.totalTime !== nextProps.message.totalTime;
+    return repeatChanged || openChanged || tableDataChanged || responseChanged ||
+      totalTimeChanged;
   },
 
   render() {
     const { message } = this.props;
 
     let MessageComponent = createFactory(getMessageComponent(message));
     return MessageComponent(this.props);
   }
--- a/devtools/client/webconsole/new-console-output/components/message-types/network-event-message.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/network-event-message.js
@@ -24,34 +24,59 @@ NetworkEventMessage.propTypes = {
   }),
   indent: PropTypes.number.isRequired,
 };
 
 NetworkEventMessage.defaultProps = {
   indent: 0,
 };
 
-function NetworkEventMessage(props) {
-  const { message, serviceContainer, indent } = props;
-  const { actor, source, type, level, request, isXHR, timeStamp } = message;
+function NetworkEventMessage({
+  indent,
+  message = {},
+  serviceContainer,
+}) {
+  const {
+    actor,
+    source,
+    type,
+    level,
+    request,
+    response: {
+      httpVersion,
+      status,
+      statusText,
+    },
+    isXHR,
+    timeStamp,
+    totalTime,
+  } = message;
 
   const topLevelClasses = [ "cm-s-mozilla" ];
+  let statusInfo;
 
-  function onUrlClick() {
+  if (httpVersion && status && statusText && totalTime !== undefined) {
+    statusInfo = `[${httpVersion} ${status} ${statusText} ${totalTime}ms]`;
+  }
+
+  function openNetworkMonitor() {
     serviceContainer.openNetworkPanel(actor);
   }
 
   const method = dom.span({className: "method" }, request.method);
   const xhr = isXHR
     ? dom.span({ className: "xhr" }, l10n.getStr("webConsoleXhrIndicator"))
     : null;
-  const url = dom.a({ className: "url", title: request.url, onClick: onUrlClick },
-        request.url.replace(/\?.+/, ""));
+  const url = dom.a({ className: "url", title: request.url, onClick: openNetworkMonitor },
+    request.url.replace(/\?.+/, ""));
+  const statusBody = statusInfo
+    ? dom.a({ className: "status", onClick: openNetworkMonitor }, statusInfo)
+    : null;
 
-  const messageBody = dom.span({}, method, xhr, url);
+  const messageBody = [method, xhr, url, statusBody];
 
   const childProps = {
     source,
     type,
     level,
     indent,
     topLevelClasses,
     timeStamp,
--- a/devtools/client/webconsole/new-console-output/constants.js
+++ b/devtools/client/webconsole/new-console-output/constants.js
@@ -6,16 +6,17 @@
 "use strict";
 
 const actionTypes = {
   BATCH_ACTIONS: "BATCH_ACTIONS",
   MESSAGE_ADD: "MESSAGE_ADD",
   MESSAGES_CLEAR: "MESSAGES_CLEAR",
   MESSAGE_OPEN: "MESSAGE_OPEN",
   MESSAGE_CLOSE: "MESSAGE_CLOSE",
+  NETWORK_MESSAGE_UPDATE: "NETWORK_MESSAGE_UPDATE",
   MESSAGE_TABLE_RECEIVE: "MESSAGE_TABLE_RECEIVE",
   TIMESTAMPS_TOGGLE: "TIMESTAMPS_TOGGLE",
   FILTER_TOGGLE: "FILTER_TOGGLE",
   FILTER_TEXT_SET: "FILTER_TEXT_SET",
   FILTERS_CLEAR: "FILTERS_CLEAR",
   FILTER_BAR_TOGGLE: "FILTER_BAR_TOGGLE",
 };
 
--- a/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
+++ b/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
@@ -129,17 +129,17 @@ NewConsoleOutputWrapper.prototype = {
     // 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.get("id");
       return new Promise(resolve => {
         let jsterm = this.jsterm;
         jsterm.hud.on("new-messages", function onThisMessage(e, messages) {
           for (let m of messages) {
-            if (m.messageId == messageId) {
+            if (m.messageId === messageId) {
               resolve(m.node);
               jsterm.hud.off("new-messages", onThisMessage);
               return;
             }
           }
         });
       });
     }
@@ -155,16 +155,26 @@ NewConsoleOutputWrapper.prototype = {
   dispatchMessagesClear: function () {
     store.dispatch(actions.messagesClear());
   },
 
   dispatchTimestampsToggle: function (enabled) {
     store.dispatch(actions.timestampsToggle(enabled));
   },
 
+  dispatchMessageUpdate: function (message, res) {
+    batchedMessageAdd(actions.networkMessageUpdate(message));
+
+    // network-message-updated will emit when eventTimings message arrives
+    // which is the last one of 8 updates happening on network message update.
+    if (res.packet.updateType === "eventTimings") {
+      this.jsterm.hud.emit("network-message-updated", res);
+    }
+  },
+
   // Should be used for test purpose only.
   getStore: function () {
     return store;
   }
 };
 
 function batchedMessageAdd(action) {
   queuedActions.push(action);
--- a/devtools/client/webconsole/new-console-output/reducers/messages.js
+++ b/devtools/client/webconsole/new-console-output/reducers/messages.js
@@ -93,16 +93,21 @@ function messages(state = new MessageSta
     case constants.MESSAGE_OPEN:
       return state.set("messagesUiById", messagesUiById.push(action.id));
     case constants.MESSAGE_CLOSE:
       let index = state.messagesUiById.indexOf(action.id);
       return state.deleteIn(["messagesUiById", index]);
     case constants.MESSAGE_TABLE_RECEIVE:
       const {id, data} = action;
       return state.set("messagesTableDataById", messagesTableDataById.set(id, data));
+    case constants.NETWORK_MESSAGE_UPDATE:
+      let updateMessage = action.message;
+      return state.set("messagesById", messagesById.map((message) =>
+        (message.id === updateMessage.id) ? updateMessage : message
+      ));
   }
 
   return state;
 }
 
 function getNewCurrentGroup(currentGoup, groupsById) {
   let newCurrentGroup = null;
   if (currentGoup) {
--- a/devtools/client/webconsole/new-console-output/test/components/network-event-message.test.js
+++ b/devtools/client/webconsole/new-console-output/test/components/network-event-message.test.js
@@ -13,33 +13,33 @@ const { createFactory } = require("devto
 const NetworkEventMessage = createFactory(require("devtools/client/webconsole/new-console-output/components/message-types/network-event-message"));
 const { INDENT_WIDTH } = require("devtools/client/webconsole/new-console-output/components/message-indent");
 
 // Test fakes.
 const { stubPreparedMessages } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs/index");
 const serviceContainer = require("devtools/client/webconsole/new-console-output/test/fixtures/serviceContainer");
 
 const EXPECTED_URL = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/inexistent.html";
+const EXPECTED_STATUS = /\[HTTP\/\d\.\d \d+ [A-Za-z ]+ \d+ms\]/;
 
 describe("NetworkEventMessage component:", () => {
   describe("GET request", () => {
     it("renders as expected", () => {
-      const message = stubPreparedMessages.get("GET request");
+      const message = stubPreparedMessages.get("GET request eventTimings");
       const wrapper = render(NetworkEventMessage({ message, serviceContainer }));
       const L10n = require("devtools/client/webconsole/new-console-output/test/fixtures/L10n");
       const { timestampString } = new L10n();
 
       expect(wrapper.find(".timestamp").text()).toBe(timestampString(message.timeStamp));
       expect(wrapper.find(".message-body .method").text()).toBe("GET");
       expect(wrapper.find(".message-body .xhr").length).toBe(0);
       expect(wrapper.find(".message-body .url").length).toBe(1);
       expect(wrapper.find(".message-body .url").text()).toBe(EXPECTED_URL);
-      expect(wrapper
-        .find("div.message.cm-s-mozilla span.message-body.devtools-monospace").length
-      ).toBe(1);
+      expect(wrapper.find(".message-body .status").length).toBe(1);
+      expect(wrapper.find(".message-body .status").text()).toMatch(EXPECTED_STATUS);
     });
 
     it("has the expected indent", () => {
       const message = stubPreparedMessages.get("GET request");
 
       const indent = 10;
       let wrapper = render(NetworkEventMessage({ message, serviceContainer, indent}));
       expect(wrapper.find(".indent").prop("style").width)
@@ -47,35 +47,34 @@ describe("NetworkEventMessage component:
 
       wrapper = render(NetworkEventMessage({ message, serviceContainer }));
       expect(wrapper.find(".indent").prop("style").width).toBe(`0`);
     });
   });
 
   describe("XHR GET request", () => {
     it("renders as expected", () => {
-      const message = stubPreparedMessages.get("XHR GET request");
+      const message = stubPreparedMessages.get("XHR GET request eventTimings");
       const wrapper = render(NetworkEventMessage({ message, serviceContainer }));
 
       expect(wrapper.find(".message-body .method").text()).toBe("GET");
       expect(wrapper.find(".message-body .xhr").length).toBe(1);
       expect(wrapper.find(".message-body .xhr").text()).toBe("XHR");
       expect(wrapper.find(".message-body .url").text()).toBe(EXPECTED_URL);
-      let selector = "div.message.cm-s-mozilla span.message-body.devtools-monospace";
-      expect(wrapper.find(selector).length).toBe(1);
+      expect(wrapper.find(".message-body .status").text()).toMatch(EXPECTED_STATUS);
     });
   });
 
   describe("XHR POST request", () => {
     it("renders as expected", () => {
-      const message = stubPreparedMessages.get("XHR POST request");
+      const message = stubPreparedMessages.get("XHR POST request eventTimings");
       const wrapper = render(NetworkEventMessage({ message, serviceContainer }));
 
       expect(wrapper.find(".message-body .method").text()).toBe("POST");
       expect(wrapper.find(".message-body .xhr").length).toBe(1);
       expect(wrapper.find(".message-body .xhr").text()).toBe("XHR");
       expect(wrapper.find(".message-body .url").length).toBe(1);
       expect(wrapper.find(".message-body .url").text()).toBe(EXPECTED_URL);
-      let selector = "div.message.cm-s-mozilla span.message-body.devtools-monospace";
-      expect(wrapper.find(selector).length);
+      expect(wrapper.find(".message-body .status").length).toBe(1);
+      expect(wrapper.find(".message-body .status").text()).toMatch(EXPECTED_STATUS);
     });
   });
 });
--- a/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_network_event.js
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_network_event.js
@@ -19,29 +19,41 @@ add_task(function* () {
   for (let {keys, code} of snippets.values()) {
     OS.File.writeAtomic(TEMP_FILE_PATH, `function triggerPacket() {${code}}`);
     let toolbox = yield openNewTabAndToolbox(TEST_URI, "webconsole");
     let {ui} = toolbox.getCurrentPanel().hud;
 
     ok(ui.jsterm, "jsterm exists");
     ok(ui.newConsoleOutput, "newConsoleOutput exists");
 
-    let received = new Promise(resolve => {
+    let networkEvent = new Promise(resolve => {
       let i = 0;
-      toolbox.target.client.addListener(TARGET, (type, res) => {
+      toolbox.target.activeConsole.on(TARGET, (type, res) => {
         stubs.packets.push(formatPacket(keys[i], res));
-        stubs.preparedMessages.push(formatNetworkStub(keys[i], res));
+        stubs.preparedMessages.push(formatNetworkEventStub(keys[i], res));
         if (++i === keys.length) {
           resolve();
         }
       });
     });
 
+    let networkEventUpdate = new Promise(resolve => {
+      let i = 0;
+      ui.jsterm.hud.on("network-message-updated", function onNetworkUpdated(event, res) {
+        ui.jsterm.hud.off("network-message-updated", onNetworkUpdated);
+        stubs.preparedMessages.push(
+          formatNetworkEventStub(`${keys[i++]} ${res.packet.updateType}`, res));
+        if (i === keys.length) {
+          resolve();
+        }
+      });
+    });
+
     yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function () {
       content.wrappedJSObject.triggerPacket();
     });
 
-    yield received;
+    yield Promise.all([networkEvent, networkEventUpdate]);
   }
   let filePath = OS.Path.join(`${BASE_PATH}/stubs/${TARGET}.js`);
   OS.File.writeAtomic(filePath, formatFile(stubs, "NetworkEventMessage"));
   OS.File.writeAtomic(TEMP_FILE_PATH, "");
 });
--- a/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/head.js
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/head.js
@@ -1,15 +1,15 @@
 /* -*- 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/ */
 /* import-globals-from ../../../../../framework/test/shared-head.js */
 /* exported TEMP_FILE_PATH, TEMP_CSS_FILE_PATH, formatPacket, formatStub,
-            formatNetworkStub, formatFile */
+            formatNetworkEventStub, formatFile */
 "use strict";
 
 // shared-head.js handles imports, constants, and utility functions
 // Load the shared-head file first.
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js",
   this);
 
@@ -128,41 +128,19 @@ function formatStub(key, packet) {
   let prepared = prepareMessage(
     getCleanedPacket(key, packet),
     {getNextId: () => "1"}
   );
   let stringifiedMessage = JSON.stringify(prepared, null, 2);
   return `stubPreparedMessages.set("${key}", new ConsoleMessage(${stringifiedMessage}));`;
 }
 
-function formatNetworkStub(key, packet) {
-  let actor = packet.eventActor;
-  let networkInfo = {
-    _type: "NetworkEvent",
-    timeStamp: actor.timeStamp,
-    node: null,
-    actor: actor.actor,
-    discardRequestBody: true,
-    discardResponseBody: true,
-    startedDateTime: actor.startedDateTime,
-    request: {
-      url: actor.url,
-      method: actor.method,
-    },
-    isXHR: actor.isXHR,
-    cause: actor.cause,
-    response: {},
-    timings: {},
-    // track the list of network event updates
-    updates: [],
-    private: actor.private,
-    fromCache: actor.fromCache,
-    fromServiceWorker: actor.fromServiceWorker
-  };
-  let prepared = prepareMessage(networkInfo, {getNextId: () => "1"});
+function formatNetworkEventStub(key, packet) {
+  let networkInfo = packet.actor ? packet : packet.networkInfo;
+  let prepared = prepareMessage(networkInfo, {getNextId: () => networkInfo.actor});
   let stringifiedMessage = JSON.stringify(prepared, null, 2);
   return `stubPreparedMessages.set("${key}", ` +
     `new NetworkEventMessage(${stringifiedMessage}));`;
 }
 
 function formatFile(stubs, type) {
   return `/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */
--- a/devtools/client/webconsole/new-console-output/test/fixtures/stubs/networkEvent.js
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stubs/networkEvent.js
@@ -9,182 +9,275 @@
  */
 
 const { NetworkEventMessage } =
   require("devtools/client/webconsole/new-console-output/types");
 
 let stubPreparedMessages = new Map();
 let stubPackets = new Map();
 stubPreparedMessages.set("GET request", new NetworkEventMessage({
-  "id": "1",
-  "actor": "server1.conn0.child1/netEvent29",
+  "id": "server1.conn0.child1/netEvent30",
+  "actor": "server1.conn0.child1/netEvent30",
   "level": "log",
   "isXHR": false,
   "request": {
     "url": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/inexistent.html",
     "method": "GET"
   },
   "response": {},
   "source": "network",
   "type": "log",
-  "timeStamp": 1479159937660,
-  "groupId": null
+  "groupId": null,
+  "timeStamp": 1485777989897
+}));
+
+stubPreparedMessages.set("GET request eventTimings", new NetworkEventMessage({
+  "id": "server1.conn0.child1/netEvent30",
+  "actor": "server1.conn0.child1/netEvent30",
+  "level": "log",
+  "isXHR": false,
+  "request": {
+    "url": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/inexistent.html",
+    "method": "GET",
+    "headersSize": 489
+  },
+  "response": {
+    "httpVersion": "HTTP/1.1",
+    "status": "404",
+    "statusText": "Not Found",
+    "headersSize": 160,
+    "remoteAddress": "127.0.0.1",
+    "remotePort": 8888
+  },
+  "source": "network",
+  "type": "log",
+  "groupId": null,
+  "timeStamp": 1485777989897,
+  "totalTime": 7
 }));
 
 stubPreparedMessages.set("XHR GET request", new NetworkEventMessage({
-  "id": "1",
-  "actor": "server1.conn1.child1/netEvent29",
+  "id": "server1.conn1.child1/netEvent30",
+  "actor": "server1.conn1.child1/netEvent30",
   "level": "log",
   "isXHR": true,
   "request": {
     "url": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/inexistent.html",
     "method": "GET"
   },
   "response": {},
   "source": "network",
   "type": "log",
-  "timeStamp": 1479159938522,
-  "groupId": null
+  "groupId": null,
+  "timeStamp": 1485777990639
+}));
+
+stubPreparedMessages.set("XHR GET request eventTimings", new NetworkEventMessage({
+  "id": "server1.conn1.child1/netEvent30",
+  "actor": "server1.conn1.child1/netEvent30",
+  "level": "log",
+  "isXHR": true,
+  "request": {
+    "url": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/inexistent.html",
+    "method": "GET",
+    "headersSize": 489
+  },
+  "response": {
+    "httpVersion": "HTTP/1.1",
+    "status": "404",
+    "statusText": "Not Found",
+    "headersSize": 160,
+    "remoteAddress": "127.0.0.1",
+    "remotePort": 8888
+  },
+  "source": "network",
+  "type": "log",
+  "groupId": null,
+  "timeStamp": 1485777990639,
+  "totalTime": 10
 }));
 
 stubPreparedMessages.set("XHR POST request", new NetworkEventMessage({
-  "id": "1",
-  "actor": "server1.conn2.child1/netEvent29",
+  "id": "server1.conn2.child1/netEvent30",
+  "actor": "server1.conn2.child1/netEvent30",
   "level": "log",
   "isXHR": true,
   "request": {
     "url": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/inexistent.html",
     "method": "POST"
   },
   "response": {},
   "source": "network",
   "type": "log",
-  "timeStamp": 1479159939328,
-  "groupId": null
+  "groupId": null,
+  "timeStamp": 1485777991739
+}));
+
+stubPreparedMessages.set("XHR POST request eventTimings", new NetworkEventMessage({
+  "id": "server1.conn2.child1/netEvent30",
+  "actor": "server1.conn2.child1/netEvent30",
+  "level": "log",
+  "isXHR": true,
+  "request": {
+    "url": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/inexistent.html",
+    "method": "POST",
+    "headersSize": 509
+  },
+  "response": {
+    "httpVersion": "HTTP/1.1",
+    "status": "404",
+    "statusText": "Not Found",
+    "headersSize": 160,
+    "remoteAddress": "127.0.0.1",
+    "remotePort": 8888
+  },
+  "source": "network",
+  "type": "log",
+  "groupId": null,
+  "timeStamp": 1485777991739,
+  "totalTime": 9
 }));
 
 stubPackets.set("GET request", {
-  "from": "server1.conn0.child1/consoleActor2",
-  "type": "networkEvent",
-  "eventActor": {
-    "actor": "server1.conn0.child1/netEvent29",
-    "startedDateTime": "2016-10-15T23:12:04.196Z",
-    "timeStamp": 1479159937660,
+  "_type": "NetworkEvent",
+  "timeStamp": 1485777989897,
+  "node": null,
+  "actor": "server1.conn0.child1/netEvent30",
+  "discardRequestBody": true,
+  "discardResponseBody": true,
+  "startedDateTime": "2017-01-30T12:06:29.897Z",
+  "request": {
     "url": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/inexistent.html",
-    "method": "GET",
-    "isXHR": false,
-    "cause": {
-      "type": 3,
-      "loadingDocumentUri": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-network-event.html",
-      "stacktrace": [
-        {
-          "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js",
-          "lineNumber": 3,
-          "columnNumber": 1,
-          "functionName": "triggerPacket",
-          "asyncCause": null
-        },
-        {
-          "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js line 52 > eval",
-          "lineNumber": 4,
-          "columnNumber": 7,
-          "functionName": null,
-          "asyncCause": null
-        },
-        {
-          "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js",
-          "lineNumber": 53,
-          "columnNumber": 20,
-          "functionName": null,
-          "asyncCause": null
-        }
-      ]
-    },
-    "private": false
-  }
+    "method": "GET"
+  },
+  "isXHR": false,
+  "cause": {
+    "type": "img",
+    "loadingDocumentUri": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-network-event.html",
+    "stacktrace": [
+      {
+        "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js",
+        "lineNumber": 3,
+        "columnNumber": 1,
+        "functionName": "triggerPacket",
+        "asyncCause": null
+      },
+      {
+        "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js line 52 > eval",
+        "lineNumber": 4,
+        "columnNumber": 7,
+        "functionName": null,
+        "asyncCause": null
+      },
+      {
+        "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js",
+        "lineNumber": 53,
+        "columnNumber": 20,
+        "functionName": null,
+        "asyncCause": null
+      }
+    ]
+  },
+  "response": {},
+  "timings": {},
+  "updates": [],
+  "private": false,
+  "from": "server1.conn0.child1/consoleActor2"
 });
 
 stubPackets.set("XHR GET request", {
-  "from": "server1.conn1.child1/consoleActor2",
-  "type": "networkEvent",
-  "eventActor": {
-    "actor": "server1.conn1.child1/netEvent29",
-    "startedDateTime": "2016-10-15T23:12:05.690Z",
-    "timeStamp": 1479159938522,
+  "_type": "NetworkEvent",
+  "timeStamp": 1485777990639,
+  "node": null,
+  "actor": "server1.conn1.child1/netEvent30",
+  "discardRequestBody": true,
+  "discardResponseBody": true,
+  "startedDateTime": "2017-01-30T12:06:30.639Z",
+  "request": {
     "url": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/inexistent.html",
-    "method": "GET",
-    "isXHR": true,
-    "cause": {
-      "type": 11,
-      "loadingDocumentUri": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-network-event.html",
-      "stacktrace": [
-        {
-          "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js",
-          "lineNumber": 4,
-          "columnNumber": 1,
-          "functionName": "triggerPacket",
-          "asyncCause": null
-        },
-        {
-          "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js line 52 > eval",
-          "lineNumber": 4,
-          "columnNumber": 7,
-          "functionName": null,
-          "asyncCause": null
-        },
-        {
-          "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js",
-          "lineNumber": 53,
-          "columnNumber": 20,
-          "functionName": null,
-          "asyncCause": null
-        }
-      ]
-    },
-    "private": false
-  }
+    "method": "GET"
+  },
+  "isXHR": true,
+  "cause": {
+    "type": "xhr",
+    "loadingDocumentUri": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-network-event.html",
+    "stacktrace": [
+      {
+        "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js",
+        "lineNumber": 4,
+        "columnNumber": 1,
+        "functionName": "triggerPacket",
+        "asyncCause": null
+      },
+      {
+        "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js line 52 > eval",
+        "lineNumber": 4,
+        "columnNumber": 7,
+        "functionName": null,
+        "asyncCause": null
+      },
+      {
+        "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js",
+        "lineNumber": 53,
+        "columnNumber": 20,
+        "functionName": null,
+        "asyncCause": null
+      }
+    ]
+  },
+  "response": {},
+  "timings": {},
+  "updates": [],
+  "private": false,
+  "from": "server1.conn1.child1/consoleActor2"
 });
 
 stubPackets.set("XHR POST request", {
-  "from": "server1.conn2.child1/consoleActor2",
-  "type": "networkEvent",
-  "eventActor": {
-    "actor": "server1.conn2.child1/netEvent29",
-    "startedDateTime": "2016-10-15T23:12:07.158Z",
-    "timeStamp": 1479159939328,
+  "_type": "NetworkEvent",
+  "timeStamp": 1485777991739,
+  "node": null,
+  "actor": "server1.conn2.child1/netEvent30",
+  "discardRequestBody": true,
+  "discardResponseBody": true,
+  "startedDateTime": "2017-01-30T12:06:31.739Z",
+  "request": {
     "url": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/inexistent.html",
-    "method": "POST",
-    "isXHR": true,
-    "cause": {
-      "type": 11,
-      "loadingDocumentUri": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-network-event.html",
-      "stacktrace": [
-        {
-          "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js",
-          "lineNumber": 4,
-          "columnNumber": 1,
-          "functionName": "triggerPacket",
-          "asyncCause": null
-        },
-        {
-          "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js line 52 > eval",
-          "lineNumber": 4,
-          "columnNumber": 7,
-          "functionName": null,
-          "asyncCause": null
-        },
-        {
-          "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js",
-          "lineNumber": 53,
-          "columnNumber": 20,
-          "functionName": null,
-          "asyncCause": null
-        }
-      ]
-    },
-    "private": false
-  }
+    "method": "POST"
+  },
+  "isXHR": true,
+  "cause": {
+    "type": "xhr",
+    "loadingDocumentUri": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-network-event.html",
+    "stacktrace": [
+      {
+        "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js",
+        "lineNumber": 4,
+        "columnNumber": 1,
+        "functionName": "triggerPacket",
+        "asyncCause": null
+      },
+      {
+        "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js line 52 > eval",
+        "lineNumber": 4,
+        "columnNumber": 7,
+        "functionName": null,
+        "asyncCause": null
+      },
+      {
+        "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js",
+        "lineNumber": 53,
+        "columnNumber": 20,
+        "functionName": null,
+        "asyncCause": null
+      }
+    ]
+  },
+  "response": {},
+  "timings": {},
+  "updates": [],
+  "private": false,
+  "from": "server1.conn2.child1/consoleActor2"
 });
 
 module.exports = {
   stubPreparedMessages,
   stubPackets,
 };
--- a/devtools/client/webconsole/new-console-output/types.js
+++ b/devtools/client/webconsole/new-console-output/types.js
@@ -45,11 +45,12 @@ exports.NetworkEventMessage = Immutable.
   id: null,
   actor: null,
   level: MESSAGE_LEVEL.LOG,
   isXHR: false,
   request: null,
   response: null,
   source: MESSAGE_SOURCE.NETWORK,
   type: MESSAGE_TYPE.LOG,
+  groupId: null,
   timeStamp: null,
-  groupId: null,
+  totalTime: null,
 });
--- a/devtools/client/webconsole/new-console-output/utils/id-generator.js
+++ b/devtools/client/webconsole/new-console-output/utils/id-generator.js
@@ -6,17 +6,12 @@
 
 "use strict";
 
 exports.IdGenerator = class IdGenerator {
   constructor() {
     this.messageId = 1;
   }
 
-  getNextId() {
-    // Return the next message id, as a string.
-    return "" + this.messageId++;
-  }
-
-  getCurrentId() {
-    return this.messageId;
+  getNextId(packet) {
+    return (packet && packet.actor) ? packet.actor : "" + this.messageId++;
   }
 };
--- a/devtools/client/webconsole/new-console-output/utils/messages.js
+++ b/devtools/client/webconsole/new-console-output/utils/messages.js
@@ -24,17 +24,17 @@ function prepareMessage(packet, idGenera
   // This packet is already in the expected packet structure. Simply return.
   if (!packet.source) {
     packet = transformPacket(packet);
   }
 
   if (packet.allowRepeating) {
     packet = packet.set("repeatId", getRepeatId(packet));
   }
-  return packet.set("id", idGenerator.getNextId());
+  return packet.set("id", idGenerator.getNextId(packet));
 }
 
 /**
  * Transforms a packet from Firefox RDP structure to Chrome RDP structure.
  */
 function transformPacket(packet) {
   if (packet._type) {
     packet = convertCachedPacket(packet);
@@ -177,17 +177,18 @@ function transformPacket(packet) {
     case "networkEvent": {
       let { networkEvent } = packet;
 
       return new NetworkEventMessage({
         actor: networkEvent.actor,
         isXHR: networkEvent.isXHR,
         request: networkEvent.request,
         response: networkEvent.response,
-        timeStamp: networkEvent.timeStamp
+        timeStamp: networkEvent.timeStamp,
+        totalTime: networkEvent.totalTime,
       });
     }
 
     case "evaluationResult":
     default: {
       let {
         exceptionMessage: messageText,
         exceptionDocURL,
--- a/devtools/client/webconsole/webconsole-connection-proxy.js
+++ b/devtools/client/webconsole/webconsole-connection-proxy.js
@@ -223,28 +223,35 @@ WebConsoleConnectionProxy.prototype = {
     this.webConsoleClient.getCachedMessages(msgs, this._onCachedMessages);
 
     this.webConsoleFrame._onUpdateListeners();
   },
 
   /**
    * Dispatch a message add on the new frontend and emit an event for tests.
    */
-  dispatchMessageAdd: function(packet) {
+  dispatchMessageAdd: function (packet) {
     this.webConsoleFrame.newConsoleOutput.dispatchMessageAdd(packet);
   },
 
   /**
    * Batched dispatch of messages.
    */
-  dispatchMessagesAdd: function(packets) {
+  dispatchMessagesAdd: function (packets) {
     this.webConsoleFrame.newConsoleOutput.dispatchMessagesAdd(packets);
   },
 
   /**
+   * Dispatch a message event on the new frontend and emit an event for tests.
+   */
+  dispatchMessageUpdate: function (networkInfo, response) {
+    this.webConsoleFrame.newConsoleOutput.dispatchMessageUpdate(networkInfo, response);
+  },
+
+  /**
    * The "cachedMessages" response handler.
    *
    * @private
    * @param object response
    *        The JSON response object received from the server.
    */
   _onCachedMessages: function (response) {
     if (response.error) {
@@ -355,23 +362,25 @@ WebConsoleConnectionProxy.prototype = {
 
   /**
    * The "networkEventUpdate" 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.
-   * @param object networkInfo
-   *        The network request information.
+   * @param object response
+   *        The update response received from the server.
    */
-  _onNetworkEventUpdate: function (type, { packet, networkInfo }) {
+  _onNetworkEventUpdate: function (type, response) {
+    let { packet, networkInfo } = response;
     if (this.webConsoleFrame) {
+      if (this.webConsoleFrame.NEW_CONSOLE_OUTPUT_ENABLED) {
+        this.dispatchMessageUpdate(networkInfo, response);
+      }
       this.webConsoleFrame.handleNetworkEventUpdate(networkInfo, packet);
     }
   },
 
   /**
    * The "fileActivity" message type handler. We redirect any message to
    * the UI for displaying.
    *
@@ -492,9 +501,9 @@ WebConsoleConnectionProxy.prototype = {
     this.connected = false;
     this.webConsoleFrame = null;
     this._disconnecter.resolve(null);
 
     return this._disconnecter.promise;
   },
 };
 
-exports.WebConsoleConnectionProxy = WebConsoleConnectionProxy;
\ No newline at end of file
+exports.WebConsoleConnectionProxy = WebConsoleConnectionProxy;