Bug 1526566 - Show console messages in the order in which they executed, r=nchevobbe.
authorBrian Hackett <bhackett1024@gmail.com>
Fri, 08 Feb 2019 14:56:53 -1000
changeset 459668 333a0c5f1b61d8e04ed8681cd7b92780a0642fc0
parent 459667 a4a2586e511abbfd4e4947fb4154296e75863ba4
child 459669 22ed69977c45690df9c2cd6c8123eb98cd658829
push id35564
push userccoroiu@mozilla.com
push dateSat, 16 Feb 2019 09:37:16 +0000
treeherdermozilla-central@7ab4a0c9980f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnchevobbe
bugs1526566
milestone67.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 1526566 - Show console messages in the order in which they executed, r=nchevobbe.
devtools/client/webconsole/reducers/messages.js
--- a/devtools/client/webconsole/reducers/messages.js
+++ b/devtools/client/webconsole/reducers/messages.js
@@ -120,16 +120,18 @@ function addMessage(state, filtersState,
     }
   }
 
   // Add the new message with a reference to the parent group.
   const parentGroups = getParentGroups(currentGroup, groupsById);
   newMessage.groupId = currentGroup;
   newMessage.indent = parentGroups.length;
 
+  ensureExecutionPoint(state, newMessage);
+
   const addedMessage = Object.freeze(newMessage);
   state.messagesById.set(newMessage.id, addedMessage);
 
   if (newMessage.type === "trace") {
     // We want the stacktrace to be open by default.
     state.messagesUiById.push(newMessage.id);
   } else if (isGroupType(newMessage.type)) {
     state.currentGroup = newMessage.id;
@@ -143,16 +145,17 @@ function addMessage(state, filtersState,
 
   const {
     visible,
     cause,
   } = getMessageVisibility(addedMessage, state, filtersState);
 
   if (visible) {
     state.visibleMessages.push(newMessage.id);
+    maybeSortVisibleMessages(state);
   } else if (DEFAULT_FILTERS.includes(cause)) {
     state.filteredMessagesCount.global++;
     state.filteredMessagesCount[cause]++;
   }
 
   // Append received network-data also into networkMessagesUpdateById
   // that is responsible for collecting (lazy loaded) HTTP payload data.
   if (newMessage.source == "network") {
@@ -360,21 +363,24 @@ function messages(state = MessageState()
         if (visible) {
           messagesToShow.push(msgId);
         } else if (DEFAULT_FILTERS.includes(cause)) {
           filtered.global = filtered.global + 1;
           filtered[cause] = filtered[cause] + 1;
         }
       });
 
-      return {
+      const filteredState = {
         ...state,
         visibleMessages: messagesToShow,
         filteredMessagesCount: filtered,
       };
+      maybeSortVisibleMessages(filteredState);
+
+      return filteredState;
   }
 
   return state;
 }
 
 /**
  * Returns the new current group id given the previous current group and the groupsById
  * state property.
@@ -934,9 +940,82 @@ function getDefaultFiltersCounter() {
   const count = DEFAULT_FILTERS.reduce((res, filter) => {
     res[filter] = 0;
     return res;
   }, {});
   count.global = 0;
   return count;
 }
 
+// Make sure that message has an execution point which can be used for sorting
+// if other messages with real execution points appear later.
+function ensureExecutionPoint(state, newMessage) {
+  if (newMessage.executionPoint) {
+    return;
+  }
+
+  // Add a lastExecutionPoint property which will place this message immediately
+  // after the last visible one when sorting.
+  let point = { progress: 0 }, messageCount = 1;
+  if (state.visibleMessages.length) {
+    const lastId = state.visibleMessages[state.visibleMessages.length - 1];
+    const lastMessage = state.messagesById.get(lastId);
+    if (lastMessage.executionPoint) {
+      point = lastMessage.executionPoint;
+    } else {
+      point = lastMessage.lastExecutionPoint.point;
+      messageCount = lastMessage.lastExecutionPoint.messageCount + 1;
+    }
+  }
+  newMessage.lastExecutionPoint = { point, messageCount };
+}
+
+function messageExecutionPoint(state, id) {
+  const message = state.messagesById.get(id);
+  return message.executionPoint || message.lastExecutionPoint.point;
+}
+
+function messageCountSinceLastExecutionPoint(state, id) {
+  const message = state.messagesById.get(id);
+  return message.lastExecutionPoint ? message.lastExecutionPoint.messageCount : 0;
+}
+
+function maybeSortVisibleMessages(state) {
+  // When using log points while replaying, messages can be added out of order
+  // with respect to how they originally executed. Use the execution point
+  // information in the messages to sort visible messages according to how
+  // they originally executed. This isn't necessary if we haven't seen any
+  // messages with progress counters, as either we aren't replaying or haven't
+  // seen any messages yet.
+  if (state.replayProgressMessages.size) {
+    state.visibleMessages.sort((a, b) => {
+      const pointA = messageExecutionPoint(state, a);
+      const pointB = messageExecutionPoint(state, b);
+      if (pointA.progress != pointB.progress) {
+        return pointA.progress > pointB.progress;
+      }
+      // Execution points without a progress counter predate execution points
+      // with one, i.e. a console.log() call (which bumps the progress value)
+      // predates the code that runs afterward.
+      if ("frameIndex" in pointA != "frameIndex" in pointB) {
+        return "frameIndex" in pointA;
+      }
+      // Deeper frames predate shallower frames, if the progress counter is the
+      // same. We bump the progress counter when pushing frames, but not when
+      // popping them.
+      if (pointA.frameIndex != pointB.frameIndex) {
+        return pointA.frameIndex < pointB.frameIndex;
+      }
+      // Earlier script locations predate later script locations.
+      if (pointA.offset != pointB.offset) {
+        return pointA.offset > pointB.offset;
+      }
+      // When messages don't have their own execution point, they can still be
+      // distinguished by the number of messages since the last one which did
+      // have an execution point.
+      const countA = messageCountSinceLastExecutionPoint(state, a);
+      const countB = messageCountSinceLastExecutionPoint(state, b);
+      return countA > countB;
+    });
+  }
+}
+
 exports.messages = messages;