Bug 1371721 - Create a MESSAGES_ADD action to add batches of messages at once. r=nchevobbe
authorbgrins <bgrinstead@mozilla.com>
Fri, 15 Sep 2017 11:10:11 +0200
changeset 435478 ac6de5490c428d903a9eea197ef3251c29822391
parent 435477 97a7cd935f22129be5d75fdc68ff9239ef229373
child 435479 707d860b4e2f5585806e2fa71d1870af6d05a2dd
push id1618
push userCallek@gmail.com
push dateThu, 11 Jan 2018 17:45:48 +0000
treeherdermozilla-release@882ca853e05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnchevobbe
bugs1371721
milestone58.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 1371721 - Create a MESSAGES_ADD action to add batches of messages at once. r=nchevobbe MozReview-Commit-ID: GUp3lQpNxfn
devtools/client/webconsole/new-console-output/actions/messages.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
--- a/devtools/client/webconsole/new-console-output/actions/messages.js
+++ b/devtools/client/webconsole/new-console-output/actions/messages.js
@@ -9,27 +9,53 @@
 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/shared/redux/middleware/debounce");
 
 const {
   MESSAGE_ADD,
+  MESSAGES_ADD,
   NETWORK_MESSAGE_UPDATE,
   NETWORK_UPDATE_REQUEST,
   MESSAGES_CLEAR,
   MESSAGE_OPEN,
   MESSAGE_CLOSE,
   MESSAGE_TYPE,
   MESSAGE_TABLE_RECEIVE,
 } = require("../constants");
 
 const defaultIdGenerator = new IdGenerator();
 
