Bug 1534927 - Create warningGroup outside of console.group. r=Honza.
authorNicolas Chevobbe <nchevobbe@mozilla.com>
Tue, 09 Apr 2019 14:05:04 +0000
changeset 468585 354e9abf1a431424b554e2ce00e232dcc6955ff8
parent 468584 f09e9d011ba193397c2f0035ab74b3120355b99d
child 468586 829c937f690939edeb83cc5143214e8445fbbf36
push id112738
push usernbeleuzu@mozilla.com
push dateTue, 09 Apr 2019 22:28:41 +0000
treeherdermozilla-inbound@d6bbb316b768 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersHonza
bugs1534927
milestone68.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 1534927 - Create warningGroup outside of console.group. r=Honza. With this patch, we always create warningGroups outside of the outermost console.group the first warning message could be in. This is done because we don't want a warningGroup to be burried in a console.group, or worse, in a console.groupCollapsed, where it wouldn't be visible at all. The messages reducer is modified to do that, and a test is added to ensure all the group interactions work as expected. Differential Revision: https://phabricator.services.mozilla.com/D25910
devtools/client/webconsole/reducers/messages.js
devtools/client/webconsole/test/mochitest/browser.ini
devtools/client/webconsole/test/mochitest/browser_webconsole_warning_group_content_blocking.js
devtools/client/webconsole/test/mochitest/browser_webconsole_warning_groups.js
devtools/client/webconsole/test/mochitest/browser_webconsole_warning_groups_outside_console_group.js
devtools/client/webconsole/test/mochitest/head.js
--- a/devtools/client/webconsole/reducers/messages.js
+++ b/devtools/client/webconsole/reducers/messages.js
@@ -146,18 +146,20 @@ function addMessage(newMessage, state, f
     ) {
       state.repeatById[lastMessage.id] = (repeatById[lastMessage.id] || 1) + 1;
       return state;
     }
   }
 
   // Add the new message with a reference to the parent group.
   const parentGroups = getParentGroups(currentGroup, groupsById);
-  newMessage.groupId = currentGroup;
-  newMessage.indent = parentGroups.length;
+  if (!isWarningGroup(newMessage)) {
+    newMessage.groupId = currentGroup;
+    newMessage.indent = parentGroups.length;
+  }
 
   ensureExecutionPoint(state, newMessage);
 
   // Check if the current message could be placed in a Warning Group.
   // This needs to be done before setting the new message in messagesById so we have a
   // proper message.
   const warningGroupType = getWarningGroupType(newMessage);
 