+function messagesAdd(packets, idGenerator = null) {
+  if (idGenerator == null) {
+    idGenerator = defaultIdGenerator;
+  }
+  let messages = packets.map(packet => prepareMessage(packet, idGenerator));
+  for (let i = messages.length - 1; i >= 0; i--) {
+    if (messages[i].type === MESSAGE_TYPE.CLEAR) {
+      return batchActions([
+        messagesClear(),
+        {
+          type: MESSAGES_ADD,
+          messages: messages.slice(i),
+        }
+      ]);
+    }
+  }
+
+  // When this is used for non-cached messages then handle clear message and
+  // split up into batches
+  return {
+    type: MESSAGES_ADD,
+    messages
+  };
+}
+
 function messageAdd(packet, idGenerator = null) {
   if (idGenerator == null) {
     idGenerator = defaultIdGenerator;
   }
   let message = prepareMessage(packet, idGenerator);
   const addMessageAction = {
     type: MESSAGE_ADD,
     message
@@ -112,16 +138,17 @@ function networkUpdateRequest(id, data) 
     type: NETWORK_UPDATE_REQUEST,
     id,
     data,
   };
 }
 
 module.exports = {
   messageAdd,
+  messagesAdd,
   messagesClear,
   messageOpen,
   messageClose,
   messageTableDataGet,
   networkMessageUpdate,
   networkUpdateRequest,
   // for test purpose only.
   messageTableDataReceive,
--- a/devtools/client/webconsole/new-console-output/constants.js
+++ b/devtools/client/webconsole/new-console-output/constants.js
@@ -3,16 +3,17 @@
 /* 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/. */
 "use strict";
 
 const actionTypes = {
   BATCH_ACTIONS: "BATCH_ACTIONS",
   MESSAGE_ADD: "MESSAGE_ADD",
+  MESSAGES_ADD: "MESSAGES_ADD",
   MESSAGES_CLEAR: "MESSAGES_CLEAR",
   MESSAGE_OPEN: "MESSAGE_OPEN",
   MESSAGE_CLOSE: "MESSAGE_CLOSE",
   NETWORK_MESSAGE_UPDATE: "NETWORK_MESSAGE_UPDATE",
   NETWORK_UPDATE_REQUEST: "NETWORK_UPDATE_REQUEST",
   MESSAGE_TABLE_RECEIVE: "MESSAGE_TABLE_RECEIVE",
   REMOVED_ACTORS_CLEAR: "REMOVED_ACTORS_CLEAR",
   TIMESTAMPS_TOGGLE: "TIMESTAMPS_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
@@ -4,26 +4,27 @@
 "use strict";
 
 // React & Redux
 const React = require("devtools/client/shared/vendor/react");
 const ReactDOM = require("devtools/client/shared/vendor/react-dom");
 const { Provider } = require("devtools/client/shared/vendor/react-redux");
 
 const actions = require("devtools/client/webconsole/new-console-output/actions/index");
-const { batchActions } = require("devtools/client/shared/redux/middleware/debounce");
 const { createContextMenu } = require("devtools/client/webconsole/new-console-output/utils/context-menu");
 const { configureStore } = require("devtools/client/webconsole/new-console-output/store");
 
 const EventEmitter = require("devtools/shared/old-event-emitter");
 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"));
 
 let store = null;
-let queuedActions = [];
+let queuedMessageAdds = [];
+let queuedMessageUpdates = [];
+let queuedRequestUpdates = [];
 let throttledDispatchTimeout = false;
 
 function NewConsoleOutputWrapper(parentNode, jsterm, toolbox, owner, document) {
   EventEmitter.decorate(this);
 
   this.parentNode = parentNode;
   this.jsterm = jsterm;
   this.toolbox = toolbox;
@@ -176,67 +177,65 @@ NewConsoleOutputWrapper.prototype = {
         filterBar,
         childComponent
     ));
     this.body = ReactDOM.render(provider, this.parentNode);
 
     this.jsterm.focus();
   },
   dispatchMessageAdd: function (message, waitForResponse) {
-    let action = actions.messageAdd(message);
-    batchedMessageAdd(action);
+    batchedMessagesAdd(message);
     // 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;
+    if (waitForResponse) {
+      let {timeStamp} = message;
       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.timeStamp === timeStamp) {
               resolve(m.node);
               jsterm.hud.off("new-messages", onThisMessage);
               return;
             }
           }
         });
       });
     }
 
     return Promise.resolve();
   },
 
   dispatchMessagesAdd: function (messages) {
-    const batchedActions = messages.map(message => actions.messageAdd(message));
-    store.dispatch(batchActions(batchedActions));
+    store.dispatch(actions.messagesAdd(messages));
   },
 
   dispatchMessagesClear: function () {
     store.dispatch(actions.messagesClear());
   },
 
   dispatchTimestampsToggle: function (enabled) {
     store.dispatch(actions.timestampsToggle(enabled));
   },
 
   dispatchMessageUpdate: function (message, res) {
     // network-message-updated will emit when all the update message arrives.
     // Since we can't ensure the order of the network update, we check
     // that networkInfo.updates has all we need.
     const NUMBER_OF_NETWORK_UPDATE = 8;
     if (res.networkInfo.updates.length === NUMBER_OF_NETWORK_UPDATE) {
-      batchedMessageAdd(actions.networkMessageUpdate(message));
+      batchedMessageUpdates(message);
       this.jsterm.hud.emit("network-message-updated", res);
     }
   },
 
   dispatchRequestUpdate: function (id, data) {
-    batchedMessageAdd(actions.networkUpdateRequest(id, data));
+    batchedRequestUpdates({ id, data });
 
     // Fire an event indicating that all data fetched from
     // the backend has been received. This is based on
     // 'FirefoxDataProvider.isQueuePayloadReady', see more
     // comments in that method.
     // (netmonitor/src/connector/firefox-data-provider).
     // This event might be utilized in tests to find the right
     // time when to finish.
@@ -244,21 +243,43 @@ NewConsoleOutputWrapper.prototype = {
   },
 
   // Should be used for test purpose only.
   getStore: function () {
     return store;
   }
 };
 
-function batchedMessageAdd(action) {
-  queuedActions.push(action);
+function setTimeoutIfNeeded() {
   if (!throttledDispatchTimeout) {
     throttledDispatchTimeout = setTimeout(() => {
-      store.dispatch(batchActions(queuedActions));
-      queuedActions = [];
+      store.dispatch(actions.messagesAdd(queuedMessageAdds));
+      queuedMessageUpdates.forEach(message => {
+        actions.networkMessageUpdate(message);
+      });
+      queuedRequestUpdates.forEach(({ id, data}) => {
+        actions.networkUpdateRequest(id, data);
+      });
+      queuedMessageAdds = [];
+      queuedMessageUpdates = [];
+      queuedRequestUpdates = [];
       throttledDispatchTimeout = null;
     }, 50);
   }
 }
 
+function batchedMessageUpdates(message) {
+  queuedMessageUpdates.push(message);
+  setTimeoutIfNeeded();
+}
+
+function batchedRequestUpdates(message) {
+  queuedRequestUpdates.push(message);
+  setTimeoutIfNeeded();
+}
+
+function batchedMessagesAdd(message) {
+  queuedMessageAdds.push(message);
+  setTimeoutIfNeeded();
+}
+
 // Exports from this module
 module.exports = NewConsoleOutputWrapper;
--- a/devtools/client/webconsole/new-console-output/reducers/messages.js
+++ b/devtools/client/webconsole/new-console-output/reducers/messages.js
@@ -49,105 +49,136 @@ const MessageState = Immutable.Record({
   removedActors: [],
   // Map of the form {messageId : numberOfRepeat}
   repeatById: {},
   // Map of the form {messageId : networkInformation}
   // `networkInformation` holds request, response, totalTime, ...
   networkMessagesUpdateById: {},
 });
 
-function messages(state = new MessageState(), action, filtersState, prefsState) {
+function addMessage(state, filtersState, prefsState, newMessage) {
   const {
     messagesById,
     messagesUiById,
-    messagesTableDataById,
-    networkMessagesUpdateById,
     groupsById,
     currentGroup,
     repeatById,
     visibleMessages,
     filteredMessagesCount,
   } = state;
 
+  if (newMessage.type === constants.MESSAGE_TYPE.NULL_MESSAGE) {
+    // When the message has a NULL type, we don't add it.
+    return state;
+  }
+
+  if (newMessage.type === constants.MESSAGE_TYPE.END_GROUP) {
+    // Compute the new current group.
+    return state.set("currentGroup", getNewCurrentGroup(currentGroup, groupsById));
+  }
+
+  if (newMessage.allowRepeating && messagesById.size > 0) {
+    let lastMessage = messagesById.last();
+    if (
+      lastMessage.repeatId === newMessage.repeatId
+      && lastMessage.groupId === currentGroup
+    ) {
+      return state.set(
+        "repeatById",
+        Object.assign({}, repeatById, {
+          [lastMessage.id]: (repeatById[lastMessage.id] || 1) + 1
+        })
+      );
+    }
+  }
+
+  return state.withMutations(function (record) {
+    // Add the new message with a reference to the parent group.
+    let parentGroups = getParentGroups(currentGroup, groupsById);
+    newMessage.groupId = currentGroup;
+    newMessage.indent = parentGroups.length;
+
+    const addedMessage = Object.freeze(newMessage);
+    record.set(
+      "messagesById",
+      messagesById.set(newMessage.id, addedMessage)
+    );
+
+    if (newMessage.type === "trace") {
+      // We want the stacktrace to be open by default.
+      record.set("messagesUiById", messagesUiById.push(newMessage.id));
+    } else if (isGroupType(newMessage.type)) {
+      record.set("currentGroup", newMessage.id);
+      record.set("groupsById", groupsById.set(newMessage.id, parentGroups));
+
+      if (newMessage.type === constants.MESSAGE_TYPE.START_GROUP) {
+        // We want the group to be open by default.
+        record.set("messagesUiById", messagesUiById.push(newMessage.id));
+      }
+    }
+
+    const {
+      visible,
+      cause
+    } = getMessageVisibility(addedMessage, record, filtersState);
+
+    if (visible) {
+      record.set("visibleMessages", [...visibleMessages, newMessage.id]);
+    } else if (DEFAULT_FILTERS.includes(cause)) {
+      record.set("filteredMessagesCount", Object.assign({}, filteredMessagesCount, {
+        global: filteredMessagesCount.global + 1,
+        [cause]: filteredMessagesCount[cause] + 1
+      }));
+    }
+  });
+}
+
+function messages(state = new MessageState(), action, filtersState, prefsState) {
+  const {
+    messagesById,
+    messagesUiById,
+    messagesTableDataById,
+    networkMessagesUpdateById,
+    groupsById,
+    visibleMessages,
+  } = state;
+
   const {logLimit} = prefsState;
 
+  let newState;
   switch (action.type) {
-    case constants.MESSAGE_ADD:
-      let newMessage = action.message;
-
-      if (newMessage.type === constants.MESSAGE_TYPE.NULL_MESSAGE) {
-        // When the message has a NULL type, we don't add it.
-        return state;
-      }
+    case constants.MESSAGES_ADD:
+      newState = state;
 
-      if (newMessage.type === constants.MESSAGE_TYPE.END_GROUP) {
-        // Compute the new current group.
-        return state.set("currentGroup", getNewCurrentGroup(currentGroup, groupsById));
-      }
-
-      if (newMessage.allowRepeating && messagesById.size > 0) {
-        let lastMessage = messagesById.last();
-        if (
-          lastMessage.repeatId === newMessage.repeatId
-          && lastMessage.groupId === currentGroup
-        ) {
-          return state.set(
-            "repeatById",
-            Object.assign({}, repeatById, {
-              [lastMessage.id]: (repeatById[lastMessage.id] || 1) + 1
-            })
-          );
+      // Preemptively remove messages that will never be rendered
+      let list = [];
+      let prunableCount = 0;
+      for (let i = action.messages.length - 1; i >= 0; i--) {
+        if (!action.messages[i].groupId && !isGroupType(action.messages[i].type) &&
+            action.messages[i].type !== MESSAGE_TYPE.END_GROUP) {
+          prunableCount++;
+          // Once we've added the max number of messages that can be added stop.
+          // TODO: what about repeats?
+          if (prunableCount <= logLimit) {
+            list.unshift(action.messages[i]);
+          }
+        } else {
+          list.unshift(action.messages[i]);
         }
       }
 
-      return state.withMutations(function (record) {
-        // Add the new message with a reference to the parent group.
-        let parentGroups = getParentGroups(currentGroup, groupsById);
-        newMessage.groupId = currentGroup;
-        newMessage.indent = parentGroups.length;
-
-        const addedMessage = Object.freeze(newMessage);
-        record.set(
-          "messagesById",
-          messagesById.set(newMessage.id, addedMessage)
-        );
-
-        if (newMessage.type === "trace") {
-          // We want the stacktrace to be open by default.
-          record.set("messagesUiById", messagesUiById.push(newMessage.id));
-        } else if (isGroupType(newMessage.type)) {
-          record.set("currentGroup", newMessage.id);
-          record.set("groupsById", groupsById.set(newMessage.id, parentGroups));
+      list.forEach(message => {
+        newState = addMessage(newState, filtersState, prefsState, message);
+      });
 
-          if (newMessage.type === constants.MESSAGE_TYPE.START_GROUP) {
-            // We want the group to be open by default.
-            record.set("messagesUiById", messagesUiById.push(newMessage.id));
-          }
-        }
-
-        const {
-          visible,
-          cause
-        } = getMessageVisibility(addedMessage, record, filtersState);
+      return limitTopLevelMessageCount(newState, logLimit);
 
-        if (visible) {
-          record.set("visibleMessages", [...visibleMessages, newMessage.id]);
-        } else if (DEFAULT_FILTERS.includes(cause)) {
-          record.set("filteredMessagesCount", Object.assign({}, filteredMessagesCount, {
-            global: filteredMessagesCount.global + 1,
-            [cause]: filteredMessagesCount[cause] + 1
-          }));
-        }
-
-        // Remove top level message if the total count of top level messages
-        // exceeds the current limit.
-        if (record.messagesById.size > logLimit) {
-          limitTopLevelMessageCount(state, record, logLimit);
-        }
-      });
+    case constants.MESSAGE_ADD:
+      newState = addMessage(state, filtersState, prefsState, action.message);
+      return limitTopLevelMessageCount(newState, logLimit);
 
     case constants.MESSAGES_CLEAR:
       return new MessageState({
         // Store all actors from removed messages. This array is used by
         // `releaseActorsEnhancer` to release all of those backend actors.
         "removedActors": [...state.messagesById].reduce((res, [id, msg]) => {
           res.push(...getAllActorsInMessage(msg, state));
           return res;
@@ -261,17 +292,17 @@ function messages(state = new MessageSta
                 headers: [],
                 headersSize: 0,
               };
               break;
           }
         }
       }
 
-      let newState = state.set(
+      newState = state.set(
         "networkMessagesUpdateById",
         Object.assign({}, networkMessagesUpdateById, {
           [action.id]: Object.assign({}, request, values)
         })
       );
 
       return newState;
     }
@@ -336,109 +367,109 @@ function getParentGroups(currentGroup, g
   return groups;
 }
 
 /**
  * Remove all top level messages that exceeds message limit.
  * Also populate an array of all backend actors associated with these
  * messages so they can be released.
  */
-function limitTopLevelMessageCount(state, record, logLimit) {
-  let topLevelCount = record.groupsById.size === 0
-    ? record.messagesById.size
-    : getToplevelMessageCount(record);
-
-  if (topLevelCount <= logLimit) {
-    return record;
-  }
-
-  const removedMessagesId = [];
-  const removedActors = [];
-  let visibleMessages = [...record.visibleMessages];
-
-  let cleaningGroup = false;
-  record.messagesById.forEach((message, id) => {
-    // If we were cleaning a group and the current message does not have
-    // a groupId, we're done cleaning.
-    if (cleaningGroup === true && !message.groupId) {
-      cleaningGroup = false;
-    }
+function limitTopLevelMessageCount(state, logLimit) {
+  return state.withMutations(function (record) {
+    let topLevelCount = record.groupsById.size === 0
+      ? record.messagesById.size
+      : getToplevelMessageCount(record);
 
-    // If we're not cleaning a group and the message count is below the logLimit,
-    // we exit the forEach iteration.
-    if (cleaningGroup === false && topLevelCount <= logLimit) {
-      return false;
-    }
-
-    // If we're not currently cleaning a group, and the current message is identified
-    // as a group, set the cleaning flag to true.
-    if (cleaningGroup === false && record.groupsById.has(id)) {
-      cleaningGroup = true;
-    }
-
-    if (!message.groupId) {
-      topLevelCount--;
-    }
-
-    removedMessagesId.push(id);
-    removedActors.push(...getAllActorsInMessage(message, record));
-
-    const index = visibleMessages.indexOf(id);
-    if (index > -1) {
-      visibleMessages.splice(index, 1);
+    if (topLevelCount <= logLimit) {
+      return;
     }
 
-    return true;
-  });
+    const removedMessagesId = [];
+    const removedActors = [];
+    let visibleMessages = [...record.visibleMessages];
 
-  if (removedActors.length > 0) {
-    record.set("removedActors", record.removedActors.concat(removedActors));
-  }
-
-  if (record.visibleMessages.length > visibleMessages.length) {
-    record.set("visibleMessages", visibleMessages);
-  }
+    let cleaningGroup = false;
+    record.messagesById.forEach((message, id) => {
+      // If we were cleaning a group and the current message does not have
+      // a groupId, we're done cleaning.
+      if (cleaningGroup === true && !message.groupId) {
+        cleaningGroup = false;
+      }
 
-  const isInRemovedId = id => removedMessagesId.includes(id);
-  const mapHasRemovedIdKey = map => map.findKey((value, id) => isInRemovedId(id));
-  const objectHasRemovedIdKey = obj => Object.keys(obj).findIndex(isInRemovedId) !== -1;
-  const cleanUpCollection = map => removedMessagesId.forEach(id => map.remove(id));
-  const cleanUpList = list => list.filter(id => {
-    return isInRemovedId(id) === false;
-  });
-  const cleanUpObject = object => [...Object.entries(object)]
-    .reduce((res, [id, value]) => {
-      if (!isInRemovedId(id)) {
-        res[id] = value;
+      // If we're not cleaning a group and the message count is below the logLimit,
+      // we exit the forEach iteration.
+      if (cleaningGroup === false && topLevelCount <= logLimit) {
+        return false;
+      }
+
+      // If we're not currently cleaning a group, and the current message is identified
+      // as a group, set the cleaning flag to true.
+      if (cleaningGroup === false && record.groupsById.has(id)) {
+        cleaningGroup = true;
+      }
+
+      if (!message.groupId) {
+        topLevelCount--;
+      }
+
+      removedMessagesId.push(id);
+      removedActors.push(...getAllActorsInMessage(message, record));
+
+      const index = visibleMessages.indexOf(id);
+      if (index > -1) {
+        visibleMessages.splice(index, 1);
       }
-      return res;
-    }, {});
+
+      return true;
+    });
 
-  record.set("messagesById", record.messagesById.withMutations(cleanUpCollection));
+    if (removedActors.length > 0) {
+      record.set("removedActors", record.removedActors.concat(removedActors));
+    }
+
+    if (record.visibleMessages.length > visibleMessages.length) {
+      record.set("visibleMessages", visibleMessages);
+    }
 
-  if (record.messagesUiById.find(isInRemovedId)) {
-    record.set("messagesUiById", cleanUpList(record.messagesUiById));
-  }
-  if (mapHasRemovedIdKey(record.messagesTableDataById)) {
-    record.set("messagesTableDataById",
-      record.messagesTableDataById.withMutations(cleanUpCollection));
-  }
-  if (mapHasRemovedIdKey(record.groupsById)) {
-    record.set("groupsById", record.groupsById.withMutations(cleanUpCollection));
-  }
-  if (objectHasRemovedIdKey(record.repeatById)) {
-    record.set("repeatById", cleanUpObject(record.repeatById));
-  }
+    const isInRemovedId = id => removedMessagesId.includes(id);
+    const mapHasRemovedIdKey = map => map.findKey((value, id) => isInRemovedId(id));
+    const objectHasRemovedIdKey = obj => Object.keys(obj).findIndex(isInRemovedId) !== -1;
+    const cleanUpCollection = map => removedMessagesId.forEach(id => map.remove(id));
+    const cleanUpList = list => list.filter(id => {
+      return isInRemovedId(id) === false;
+    });
+    const cleanUpObject = object => [...Object.entries(object)]
+      .reduce((res, [id, value]) => {
+        if (!isInRemovedId(id)) {
+          res[id] = value;
+        }
+        return res;
+      }, {});
+
+    record.set("messagesById", record.messagesById.withMutations(cleanUpCollection));
 
-  if (objectHasRemovedIdKey(record.networkMessagesUpdateById)) {
-    record.set("networkMessagesUpdateById",
-      cleanUpObject(record.networkMessagesUpdateById));
-  }
+    if (record.messagesUiById.find(isInRemovedId)) {
+      record.set("messagesUiById", cleanUpList(record.messagesUiById));
+    }
+    if (mapHasRemovedIdKey(record.messagesTableDataById)) {
+      record.set("messagesTableDataById",
+        record.messagesTableDataById.withMutations(cleanUpCollection));
+    }
+    if (mapHasRemovedIdKey(record.groupsById)) {
+      record.set("groupsById", record.groupsById.withMutations(cleanUpCollection));
+    }
+    if (objectHasRemovedIdKey(record.repeatById)) {
+      record.set("repeatById", cleanUpObject(record.repeatById));
+    }
 
-  return record;
+    if (objectHasRemovedIdKey(record.networkMessagesUpdateById)) {
+      record.set("networkMessagesUpdateById",
+        cleanUpObject(record.networkMessagesUpdateById));
+    }
+  });
 }
 
 /**
  * Get an array of all the actors logged in a specific message.
  *
  * @param {Message} message: The message to get actors from.
  * @param {Record} state: The redux state.
  * @return {Array} An array containing all the actors logged in a message.