@@ -183,22 +185,37 @@ function addMessage(newMessage, state, f
       && getMessageVisibility(state.messagesById.get(warningGroupMessageId), {
         messagesState: state,
         filtersState,
         prefsState,
       }).visible
     ) {
       // Then we put it in the visibleMessages properties, at the position of the first
       // warning message inside the warningGroup.
-      // TODO [Bug 1534927]: It should be added before the outermost console.group message
-      // a warning message could be in.
-      const index = state
-        .visibleMessages
-        .indexOf(state.warningGroupsById.get(warningGroupMessageId)[0]);
-      state.visibleMessages.splice(index, 1, warningGroupMessageId);
+      // If that first warning message is in a console.group, we place it before the
+      // outermost console.group message.
+      const firstWarningMessageId = state.warningGroupsById.get(warningGroupMessageId)[0];
+      const firstWarningMessage = state.messagesById.get(firstWarningMessageId);
+      const outermostGroupId = getOutermostGroup(firstWarningMessage, groupsById);
+      const groupIndex = state.visibleMessages.indexOf(outermostGroupId);
+      const warningMessageIndex = state.visibleMessages.indexOf(firstWarningMessageId);
+
+      if (groupIndex > -1) {
+        // We remove the warning message
+        if (warningMessageIndex > -1) {
+          state.visibleMessages.splice(warningMessageIndex, 1);
+        }
+
+        // And we put the warning group before the console.group
+        state.visibleMessages.splice(groupIndex, 0, warningGroupMessageId);
+      } else {
+        // If the warning message is not in a console.group, we replace it by the
+        // warning group message.
+        state.visibleMessages.splice(warningMessageIndex, 1, warningGroupMessageId);
+      }
     }
   }
 
   const addedMessage = Object.freeze(newMessage);
   state.messagesById.set(newMessage.id, addedMessage);
 
   if (newMessage.type === "trace") {
     // We want the stacktrace to be open by default.
@@ -410,21 +427,30 @@ function messages(state = MessageState()
       const closeState = {...state};
       const messageId = action.id;
       const index = closeState.messagesUiById.indexOf(messageId);
       closeState.messagesUiById.splice(index, 1);
       closeState.messagesUiById = [...closeState.messagesUiById];
 
       // If the message is a group
       if (isGroupType(messagesById.get(messageId).type)) {
-        // Hide all its children
-        closeState.visibleMessages = visibleMessages.filter(id =>
-          getParentGroups(messagesById.get(id).groupId, groupsById)
-            .includes(messageId) === false
-        );
+        // Hide all its children, unless they're in a warningGroup.
+        closeState.visibleMessages = visibleMessages.filter((id, i, arr) => {
+          const message = messagesById.get(id);
+          const warningGroupMessage =
+            messagesById.get(getParentWarningGroupMessageId(message));
+
+          // If the message is in a warning group, then we return its current visibility.
+          if (shouldGroupWarningMessages(warningGroupMessage, closeState, prefsState)) {
+            return arr.includes(id);
+          }
+
+          const parentGroups = getParentGroups(message.groupId, groupsById);
+          return parentGroups.includes(messageId) === false;
+        });
       } else if (isWarningGroup(messagesById.get(messageId))) {
         // If the message was a warningGroup, we hide all the messages in the group.
         const groupMessages = closeState.warningGroupsById.get(messageId);
         closeState.visibleMessages =
           visibleMessages.filter(id => !groupMessages.includes(id));
       }
       return closeState;
 
@@ -546,16 +572,24 @@ function getParentGroups(currentGroup, g
     if (Array.isArray(parentGroups) && parentGroups.length > 0) {
       groups = groups.concat(parentGroups);
     }
   }
 
   return groups;
 }
 
+function getOutermostGroup(message, groupsById) {
+  const groups = getParentGroups(message.groupId, groupsById);
+  if (groups.length === 0) {
+    return null;
+  }
+  return groups[groups.length - 1];
+}
+
 /**
  * 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(newState, logLimit) {
   let topLevelCount = newState.groupsById.size === 0
     ? newState.messagesById.size
@@ -729,20 +763,24 @@ function getToplevelMessageCount(state) 
  *         - cause {String}: if visible is false, what causes the message to be hidden.
  */
 function getMessageVisibility(message, {
     messagesState,
     filtersState,
     prefsState,
     checkGroup = true,
 }) {
-  // Do not display the message if it's in closed group.
+  const warningGroupMessage =
+    messagesState.messagesById.get(getParentWarningGroupMessageId(message));
+
+  // Do not display the message if it's in closed group and not in a warning group.
   if (
     checkGroup
     && !isInOpenedGroup(message, messagesState.groupsById, messagesState.messagesUiById)
+    && !shouldGroupWarningMessages(warningGroupMessage, messagesState, prefsState)
   ) {
     return {
       visible: false,
       cause: "closedGroup",
     };
   }
 
   // If the message is a warningGroup, check if it should be displayed.
@@ -1178,22 +1216,30 @@ function getLastMessageId(state) {
 /**
  * Returns if a given type of warning message should be grouped.
  *
  * @param {ConsoleMessage} warningGroupMessage
  * @param {MessageState} messagesState
  * @param {PrefsState} prefsState
  */
 function shouldGroupWarningMessages(warningGroupMessage, messagesState, prefsState) {
+  if (!warningGroupMessage) {
+    return false;
+  }
+
   // Only group if the preference is ON.
   if (!prefsState.groupWarnings) {
     return false;
   }
 
   // We group warning messages if there are at least 2 messages that could go in it.
   const warningGroup = messagesState.warningGroupsById.get(warningGroupMessage.id);
-  return warningGroup && warningGroup.length > 1;
+  if (!warningGroup || !Array.isArray(warningGroup)) {
+    return false;
+  }
+
+  return warningGroup.length > 1;
 }
 
 exports.messages = messages;
 
 // Export for testing purpose.
 exports.ensureExecutionPoint = ensureExecutionPoint;
--- a/devtools/client/webconsole/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/test/mochitest/browser.ini
@@ -394,10 +394,11 @@ skip-if = verify
 [browser_webconsole_time_methods.js]
 [browser_webconsole_timestamps.js]
 [browser_webconsole_trackingprotection_errors.js]
 tags = trackingprotection
 [browser_webconsole_view_source.js]
 [browser_webconsole_visibility_messages.js]
 [browser_webconsole_warn_about_replaced_api.js]
 [browser_webconsole_warning_group_content_blocking.js]
+[browser_webconsole_warning_groups_outside_console_group.js]
 [browser_webconsole_warning_groups.js]
 [browser_webconsole_websocket.js]
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_warning_group_content_blocking.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_warning_group_content_blocking.js
@@ -58,25 +58,25 @@ add_task(async function testContentBlock
   const onContentBlockingWarningGroupMessage =
     waitForMessage(hud, CONTENT_BLOCKING_GROUP_LABEL, ".warn");
   emitContentBlockingMessage(tab, `${TRACKER_URL}?2&${now}`);
   const {node} = await onContentBlockingWarningGroupMessage;
   is(node.querySelector(".warning-group-badge").textContent, "2",
     "The badge has the expected text");
 
   checkConsoleOutputForWarningGroup(hud, [
-    `▶︎ ${CONTENT_BLOCKING_GROUP_LABEL} 2`,
+    `▶︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL} 2`,
   ]);
 
   info("Open the group");
   node.querySelector(".arrow").click();
   await waitFor(() => findMessage(hud, "http://tracking.example.com/?1"));
 
   checkConsoleOutputForWarningGroup(hud, [
-    `▼︎ ${CONTENT_BLOCKING_GROUP_LABEL} 2`,
+    `▼︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL} 2`,
     `| The resource at \u201chttp://tracking.example.com/?1&${now}\u201d was blocked`,
     `| The resource at \u201chttp://tracking.example.com/?2&${now}\u201d was blocked`,
   ]);
   await win.close();
 });
 
 add_task(async function testForeignCookieBlockedMessage() {
   info("Test foreign cookie blocked message");
@@ -149,25 +149,25 @@ async function testStorageAccessBlockedG
   const onContentBlockingWarningGroupMessage =
     waitForMessage(hud, CONTENT_BLOCKING_GROUP_LABEL, ".warn");
   emitStorageAccessBlockedMessage(tab, `${TRACKER_IMG}?2&${now}`);
   const {node} = await onContentBlockingWarningGroupMessage;
   is(node.querySelector(".warning-group-badge").textContent, "2",
     "The badge has the expected text");
 
   checkConsoleOutputForWarningGroup(hud, [
-    `▶︎ ${CONTENT_BLOCKING_GROUP_LABEL} 2`,
+    `▶︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL} 2`,
   ]);
 
   info("Open the group");
   node.querySelector(".arrow").click();
   await waitFor(() => findMessage(hud, TRACKER_IMG));
 
   checkConsoleOutputForWarningGroup(hud, [
-    `▼︎ ${CONTENT_BLOCKING_GROUP_LABEL} 2`,
+    `▼︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL} 2`,
     `| ${getWarningMessage(TRACKER_IMG + "?1&" + now)}`,
     `| ${getWarningMessage(TRACKER_IMG + "?2&" + now)}`,
   ]);
 
   hud.ui.clearOutput();
   await win.close();
 }
 
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_warning_groups.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_warning_groups.js
@@ -47,61 +47,61 @@ add_task(async function testContentBlock
   let onContentBlockingWarningGroupMessage =
     waitForMessage(hud, CONTENT_BLOCKING_GROUP_LABEL, ".warn");
   emitStorageAccessBlockedMessage(hud);
   ({node} = await onContentBlockingWarningGroupMessage);
   is(node.querySelector(".warning-group-badge").textContent, "2",
     "The badge has the expected text");
 
   checkConsoleOutputForWarningGroup(hud, [
-    `▶︎ ${CONTENT_BLOCKING_GROUP_LABEL}`,
+    `▶︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`,
     `simple message 1`,
   ]);
 
   info("Log another simple message");
   await logString(hud, "simple message 2");
 
   checkConsoleOutputForWarningGroup(hud, [
-    `▶︎ ${CONTENT_BLOCKING_GROUP_LABEL}`,
+    `▶︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`,
     `simple message 1`,
     `simple message 2`,
   ]);
 
   info("Log a third tracking protection message to check that the badge updates");
   emitStorageAccessBlockedMessage(hud);
   await waitFor(() => node.querySelector(".warning-group-badge").textContent == "3");
 
   checkConsoleOutputForWarningGroup(hud, [
-    `▶︎ ${CONTENT_BLOCKING_GROUP_LABEL}`,
+    `▶︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`,
     `simple message 1`,
     `simple message 2`,
   ]);
 
   info("Open the group");
   node.querySelector(".arrow").click();
   await waitFor(() => findMessage(hud, BLOCKED_URL));
 
   checkConsoleOutputForWarningGroup(hud, [
-    `▼︎ ${CONTENT_BLOCKING_GROUP_LABEL}`,
+    `▼︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`,
     `| ${BLOCKED_URL}?1`,
     `| ${BLOCKED_URL}?2`,
     `| ${BLOCKED_URL}?3`,
     `simple message 1`,
     `simple message 2`,
   ]);
 
   info("Log a new tracking protection message to check it appears inside the group");
   onContentBlockingWarningMessage =
     waitForMessage(hud, BLOCKED_URL, ".warn");
   emitStorageAccessBlockedMessage(hud);
   await onContentBlockingWarningMessage;
   ok(true, "The new tracking protection message is displayed");
 
   checkConsoleOutputForWarningGroup(hud, [
-    `▼︎ ${CONTENT_BLOCKING_GROUP_LABEL}`,
+    `▼︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`,
     `| ${BLOCKED_URL}?1`,
     `| ${BLOCKED_URL}?2`,
     `| ${BLOCKED_URL}?3`,
     `| ${BLOCKED_URL}?4`,
     `simple message 1`,
     `simple message 2`,
   ]);
 
@@ -120,17 +120,17 @@ add_task(async function testContentBlock
   onContentBlockingWarningMessage =
     waitForMessage(hud, BLOCKED_URL, ".warn");
   emitStorageAccessBlockedMessage(hud);
   await onContentBlockingWarningMessage;
 
   await logString(hud, "simple message 3");
 
   checkConsoleOutputForWarningGroup(hud, [
-    `▼︎ ${CONTENT_BLOCKING_GROUP_LABEL}`,
+    `▼︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`,
     `| ${BLOCKED_URL}?1`,
     `| ${BLOCKED_URL}?2`,
     `| ${BLOCKED_URL}?3`,
     `| ${BLOCKED_URL}?4`,
     `simple message 1`,
     `simple message 2`,
     "Navigated to",
     `${BLOCKED_URL}?5`,
@@ -141,78 +141,78 @@ add_task(async function testContentBlock
   onContentBlockingWarningGroupMessage =
     waitForMessage(hud, CONTENT_BLOCKING_GROUP_LABEL, ".warn");
   emitStorageAccessBlockedMessage(hud);
   ({node} = await onContentBlockingWarningGroupMessage);
   is(node.querySelector(".warning-group-badge").textContent, "2",
     "The badge has the expected text");
 
   checkConsoleOutputForWarningGroup(hud, [
-    `▼︎ ${CONTENT_BLOCKING_GROUP_LABEL}`,
+    `▼︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`,
     `| ${BLOCKED_URL}?1`,
     `| ${BLOCKED_URL}?2`,
     `| ${BLOCKED_URL}?3`,
     `| ${BLOCKED_URL}?4`,
     `simple message 1`,
     `simple message 2`,
     `Navigated to`,
-    `▶︎ ${CONTENT_BLOCKING_GROUP_LABEL}`,
+    `▶︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`,
     `simple message 3`,
   ]);
 
   info("Check that opening this group works");
   node.querySelector(".arrow").click();
   await waitFor(() => findMessages(hud, BLOCKED_URL).length === 6);
 
   checkConsoleOutputForWarningGroup(hud, [
-    `▼︎ ${CONTENT_BLOCKING_GROUP_LABEL}`,
+    `▼︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`,
     `| ${BLOCKED_URL}?1`,
     `| ${BLOCKED_URL}?2`,
     `| ${BLOCKED_URL}?3`,
     `| ${BLOCKED_URL}?4`,
     `simple message 1`,
     `simple message 2`,
     `Navigated to`,
-    `▼︎ ${CONTENT_BLOCKING_GROUP_LABEL}`,
+    `▼︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`,
     `| ${BLOCKED_URL}?5`,
     `| ${BLOCKED_URL}?6`,
     `simple message 3`,
   ]);
 
   info("Check that closing this group works, and let the other one open");
   node.querySelector(".arrow").click();
   await waitFor(() => findMessages(hud, BLOCKED_URL).length === 4);
 
   checkConsoleOutputForWarningGroup(hud, [
-    `▼︎ ${CONTENT_BLOCKING_GROUP_LABEL}`,
+    `▼︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`,
     `| ${BLOCKED_URL}?1`,
     `| ${BLOCKED_URL}?2`,
     `| ${BLOCKED_URL}?3`,
     `| ${BLOCKED_URL}?4`,
     `simple message 1`,
     `simple message 2`,
     `Navigated to`,
-    `▶︎ ${CONTENT_BLOCKING_GROUP_LABEL}`,
+    `▶︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`,
     `simple message 3`,
   ]);
 
   info("Log a third tracking protection message to check that the badge updates");
   emitStorageAccessBlockedMessage(hud);
   await waitFor(() => node.querySelector(".warning-group-badge").textContent == "3");
 
   checkConsoleOutputForWarningGroup(hud, [
-    `▼︎ ${CONTENT_BLOCKING_GROUP_LABEL}`,
+    `▼︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`,
     `| ${BLOCKED_URL}?1`,
     `| ${BLOCKED_URL}?2`,
     `| ${BLOCKED_URL}?3`,
     `| ${BLOCKED_URL}?4`,
     `simple message 1`,
     `simple message 2`,
     `Navigated to`,
-    `▶︎ ${CONTENT_BLOCKING_GROUP_LABEL}`,
+    `▶︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`,
     `simple message 3`,
   ]);
 });
 
 let cpt = 0;
 /**
  * Emit a Content Blocking message. This is done by loading an image from an origin
  * tagged as tracker. The image is loaded with a incremented counter query parameter
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_warning_groups_outside_console_group.js
@@ -0,0 +1,201 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that warning groups are not created outside console.group.
+
+"use strict";
+requestLongerTimeout(2);
+
+const TEST_FILE =
+  "browser/devtools/client/webconsole/test/mochitest/test-warning-groups.html";
+const TEST_URI = "http://example.com/" + TEST_FILE;
+
+const TRACKER_URL = "http://tracking.example.org/";
+const BLOCKED_URL = TRACKER_URL +
+  "browser/devtools/client/webconsole/test/mochitest/test-image.png";
+
+const {UrlClassifierTestUtils} = ChromeUtils.import("resource://testing-common/UrlClassifierTestUtils.jsm");
+UrlClassifierTestUtils.addTestTrackers();
+registerCleanupFunction(function() {
+  UrlClassifierTestUtils.cleanupTestTrackers();
+});
+
+// Tracking protection preferences
+pushPref("privacy.trackingprotection.enabled", true);
+
+add_task(async function testContentBlockingMessage() {
+  const CONTENT_BLOCKING_GROUP_LABEL = "Content blocked messages";
+
+  // Enable groupWarning and persist log
+  await pushPref("devtools.webconsole.groupWarningMessages", true);
+
+  const hud = await openNewTabAndConsole(TEST_URI);
+
+  info("Log a console.group");
+  const onGroupMessage = waitForMessage(hud, "myGroup");
+  let onInGroupMessage = waitForMessage(hud, "log in group");
+  ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+    content.wrappedJSObject.console.group("myGroup");
+    content.wrappedJSObject.console.log("log in group");
+  });
+  const {node: consoleGroupMessageNode} = await onGroupMessage;
+  await onInGroupMessage;
+
+  checkConsoleOutputForWarningGroup(hud, [
+    `▼ myGroup`,
+    `| log in group`,
+  ]);
+
+  info("Log a tracking protection message to check a single message isn't grouped");
+  const now = Date.now();
+  let onContentBlockingWarningMessage = waitForMessage(hud, BLOCKED_URL, ".warn");
+  emitStorageAccessBlockedMessage(now);
+  await onContentBlockingWarningMessage;
+
+  checkConsoleOutputForWarningGroup(hud, [
+    `▼ myGroup`,
+    `| log in group`,
+    `| ${BLOCKED_URL}?${now}-1`,
+  ]);
+
+  info("Collapse the console.group");
+  consoleGroupMessageNode.querySelector(".arrow").click();
+  await waitFor(() => !findMessage(hud, "log in group"));
+
+  checkConsoleOutputForWarningGroup(hud, [
+    `▶︎ myGroup`,
+  ]);
+
+  info("Expand the console.group");
+  consoleGroupMessageNode.querySelector(".arrow").click();
+  await waitFor(() => findMessage(hud, "log in group"));
+
+  checkConsoleOutputForWarningGroup(hud, [
+    `▼ myGroup`,
+    `| log in group`,
+    `| ${BLOCKED_URL}?${now}-1`,
+  ]);
+
+  info("Log a second tracking protection message to check that it causes the grouping");
+  const onContentBlockingWarningGroupMessage =
+    waitForMessage(hud, CONTENT_BLOCKING_GROUP_LABEL, ".warn");
+  emitStorageAccessBlockedMessage(now);
+  const {node: warningGroupNode} = await onContentBlockingWarningGroupMessage;
+
+  checkConsoleOutputForWarningGroup(hud, [
+    `▶︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`,
+    `▼ myGroup`,
+    `| log in group`,
+  ]);
+
+  info("Open the warning group");
+  warningGroupNode.querySelector(".arrow").click();
+  await waitFor(() => findMessage(hud, BLOCKED_URL));
+
+  checkConsoleOutputForWarningGroup(hud, [
+    `▼︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`,
+    `| ${BLOCKED_URL}?${now}-1`,
+    `| ${BLOCKED_URL}?${now}-2`,
+    `▼ myGroup`,
+    `| log in group`,
+  ]);
+
+  info("Log a new tracking protection message to check it appears inside the group");
+  onContentBlockingWarningMessage =
+    waitForMessage(hud, BLOCKED_URL, ".warn");
+  emitStorageAccessBlockedMessage(now);
+  await onContentBlockingWarningMessage;
+  ok(true, "The new tracking protection message is displayed");
+
+  checkConsoleOutputForWarningGroup(hud, [
+    `▼︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`,
+    `| ${BLOCKED_URL}?${now}-1`,
+    `| ${BLOCKED_URL}?${now}-2`,
+    `| ${BLOCKED_URL}?${now}-3`,
+    `▼ myGroup`,
+    `| log in group`,
+  ]);
+
+  info("Log a simple message to check if it goes into the console.group");
+  onInGroupMessage = waitForMessage(hud, "log in group");
+  ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+    content.wrappedJSObject.console.log("second log in group");
+  });
+  await onInGroupMessage;
+
+  checkConsoleOutputForWarningGroup(hud, [
+    `▼︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`,
+    `| ${BLOCKED_URL}?${now}-1`,
+    `| ${BLOCKED_URL}?${now}-2`,
+    `| ${BLOCKED_URL}?${now}-3`,
+    `▼ myGroup`,
+    `| log in group`,
+    `| second log in group`,
+  ]);
+
+  info("Collapse the console.group");
+  consoleGroupMessageNode.querySelector(".arrow").click();
+  await waitFor(() => !findMessage(hud, "log in group"));
+
+  checkConsoleOutputForWarningGroup(hud, [
+    `▼︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`,
+    `| ${BLOCKED_URL}?${now}-1`,
+    `| ${BLOCKED_URL}?${now}-2`,
+    `| ${BLOCKED_URL}?${now}-3`,
+    `▶︎ myGroup`,
+  ]);
+
+  info("Close the warning group");
+  warningGroupNode.querySelector(".arrow").click();
+  await waitFor(() => !findMessage(hud, BLOCKED_URL));
+
+  checkConsoleOutputForWarningGroup(hud, [
+    `▶︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`,
+    `▶︎ myGroup`,
+  ]);
+
+  info("Open the console group");
+  consoleGroupMessageNode.querySelector(".arrow").click();
+  await waitFor(() => findMessage(hud, "log in group"));
+
+  checkConsoleOutputForWarningGroup(hud, [
+    `▶︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`,
+    `▼ myGroup`,
+    `| log in group`,
+    `| second log in group`,
+  ]);
+
+  info("Collapse the console.group");
+  consoleGroupMessageNode.querySelector(".arrow").click();
+  await waitFor(() => !findMessage(hud, "log in group"));
+
+  checkConsoleOutputForWarningGroup(hud, [
+    `▶︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`,
+    `▶︎ myGroup`,
+  ]);
+
+  info("Open the warning group");
+  warningGroupNode.querySelector(".arrow").click();
+  await waitFor(() => findMessage(hud, BLOCKED_URL));
+
+  checkConsoleOutputForWarningGroup(hud, [
+    `▼︎⚠ ${CONTENT_BLOCKING_GROUP_LABEL}`,
+    `| ${BLOCKED_URL}?${now}-1`,
+    `| ${BLOCKED_URL}?${now}-2`,
+    `| ${BLOCKED_URL}?${now}-3`,
+    `▶︎ myGroup`,
+  ]);
+});
+
+let cpt = 0;
+/**
+ * Emit a Content Blocking message. This is done by loading an image from an origin
+ * tagged as tracker. The image is loaded with a incremented counter query parameter
+ * each time so we can get the warning message.
+ */
+function emitStorageAccessBlockedMessage(prefix) {
+  const url = `${BLOCKED_URL}?${prefix}-${++cpt}`;
+  ContentTask.spawn(gBrowser.selectedBrowser, url, function(innerURL) {
+    content.wrappedJSObject.loadImage(innerURL);
+  });
+}
--- a/devtools/client/webconsole/test/mochitest/head.js
+++ b/devtools/client/webconsole/test/mochitest/head.js
@@ -1240,41 +1240,83 @@ function isScrolledToBottom(container) {
 }
 
 /**
  *
  * @param {WebConsole} hud
  * @param {Array<String>} expectedMessages: An array of string representing the messages
  *                        from the output. This can only be a part of the string of the
  *                        message.
- *                        Start the string with "▶︎ " or "▼ " to indicate that the
+ *                        Start the string with "▶︎⚠ " or "▼⚠ " to indicate that the
  *                        message is a warningGroup (with respectively an open or
  *                        collapsed arrow).
  *                        Start the string with "|︎ " to indicate that the message is
  *                        inside a group and should be indented.
  */
 function checkConsoleOutputForWarningGroup(hud, expectedMessages) {
   const messages = findMessages(hud, "");
   is(messages.length, expectedMessages.length, "Got the expected number of messages");
+
+  const isInWarningGroup = index => {
+    const message = expectedMessages[index];
+    if (!message.startsWith("|")) {
+      return false;
+    }
+    const groups = expectedMessages.slice(0, index)
+      .reverse()
+      .filter(m => !m.startsWith("|"));
+    if (groups.length === 0) {
+      ok(false, "Unexpected structure: an indented message isn't in a group");
+    }
+
+    return groups[0].startsWith("▶︎⚠") || groups[0].startsWith("▼⚠");
+  };
+
   expectedMessages.forEach((expectedMessage, i) => {
     const message = messages[i];
+    info(`Checking "${expectedMessage}"`);
+
+    // Collapsed Warning group
+    if (expectedMessage.startsWith("▶︎⚠")) {
+      is(message.querySelector(".arrow").getAttribute("aria-expanded"), "false",
+        "There's a collapsed arrow");
+      is(message.querySelector(".indent").getAttribute("data-indent"), "0",
+        "The warningGroup has the expected indent");
+      expectedMessage = expectedMessage.replace("▶︎⚠ ", "");
+    }
+
+    // Expanded Warning group
+    if (expectedMessage.startsWith("▼︎⚠")) {
+      is(message.querySelector(".arrow").getAttribute("aria-expanded"), "true",
+        "There's an expanded arrow");
+      is(message.querySelector(".indent").getAttribute("data-indent"), "0",
+        "The warningGroup has the expected indent");
+      expectedMessage = expectedMessage.replace("▼︎⚠ ", "");
+    }
+
+    // Collapsed console.group
     if (expectedMessage.startsWith("▶︎")) {
       is(message.querySelector(".arrow").getAttribute("aria-expanded"), "false",
         "There's a collapsed arrow");
       expectedMessage = expectedMessage.replace("▶︎ ", "");
     }
 
+    // Expanded console.group
     if (expectedMessage.startsWith("▼")) {
       is(message.querySelector(".arrow").getAttribute("aria-expanded"), "true",
         "There's an expanded arrow");
-      expectedMessage = expectedMessage.replace("▼︎ ", "");
+      expectedMessage = expectedMessage.replace("▼ ", "");
     }
 
+    // In-group message
     if (expectedMessage.startsWith("|")) {
-      is(message.querySelector(".indent.warning-indent").getAttribute("data-indent"), "1",
-        "The message has the expected indent");
+      if (isInWarningGroup(i)) {
+        is(message.querySelector(".indent.warning-indent").getAttribute("data-indent"),
+          "1", "The message has the expected indent");
+      }
+
       expectedMessage = expectedMessage.replace("| ", "");
     }
 
     ok(message.textContent.trim().includes(expectedMessage.trim()), `Message includes ` +
       `the expected "${expectedMessage}" content - "${message.textContent.trim()}"`);
   });
 }