messages.js (53502B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 "use strict"; 5 6 const { 7 isGroupType, 8 isMessageNetworkError, 9 l10n, 10 } = require("resource://devtools/client/webconsole/utils/messages.js"); 11 12 const constants = require("resource://devtools/client/webconsole/constants.js"); 13 const { DEFAULT_FILTERS, FILTERS, MESSAGE_TYPE, MESSAGE_SOURCE } = constants; 14 15 const { getGripPreviewItems } = ChromeUtils.importESModule( 16 "resource://devtools/client/shared/components/reps/index.mjs", 17 { global: "current" } 18 ); 19 loader.lazyRequireGetter( 20 this, 21 "getUnicodeUrlPath", 22 "resource://devtools/client/shared/unicode-url.js", 23 true 24 ); 25 loader.lazyRequireGetter( 26 this, 27 "getSourceNames", 28 "resource://devtools/client/shared/source-utils.js", 29 true 30 ); 31 loader.lazyRequireGetter( 32 this, 33 [ 34 "areMessagesSimilar", 35 "createWarningGroupMessage", 36 "isWarningGroup", 37 "getWarningGroupType", 38 "getDescriptorValue", 39 "getParentWarningGroupMessageId", 40 "getNaturalOrder", 41 ], 42 "resource://devtools/client/webconsole/utils/messages.js", 43 true 44 ); 45 46 const { 47 UPDATE_REQUEST, 48 } = require("resource://devtools/client/netmonitor/src/constants.js"); 49 50 const { 51 processNetworkUpdates, 52 } = require("resource://devtools/client/netmonitor/src/utils/request-utils.js"); 53 54 const MessageState = overrides => 55 Object.freeze( 56 Object.assign( 57 { 58 // List of all the messages added to the console. Unlike other properties, this Map 59 // will be mutated on state changes for performance reasons. 60 mutableMessagesById: new Map(), 61 // Array of message ids, in chronological order. We use a dedicated property to store 62 // the order (instead of relying on the order of insertion in mutableMessagesById) 63 // as we might receive messages that need to be inserted at a specific index. Doing 64 // so on the Map can be costly, especially when the Map holds lots of messages. 65 mutableMessagesOrder: [], 66 // List of elements matching the selector of CSS Warning messages(populated 67 // on-demand via the UI). 68 cssMessagesMatchingElements: new Map(), 69 // Array of the visible messages. 70 visibleMessages: [], 71 // Object for the filtered messages. 72 filteredMessagesCount: getDefaultFiltersCounter(), 73 // List of the message ids which are opened. 74 messagesUiById: [], 75 // Map of the form {groupMessageId : groupArray}, 76 // where groupArray is the list of of all the parent groups' ids of the groupMessageId. 77 // This handles console API groups. 78 groupsById: new Map(), 79 // Message id of the current console API group (no corresponding console.groupEnd yet). 80 currentGroup: null, 81 // This group handles "warning groups" (Content Blocking, CORS, CSP, …) 82 warningGroupsById: new Map(), 83 // Array of fronts to release (i.e. fronts logged in removed messages). 84 // This array *should not* be consumed by any UI component. 85 frontsToRelease: [], 86 // Map of the form {messageId : numberOfRepeat} 87 repeatById: {}, 88 // Map of the form {messageId : networkInformation} 89 // `networkInformation` holds request, response, totalTime, ... 90 networkMessagesUpdateById: {}, 91 // Id of the last messages that was added. 92 lastMessageId: null, 93 // List of the message ids which are disabled 94 disabledMessagesById: [], 95 }, 96 overrides 97 ) 98 ); 99 100 function cloneState(state) { 101 return { 102 visibleMessages: [...state.visibleMessages], 103 filteredMessagesCount: { ...state.filteredMessagesCount }, 104 messagesUiById: [...state.messagesUiById], 105 cssMessagesMatchingElements: new Map(state.cssMessagesMatchingElements), 106 groupsById: new Map(state.groupsById), 107 frontsToRelease: [...state.frontsToRelease], 108 repeatById: { ...state.repeatById }, 109 networkMessagesUpdateById: { ...state.networkMessagesUpdateById }, 110 warningGroupsById: new Map(state.warningGroupsById), 111 // no need to mutate the properties below as they're not directly triggering re-render 112 mutableMessagesById: state.mutableMessagesById, 113 mutableMessagesOrder: state.mutableMessagesOrder, 114 currentGroup: state.currentGroup, 115 lastMessageId: state.lastMessageId, 116 disabledMessagesById: [...state.disabledMessagesById], 117 }; 118 } 119 120 /** 121 * Add a console message to the state. 122 * 123 * @param {ConsoleMessage} newMessage: The message to add to the state. 124 * @param {MessageState} state: The message state ( = managed by this reducer). 125 * @param {FiltersState} filtersState: The filters state. 126 * @param {PrefsState} prefsState: The preferences state. 127 * @param {UiState} uiState: The ui state. 128 * @returns {MessageState} a new messages state. 129 */ 130 // eslint-disable-next-line complexity 131 function addMessage(newMessage, state, filtersState, prefsState, uiState) { 132 const { mutableMessagesById, groupsById, repeatById } = state; 133 134 if (newMessage.type === constants.MESSAGE_TYPE.NAVIGATION_MARKER) { 135 // We set the state's currentGroup property to null after navigating 136 state.currentGroup = null; 137 } 138 const { currentGroup } = state; 139 140 if (newMessage.type === constants.MESSAGE_TYPE.NULL_MESSAGE) { 141 // When the message has a NULL type, we don't add it. 142 return state; 143 } 144 145 if (newMessage.type === constants.MESSAGE_TYPE.END_GROUP) { 146 // Compute the new current group. 147 state.currentGroup = getNewCurrentGroup(currentGroup, groupsById); 148 return state; 149 } 150 151 const lastMessage = mutableMessagesById.get(state.lastMessageId); 152 // It can happen that the new message was actually emitted earlier than the last message, 153 // which means we need to insert it at the right position. 154 const isUnsorted = 155 lastMessage && lastMessage.timeStamp > newMessage.timeStamp; 156 157 if (lastMessage && mutableMessagesById.size > 0) { 158 if ( 159 // only repeat messages if the group similar messages pref is enabled 160 prefsState.groupSimilar && 161 lastMessage.groupId === currentGroup && 162 areMessagesSimilar(lastMessage, newMessage) 163 ) { 164 state.repeatById[lastMessage.id] = (repeatById[lastMessage.id] || 1) + 1; 165 return state; 166 } 167 } 168 169 // Store the id of the message as being the last one being added. 170 if (!isUnsorted) { 171 state.lastMessageId = newMessage.id; 172 } 173 174 // Add the new message with a reference to the parent group. 175 const parentGroups = getParentGroups(currentGroup, groupsById); 176 if (!isWarningGroup(newMessage)) { 177 newMessage.groupId = currentGroup; 178 newMessage.indent = parentGroups.length; 179 } 180 181 // Check if the current message could be placed in a Warning Group. 182 // This needs to be done before setting the new message in mutableMessagesById so we have a 183 // proper message. 184 const warningGroupType = getWarningGroupType(newMessage); 185 186 // If the preference for grouping is true, and the new message could be in a warning group. 187 if (prefsState.groupSimilar && warningGroupType !== null) { 188 const warningGroupMessageId = getParentWarningGroupMessageId(newMessage); 189 190 // If there's no warning group for the type/innerWindowID yet 191 if (!state.mutableMessagesById.has(warningGroupMessageId)) { 192 // We create it and add it to the store. 193 const groupMessage = createWarningGroupMessage( 194 warningGroupMessageId, 195 warningGroupType, 196 newMessage 197 ); 198 state = addMessage( 199 groupMessage, 200 state, 201 filtersState, 202 prefsState, 203 uiState 204 ); 205 } 206 207 // We add the new message to the appropriate warningGroup. 208 state.warningGroupsById.get(warningGroupMessageId).push(newMessage.id); 209 210 // If the warningGroup message is not visible yet, but should be. 211 if ( 212 !state.visibleMessages.includes(warningGroupMessageId) && 213 getMessageVisibility( 214 state.mutableMessagesById.get(warningGroupMessageId), 215 { 216 messagesState: state, 217 filtersState, 218 prefsState, 219 uiState, 220 } 221 ).visible 222 ) { 223 // Then we put it in the visibleMessages properties, at the position of the first 224 // warning message inside the warningGroup. 225 // If that first warning message is in a console.group, we place it before the 226 // outermost console.group message. 227 const firstWarningMessageId = state.warningGroupsById.get( 228 warningGroupMessageId 229 )[0]; 230 const firstWarningMessage = state.mutableMessagesById.get( 231 firstWarningMessageId 232 ); 233 const outermostGroupId = getOutermostGroup( 234 firstWarningMessage, 235 groupsById 236 ); 237 const groupIndex = state.visibleMessages.indexOf(outermostGroupId); 238 const warningMessageIndex = state.visibleMessages.indexOf( 239 firstWarningMessageId 240 ); 241 242 if (groupIndex > -1) { 243 // We remove the warning message 244 if (warningMessageIndex > -1) { 245 state.visibleMessages.splice(warningMessageIndex, 1); 246 } 247 248 // And we put the warning group before the console.group 249 state.visibleMessages.splice(groupIndex, 0, warningGroupMessageId); 250 } else { 251 // If the warning message is not in a console.group, we replace it by the 252 // warning group message. 253 state.visibleMessages.splice( 254 warningMessageIndex, 255 1, 256 warningGroupMessageId 257 ); 258 } 259 } 260 } 261 262 // If we're creating a warningGroup, we init the array for its children. 263 if (isWarningGroup(newMessage)) { 264 state.warningGroupsById.set(newMessage.id, []); 265 } 266 267 const addedMessage = Object.freeze(newMessage); 268 269 // If the new message isn't the "oldest" one, then we need to insert it at the right 270 // position in the message map. 271 if (isUnsorted) { 272 let newMessageIndex = 0; 273 // This is can be on a hot path, so we're not using `findIndex`, which could be slow. 274 // Furthermore, there's a high chance the message beed to be inserted somewhere at the 275 // end of the list, so we loop through mutableMessagesOrder in reverse order. 276 for (let i = state.mutableMessagesOrder.length - 1; i >= 0; i--) { 277 const message = state.mutableMessagesById.get( 278 state.mutableMessagesOrder[i] 279 ); 280 if (message.timeStamp <= addedMessage.timeStamp) { 281 newMessageIndex = i + 1; 282 break; 283 } 284 } 285 286 state.mutableMessagesOrder.splice(newMessageIndex, 0, addedMessage.id); 287 } else { 288 state.mutableMessagesOrder.push(addedMessage.id); 289 } 290 state.mutableMessagesById.set(addedMessage.id, addedMessage); 291 292 if (newMessage.type === "trace" || newMessage.type === "logPoint") { 293 // We want the stacktrace to be open by default. 294 state.messagesUiById.push(newMessage.id); 295 } else if (isGroupType(newMessage.type)) { 296 state.currentGroup = newMessage.id; 297 state.groupsById.set(newMessage.id, parentGroups); 298 299 if (newMessage.type === constants.MESSAGE_TYPE.START_GROUP) { 300 // We want the group to be open by default. 301 state.messagesUiById.push(newMessage.id); 302 } 303 } 304 305 const { visible, cause } = getMessageVisibility(addedMessage, { 306 messagesState: state, 307 filtersState, 308 prefsState, 309 uiState, 310 }); 311 312 if (visible) { 313 // If the message is part of a visible warning group, we want to add it after the last 314 // visible message of the group. 315 const warningGroupId = getParentWarningGroupMessageId(newMessage); 316 if (warningGroupId && state.visibleMessages.includes(warningGroupId)) { 317 // Defaults to the warning group message. 318 let index = state.visibleMessages.indexOf(warningGroupId); 319 320 // We loop backward through the warning group's messages to get the latest visible 321 // messages in it. 322 const messagesInWarningGroup = 323 state.warningGroupsById.get(warningGroupId); 324 for (let i = messagesInWarningGroup.length - 1; i >= 0; i--) { 325 const idx = state.visibleMessages.indexOf(messagesInWarningGroup[i]); 326 if (idx > -1) { 327 index = idx; 328 break; 329 } 330 } 331 // Inserts the new warning message at the wanted location "in" the warning group. 332 state.visibleMessages.splice(index + 1, 0, newMessage.id); 333 } else if (isUnsorted) { 334 // If the new message wasn't the "oldest" one, then we need to insert its id at 335 // the right position in the array. 336 // This is can be on a hot path, so we're not using `findIndex`, which could be slow. 337 // Furthermore, there's a high chance the message beed to be inserted somewhere at the 338 // end of the list, so we loop through visibleMessages in reverse order. 339 let index = 0; 340 for (let i = state.visibleMessages.length - 1; i >= 0; i--) { 341 const id = state.visibleMessages[i]; 342 if ( 343 state.mutableMessagesById.get(id).timeStamp <= newMessage.timeStamp 344 ) { 345 index = i + 1; 346 break; 347 } 348 } 349 state.visibleMessages.splice(index, 0, newMessage.id); 350 } else { 351 state.visibleMessages.push(newMessage.id); 352 } 353 maybeSortVisibleMessages(state, false); 354 } else if (DEFAULT_FILTERS.includes(cause)) { 355 state.filteredMessagesCount.global++; 356 state.filteredMessagesCount[cause]++; 357 } 358 359 // Append received network-data also into networkMessagesUpdateById 360 // that is responsible for collecting (lazy loaded) HTTP payload data. 361 if (newMessage.source == "network") { 362 state.networkMessagesUpdateById[newMessage.actor] = newMessage; 363 } 364 365 return state; 366 } 367 368 // eslint-disable-next-line complexity 369 function messages( 370 state = MessageState(), 371 action, 372 filtersState, 373 prefsState, 374 uiState 375 ) { 376 const { 377 mutableMessagesById, 378 cssMessagesMatchingElements, 379 messagesUiById, 380 networkMessagesUpdateById, 381 groupsById, 382 visibleMessages, 383 disabledMessagesById, 384 } = state; 385 386 const { logLimit } = prefsState; 387 388 let newState; 389 switch (action.type) { 390 case constants.MESSAGES_ADD: { 391 // If the action holds more messages than the log limit, we can preemptively remove 392 // messages that will never be rendered. 393 const batchHasMoreMessagesThanLogLimit = 394 action.messages.length > logLimit; 395 const list = batchHasMoreMessagesThanLogLimit ? [] : action.messages; 396 if (batchHasMoreMessagesThanLogLimit) { 397 let prunableCount = 0; 398 let lastMessage = null; 399 for (let i = action.messages.length - 1; i >= 0; i--) { 400 const message = action.messages[i]; 401 if ( 402 !message.groupId && 403 !isGroupType(message.type) && 404 message.type !== MESSAGE_TYPE.END_GROUP 405 ) { 406 const messagesSimilar = areMessagesSimilar(lastMessage, message); 407 if (!messagesSimilar) { 408 prunableCount++; 409 } 410 // Once we've added the max number of messages that can be added, stop. 411 // Except for repeated messages, where we keep adding over the limit. 412 if (prunableCount <= logLimit || messagesSimilar) { 413 list.unshift(action.messages[i]); 414 } else { 415 break; 416 } 417 } else { 418 list.unshift(message); 419 } 420 lastMessage = message; 421 } 422 } 423 424 newState = cloneState(state); 425 for (const message of list) { 426 newState = addMessage( 427 message, 428 newState, 429 filtersState, 430 prefsState, 431 uiState 432 ); 433 } 434 435 return limitTopLevelMessageCount(newState, logLimit); 436 } 437 438 case constants.MESSAGES_CLEAR: { 439 const frontsToRelease = []; 440 for (const message of state.mutableMessagesById.values()) { 441 // We want to minimize time spent in reducer as much as we can, so we're using 442 // prototype.push.apply here as it seems faster than other solutions (e.g. the 443 // spread operator, Array#concat, …) 444 Array.prototype.push.apply( 445 frontsToRelease, 446 getAllFrontsInMessage(message) 447 ); 448 } 449 return MessageState({ 450 // Store all actors from removed messages. This array is used by 451 // `releaseActorsEnhancer` to release all of those backend actors. 452 frontsToRelease, 453 }); 454 } 455 456 case constants.PRIVATE_MESSAGES_CLEAR: { 457 const removedIds = new Set(); 458 for (const [id, message] of mutableMessagesById) { 459 if (message.private === true) { 460 removedIds.add(id); 461 } 462 } 463 464 // If there's no private messages, there's no need to change the state. 465 if (removedIds.size === 0) { 466 return state; 467 } 468 469 return removeMessagesFromState( 470 { 471 ...state, 472 }, 473 removedIds 474 ); 475 } 476 477 case constants.TARGET_MESSAGES_REMOVE: { 478 const removedIds = new Set(); 479 for (const [id, message] of mutableMessagesById) { 480 // Remove message from the target but not evaluations and their results, so 481 // 1. we're consistent with the filtering behavior, i.e. we never hide those 482 // 2. when switching mode from multiprocess to parent process and back to multi, 483 // if we'd clear evaluations we wouldn't have a way to get them back, unlike 484 // log messages and errors, which are still available in the server caches). 485 if ( 486 message.targetFront == action.targetFront && 487 message.type !== MESSAGE_TYPE.COMMAND && 488 message.type !== MESSAGE_TYPE.RESULT 489 ) { 490 removedIds.add(id); 491 } 492 } 493 494 return removeMessagesFromState( 495 { 496 ...state, 497 }, 498 removedIds 499 ); 500 } 501 502 case constants.MESSAGES_DISABLE: 503 return { 504 ...state, 505 disabledMessagesById: [...disabledMessagesById, ...action.ids], 506 }; 507 508 case constants.MESSAGE_OPEN: { 509 const openState = { ...state }; 510 openState.messagesUiById = [...messagesUiById, action.id]; 511 const currMessage = mutableMessagesById.get(action.id); 512 513 // If the message is a console.group/groupCollapsed or a warning group. 514 if (isGroupType(currMessage.type) || isWarningGroup(currMessage)) { 515 // We want to make its children visible 516 const messagesToShow = []; 517 for (const id of state.mutableMessagesOrder) { 518 const message = mutableMessagesById.get(id); 519 if ( 520 !visibleMessages.includes(message.id) && 521 ((isWarningGroup(currMessage) && !!getWarningGroupType(message)) || 522 (isGroupType(currMessage.type) && 523 getParentGroups(message.groupId, groupsById).includes( 524 action.id 525 ))) && 526 getMessageVisibility(message, { 527 messagesState: openState, 528 filtersState, 529 prefsState, 530 uiState, 531 // We want to check if the message is in an open group 532 // only if it is not a direct child of the group we're opening. 533 checkGroup: message.groupId !== action.id, 534 }).visible 535 ) { 536 messagesToShow.push(id); 537 } 538 } 539 540 // We can then insert the messages ids right after the one of the group. 541 const insertIndex = visibleMessages.indexOf(action.id) + 1; 542 openState.visibleMessages = [ 543 ...visibleMessages.slice(0, insertIndex), 544 ...messagesToShow, 545 ...visibleMessages.slice(insertIndex), 546 ]; 547 } 548 return openState; 549 } 550 551 case constants.MESSAGE_CLOSE: { 552 const closeState = { ...state }; 553 const messageId = action.id; 554 const index = closeState.messagesUiById.indexOf(messageId); 555 closeState.messagesUiById.splice(index, 1); 556 closeState.messagesUiById = [...closeState.messagesUiById]; 557 558 // If the message is a group 559 if (isGroupType(mutableMessagesById.get(messageId).type)) { 560 // Hide all its children, unless they're in a warningGroup. 561 closeState.visibleMessages = visibleMessages.filter((id, i, arr) => { 562 const message = mutableMessagesById.get(id); 563 const warningGroupMessage = mutableMessagesById.get( 564 getParentWarningGroupMessageId(message) 565 ); 566 567 // If the message is in a warning group, then we return its current visibility. 568 if ( 569 shouldGroupWarningMessages( 570 warningGroupMessage, 571 closeState, 572 prefsState 573 ) 574 ) { 575 return arr.includes(id); 576 } 577 578 const parentGroups = getParentGroups(message.groupId, groupsById); 579 return parentGroups.includes(messageId) === false; 580 }); 581 } else if (isWarningGroup(mutableMessagesById.get(messageId))) { 582 // If the message was a warningGroup, we hide all the messages in the group. 583 const groupMessages = closeState.warningGroupsById.get(messageId); 584 closeState.visibleMessages = visibleMessages.filter( 585 id => !groupMessages.includes(id) 586 ); 587 } 588 return closeState; 589 } 590 591 case constants.CSS_MESSAGE_ADD_MATCHING_ELEMENTS: 592 return { 593 ...state, 594 cssMessagesMatchingElements: new Map(cssMessagesMatchingElements).set( 595 action.id, 596 action.elements 597 ), 598 }; 599 600 case constants.NETWORK_MESSAGES_UPDATE: { 601 const updatedState = { 602 ...state, 603 networkMessagesUpdateById: { 604 ...networkMessagesUpdateById, 605 }, 606 }; 607 let hasNetworkError = null; 608 for (const message of action.messages) { 609 const { id } = message; 610 updatedState.mutableMessagesById.set(id, message); 611 updatedState.networkMessagesUpdateById[id] = { 612 ...(updatedState.networkMessagesUpdateById[id] || {}), 613 ...message, 614 }; 615 616 if (isMessageNetworkError(message)) { 617 hasNetworkError = true; 618 } 619 } 620 621 // If the message updates contained a network error, then we may have to display it. 622 if (hasNetworkError) { 623 return setVisibleMessages({ 624 messagesState: updatedState, 625 filtersState, 626 prefsState, 627 uiState, 628 }); 629 } 630 631 return updatedState; 632 } 633 634 case UPDATE_REQUEST: 635 case constants.NETWORK_UPDATES_REQUEST: { 636 newState = { 637 ...state, 638 networkMessagesUpdateById: { 639 ...networkMessagesUpdateById, 640 }, 641 }; 642 643 // Netmonitor's UPDATE_REQUEST action comes for only one request 644 const updates = 645 action.type == UPDATE_REQUEST 646 ? [{ id: action.id, data: action.data }] 647 : action.updates; 648 for (const { id, data } of updates) { 649 const request = newState.networkMessagesUpdateById[id]; 650 if (!request) { 651 continue; 652 } 653 newState.networkMessagesUpdateById[id] = { 654 ...request, 655 ...processNetworkUpdates(data), 656 }; 657 } 658 return newState; 659 } 660 661 case constants.FRONTS_TO_RELEASE_CLEAR: 662 return { 663 ...state, 664 frontsToRelease: [], 665 }; 666 667 case constants.GROUP_SIMILAR_MESSAGES_TOGGLE: { 668 // There's no warningGroups, and the pref was set to false, 669 // we don't need to do anything. 670 if (!prefsState.groupSimilar && state.warningGroupsById.size === 0) { 671 return state; 672 } 673 674 let needSort = false; 675 for (const msgId of state.mutableMessagesOrder) { 676 const message = state.mutableMessagesById.get(msgId); 677 const warningGroupType = getWarningGroupType(message); 678 if (warningGroupType) { 679 const warningGroupMessageId = getParentWarningGroupMessageId(message); 680 681 // If there's no warning group for the type/innerWindowID yet. 682 if (!state.mutableMessagesById.has(warningGroupMessageId)) { 683 // We create it and add it to the store. 684 const groupMessage = createWarningGroupMessage( 685 warningGroupMessageId, 686 warningGroupType, 687 message 688 ); 689 state = addMessage( 690 groupMessage, 691 state, 692 filtersState, 693 prefsState, 694 uiState 695 ); 696 } 697 698 // We add the new message to the appropriate warningGroup. 699 const warningGroup = state.warningGroupsById.get( 700 warningGroupMessageId 701 ); 702 if (warningGroup && !warningGroup.includes(msgId)) { 703 warningGroup.push(msgId); 704 } 705 706 needSort = true; 707 } 708 } 709 710 // If we don't have any warning messages that could be in a group, we don't do 711 // anything. 712 if (!needSort) { 713 return state; 714 } 715 716 return setVisibleMessages({ 717 messagesState: state, 718 filtersState, 719 prefsState, 720 uiState, 721 // If the user disabled warning groups, we want the messages to be sorted by their 722 // timestamps. 723 forceTimestampSort: !prefsState.groupSimilar, 724 }); 725 } 726 727 case constants.MESSAGE_REMOVE: 728 return removeMessagesFromState( 729 { 730 ...state, 731 }, 732 new Set([action.id]) 733 ); 734 735 case constants.FILTER_TOGGLE: 736 case constants.FILTER_TEXT_SET: 737 case constants.FILTERS_CLEAR: 738 case constants.DEFAULT_FILTERS_RESET: 739 return setVisibleMessages({ 740 messagesState: state, 741 filtersState, 742 prefsState, 743 uiState, 744 }); 745 } 746 747 return state; 748 } 749 750 function setVisibleMessages({ 751 messagesState, 752 filtersState, 753 prefsState, 754 uiState, 755 forceTimestampSort = false, 756 }) { 757 const { 758 mutableMessagesById, 759 mutableMessagesOrder, 760 visibleMessages, 761 messagesUiById, 762 } = messagesState; 763 764 const messagesToShow = new Set(); 765 const matchedGroups = new Set(); 766 const filtered = getDefaultFiltersCounter(); 767 768 mutableMessagesOrder.forEach(msgId => { 769 const message = mutableMessagesById.get(msgId); 770 const groupParentId = message.groupId; 771 let hasMatchedAncestor = false; 772 const ancestors = []; 773 774 if (groupParentId) { 775 let ancestorId = groupParentId; 776 777 // we track the message's ancestors and their state 778 while (ancestorId) { 779 ancestors.push({ 780 ancestorId, 781 matchedFilters: matchedGroups.has(ancestorId), 782 isOpen: messagesUiById.includes(ancestorId), 783 isCurrentlyVisible: visibleMessages.includes(ancestorId), 784 }); 785 if (!hasMatchedAncestor && matchedGroups.has(ancestorId)) { 786 hasMatchedAncestor = true; 787 } 788 ancestorId = mutableMessagesById.get(ancestorId).groupId; 789 } 790 } 791 792 const { visible, cause } = getMessageVisibility(message, { 793 messagesState, 794 filtersState, 795 prefsState, 796 uiState, 797 hasMatchedAncestor, 798 }); 799 800 // if the message is not visible but passes the search filters, we show its visible ancestors 801 if (!visible && passSearchFilters(message, filtersState)) { 802 const tmp = []; 803 ancestors.forEach(msg => { 804 if (msg.isCurrentlyVisible) { 805 tmp.push(msg.ancestorId); 806 } 807 }); 808 tmp.reverse().forEach(id => { 809 messagesToShow.add(id); 810 }); 811 } 812 if (visible) { 813 // if the visible message is a child of a group, we add its ancestors to the visible messages 814 if (groupParentId) { 815 // We need to reverse the visibleAncestors array to show the groups in the correct order 816 ancestors.reverse().forEach(msg => { 817 messagesToShow.add(msg.ancestorId); 818 }); 819 } 820 821 // we keep track of matched startGroup and startGroupCollapsed messages so we don't filter their children 822 if ( 823 message.type === "startGroup" || 824 message.type === "startGroupCollapsed" 825 ) { 826 matchedGroups.add(msgId); 827 } 828 829 messagesToShow.add(msgId); 830 } else if (DEFAULT_FILTERS.includes(cause)) { 831 filtered.global = filtered.global + 1; 832 filtered[cause] = filtered[cause] + 1; 833 } 834 }); 835 836 const newState = { 837 ...messagesState, 838 visibleMessages: Array.from(messagesToShow), 839 filteredMessagesCount: filtered, 840 }; 841 842 maybeSortVisibleMessages( 843 newState, 844 // Only sort for warningGroups if the feature is enabled 845 prefsState.groupSimilar, 846 forceTimestampSort 847 ); 848 849 return newState; 850 } 851 852 /** 853 * Returns the new current group id given the previous current group and the groupsById 854 * state property. 855 * 856 * @param {string} currentGroup: id of the current group 857 * @param {Map} groupsById 858 * @param {Array} ignoredIds: An array of ids which can't be the new current group. 859 * @returns {string | null} The new current group id, or null if there isn't one. 860 */ 861 function getNewCurrentGroup(currentGroup, groupsById, ignoredIds = new Set()) { 862 if (!currentGroup) { 863 return null; 864 } 865 866 // Retrieve the parent groups of the current group. 867 const parents = groupsById.get(currentGroup); 868 869 // If there's at least one parent, make the first one the new currentGroup. 870 if (Array.isArray(parents) && parents.length) { 871 // If the found group must be ignored, let's search for its parent. 872 if (ignoredIds.has(parents[0])) { 873 return getNewCurrentGroup(parents[0], groupsById, ignoredIds); 874 } 875 876 return parents[0]; 877 } 878 879 return null; 880 } 881 882 function getParentGroups(currentGroup, groupsById) { 883 let groups = []; 884 if (currentGroup) { 885 // If there is a current group, we add it as a parent 886 groups = [currentGroup]; 887 888 // As well as all its parents, if it has some. 889 const parentGroups = groupsById.get(currentGroup); 890 if (Array.isArray(parentGroups) && parentGroups.length) { 891 groups = groups.concat(parentGroups); 892 } 893 } 894 895 return groups; 896 } 897 898 function getOutermostGroup(message, groupsById) { 899 const groups = getParentGroups(message.groupId, groupsById); 900 if (groups.length === 0) { 901 return null; 902 } 903 return groups[groups.length - 1]; 904 } 905 906 /** 907 * Remove all top level messages that exceeds message limit. 908 * Also populate an array of all backend actors associated with these 909 * messages so they can be released. 910 */ 911 function limitTopLevelMessageCount(newState, logLimit) { 912 let topLevelCount = 913 newState.groupsById.size === 0 914 ? newState.mutableMessagesById.size 915 : getToplevelMessageCount(newState); 916 917 if (topLevelCount <= logLimit) { 918 return newState; 919 } 920 921 const removedMessagesId = new Set(); 922 923 let cleaningGroup = false; 924 for (const id of newState.mutableMessagesOrder) { 925 const message = newState.mutableMessagesById.get(id); 926 // If we were cleaning a group and the current message does not have 927 // a groupId, we're done cleaning. 928 if (cleaningGroup === true && !message.groupId) { 929 cleaningGroup = false; 930 } 931 932 // If we're not cleaning a group and the message count is below the logLimit, 933 // we exit the loop. 934 if (cleaningGroup === false && topLevelCount <= logLimit) { 935 break; 936 } 937 938 // If we're not currently cleaning a group, and the current message is identified 939 // as a group, set the cleaning flag to true. 940 if (cleaningGroup === false && newState.groupsById.has(id)) { 941 cleaningGroup = true; 942 } 943 944 if (!message.groupId) { 945 topLevelCount--; 946 } 947 948 removedMessagesId.add(id); 949 } 950 951 return removeMessagesFromState(newState, removedMessagesId); 952 } 953 954 /** 955 * Clean the properties for a given state object and an array of removed messages ids. 956 * Be aware that this function MUTATE the `state` argument. 957 * 958 * @param {MessageState} state 959 * @param {Set} removedMessagesIds 960 * @returns {MessageState} 961 */ 962 function removeMessagesFromState(state, removedMessagesIds) { 963 if (removedMessagesIds.size === 0) { 964 return state; 965 } 966 967 const frontsToRelease = []; 968 const visibleMessages = [...state.visibleMessages]; 969 removedMessagesIds.forEach(id => { 970 const index = visibleMessages.indexOf(id); 971 if (index > -1) { 972 visibleMessages.splice(index, 1); 973 } 974 975 // We want to minimize time spent in reducer as much as we can, so we're using 976 // prototype.push.apply here as it seems faster than other solutions (e.g. the 977 // spread operator, Array#concat, …) 978 Array.prototype.push.apply( 979 frontsToRelease, 980 getAllFrontsInMessage(state.mutableMessagesById.get(id)) 981 ); 982 }); 983 984 if (state.visibleMessages.length > visibleMessages.length) { 985 state.visibleMessages = visibleMessages; 986 } 987 988 if (frontsToRelease.length) { 989 state.frontsToRelease = state.frontsToRelease.concat(frontsToRelease); 990 } 991 992 const isInRemovedId = id => removedMessagesIds.has(id); 993 const mapHasRemovedIdKey = map => { 994 for (const id of removedMessagesIds) { 995 if (map.has(id)) { 996 return true; 997 } 998 } 999 return false; 1000 }; 1001 const objectHasRemovedIdKey = obj => 1002 Object.keys(obj).findIndex(isInRemovedId) !== -1; 1003 1004 const cleanUpMap = map => { 1005 const clonedMap = new Map(map); 1006 removedMessagesIds.forEach(id => clonedMap.delete(id)); 1007 return clonedMap; 1008 }; 1009 const cleanUpObject = object => 1010 [...Object.entries(object)].reduce((res, [id, value]) => { 1011 if (!isInRemovedId(id)) { 1012 res[id] = value; 1013 } 1014 return res; 1015 }, {}); 1016 1017 removedMessagesIds.forEach(id => { 1018 state.mutableMessagesById.delete(id); 1019 1020 state.mutableMessagesOrder.splice( 1021 state.mutableMessagesOrder.indexOf(id), 1022 1 1023 ); 1024 }); 1025 1026 if (state.disabledMessagesById.find(isInRemovedId)) { 1027 state.disabledMessagesById = state.disabledMessagesById.filter( 1028 id => !isInRemovedId(id) 1029 ); 1030 } 1031 1032 if (state.messagesUiById.find(isInRemovedId)) { 1033 state.messagesUiById = state.messagesUiById.filter( 1034 id => !isInRemovedId(id) 1035 ); 1036 } 1037 1038 if (isInRemovedId(state.currentGroup)) { 1039 state.currentGroup = getNewCurrentGroup( 1040 state.currentGroup, 1041 state.groupsById, 1042 removedMessagesIds 1043 ); 1044 } 1045 1046 if (mapHasRemovedIdKey(state.cssMessagesMatchingElements)) { 1047 state.cssMessagesMatchingElements = cleanUpMap( 1048 state.cssMessagesMatchingElements 1049 ); 1050 } 1051 if (mapHasRemovedIdKey(state.groupsById)) { 1052 state.groupsById = cleanUpMap(state.groupsById); 1053 } 1054 1055 if (objectHasRemovedIdKey(state.repeatById)) { 1056 state.repeatById = cleanUpObject(state.repeatById); 1057 } 1058 1059 if (objectHasRemovedIdKey(state.networkMessagesUpdateById)) { 1060 state.networkMessagesUpdateById = cleanUpObject( 1061 state.networkMessagesUpdateById 1062 ); 1063 } 1064 1065 return state; 1066 } 1067 1068 /** 1069 * Get an array of all the fronts logged in a specific message. 1070 * 1071 * @param {Message} message: The message to get actors from. 1072 * @return {Array<ObjectFront|LongStringFront>} An array containing all the fronts logged 1073 * in a message. 1074 */ 1075 function getAllFrontsInMessage(message) { 1076 const { parameters, messageText } = message; 1077 1078 const fronts = []; 1079 const isFront = p => p && typeof p.release === "function"; 1080 1081 if (Array.isArray(parameters)) { 1082 message.parameters.forEach(parameter => { 1083 if (isFront(parameter)) { 1084 fronts.push(parameter); 1085 } 1086 }); 1087 } 1088 1089 if (isFront(messageText)) { 1090 fronts.push(messageText); 1091 } 1092 1093 return fronts; 1094 } 1095 1096 /** 1097 * Returns total count of top level messages (those which are not 1098 * within a group). 1099 */ 1100 function getToplevelMessageCount(state) { 1101 let count = 0; 1102 state.mutableMessagesById.forEach(message => { 1103 if (!message.groupId) { 1104 count++; 1105 } 1106 }); 1107 return count; 1108 } 1109 1110 /** 1111 * Check if a message should be visible in the console output, and if not, what 1112 * causes it to be hidden. 1113 * 1114 * @param {Message} message: The message to check 1115 * @param {object} option: An option object of the following shape: 1116 * - {MessageState} messagesState: The current messages state 1117 * - {FilterState} filtersState: The current filters state 1118 * - {PrefsState} prefsState: The current preferences state 1119 * - {UiState} uiState: The current ui state 1120 * - {Boolean} checkGroup: Set to false to not check if a message should 1121 * be visible because it is in a console.group. 1122 * - {Boolean} checkParentWarningGroupVisibility: Set to false to not 1123 * check if a message should be visible because it is in a 1124 * warningGroup and the warningGroup is visible. 1125 * - {Boolean} hasMatchedAncestor: Set to true if message is part of a 1126 * group that has been set to visible 1127 * 1128 * @return {object} An object of the following form: 1129 * - visible {Boolean}: true if the message should be visible 1130 * - cause {String}: if visible is false, what causes the message to be hidden. 1131 */ 1132 // eslint-disable-next-line complexity 1133 function getMessageVisibility( 1134 message, 1135 { 1136 messagesState, 1137 filtersState, 1138 prefsState, 1139 uiState, 1140 checkGroup = true, 1141 checkParentWarningGroupVisibility = true, 1142 hasMatchedAncestor = false, 1143 } 1144 ) { 1145 const warningGroupMessageId = getParentWarningGroupMessageId(message); 1146 const parentWarningGroupMessage = messagesState.mutableMessagesById.get( 1147 warningGroupMessageId 1148 ); 1149 1150 // Do not display the message if it's in closed group and not in a warning group. 1151 if ( 1152 checkGroup && 1153 !isInOpenedGroup( 1154 message, 1155 messagesState.groupsById, 1156 messagesState.messagesUiById 1157 ) && 1158 !shouldGroupWarningMessages( 1159 parentWarningGroupMessage, 1160 messagesState, 1161 prefsState 1162 ) 1163 ) { 1164 return { 1165 visible: false, 1166 cause: "closedGroup", 1167 }; 1168 } 1169 1170 // If the message is a warningGroup, check if it should be displayed. 1171 if (isWarningGroup(message)) { 1172 if (!shouldGroupWarningMessages(message, messagesState, prefsState)) { 1173 return { 1174 visible: false, 1175 cause: "warningGroupHeuristicNotMet", 1176 }; 1177 } 1178 1179 // Hide a warningGroup if the warning filter is off. 1180 if (!filtersState[FILTERS.WARN]) { 1181 // We don't include any cause as we don't want that message to be reflected in the 1182 // message count. 1183 return { 1184 visible: false, 1185 }; 1186 } 1187 1188 // Display a warningGroup if at least one of its message will be visible. 1189 const childrenMessages = messagesState.warningGroupsById.get(message.id); 1190 const hasVisibleChild = 1191 childrenMessages && 1192 childrenMessages.some(id => { 1193 const child = messagesState.mutableMessagesById.get(id); 1194 if (!child) { 1195 return false; 1196 } 1197 1198 const { visible, cause } = getMessageVisibility(child, { 1199 messagesState, 1200 filtersState, 1201 prefsState, 1202 uiState, 1203 checkParentWarningGroupVisibility: false, 1204 }); 1205 return visible && cause !== "visibleWarningGroup"; 1206 }); 1207 1208 if (hasVisibleChild) { 1209 return { 1210 visible: true, 1211 cause: "visibleChild", 1212 }; 1213 } 1214 } 1215 1216 // Do not display the message if it can be in a warningGroup, and the group is 1217 // displayed but collapsed. 1218 if ( 1219 parentWarningGroupMessage && 1220 shouldGroupWarningMessages( 1221 parentWarningGroupMessage, 1222 messagesState, 1223 prefsState 1224 ) && 1225 !messagesState.messagesUiById.includes(warningGroupMessageId) 1226 ) { 1227 return { 1228 visible: false, 1229 cause: "closedWarningGroup", 1230 }; 1231 } 1232 1233 // Display a message if it is in a warningGroup that is visible. We don't check the 1234 // warningGroup visibility if `checkParentWarningGroupVisibility` is false, because 1235 // it means we're checking the warningGroup visibility based on the visibility of its 1236 // children, which would cause an infinite loop. 1237 const parentVisibility = 1238 parentWarningGroupMessage && checkParentWarningGroupVisibility 1239 ? getMessageVisibility(parentWarningGroupMessage, { 1240 messagesState, 1241 filtersState, 1242 prefsState, 1243 uiState, 1244 checkGroup, 1245 checkParentWarningGroupVisibility, 1246 }) 1247 : null; 1248 if ( 1249 parentVisibility && 1250 parentVisibility.visible && 1251 parentVisibility.cause !== "visibleChild" 1252 ) { 1253 return { 1254 visible: true, 1255 cause: "visibleWarningGroup", 1256 }; 1257 } 1258 1259 // Some messages can't be filtered out (e.g. groups). 1260 // So, always return visible: true for those. 1261 if (isUnfilterable(message)) { 1262 return { 1263 visible: true, 1264 }; 1265 } 1266 1267 // Let's check all level filters (error, warn, log, …) and return visible: false 1268 // and the message level as a cause if the function returns false. 1269 if (!passLevelFilters(message, filtersState)) { 1270 return { 1271 visible: false, 1272 cause: message.level, 1273 }; 1274 } 1275 1276 if (!passCssFilters(message, filtersState)) { 1277 return { 1278 visible: false, 1279 cause: FILTERS.CSS, 1280 }; 1281 } 1282 1283 if (!passNetworkFilter(message, filtersState)) { 1284 return { 1285 visible: false, 1286 cause: FILTERS.NET, 1287 }; 1288 } 1289 1290 if (!passXhrFilter(message, filtersState)) { 1291 return { 1292 visible: false, 1293 cause: FILTERS.NETXHR, 1294 }; 1295 } 1296 1297 // This should always be the last check, or we might report that a message was hidden 1298 // because of text search, while it may be hidden because its category is disabled. 1299 // Do not check for search filters if it is part of a group and one of its ancestor 1300 // has matched the current search filters and set to visible 1301 if (!hasMatchedAncestor && !passSearchFilters(message, filtersState)) { 1302 return { 1303 visible: false, 1304 cause: FILTERS.TEXT, 1305 }; 1306 } 1307 1308 return { 1309 visible: true, 1310 }; 1311 } 1312 1313 function isUnfilterable(message) { 1314 return [ 1315 MESSAGE_TYPE.COMMAND, 1316 MESSAGE_TYPE.RESULT, 1317 MESSAGE_TYPE.NAVIGATION_MARKER, 1318 ].includes(message.type); 1319 } 1320 1321 function isInOpenedGroup(message, groupsById, messagesUI) { 1322 return ( 1323 !message.groupId || 1324 (!isGroupClosed(message.groupId, messagesUI) && 1325 !hasClosedParentGroup(groupsById.get(message.groupId), messagesUI)) 1326 ); 1327 } 1328 1329 function hasClosedParentGroup(group, messagesUI) { 1330 return group.some(groupId => isGroupClosed(groupId, messagesUI)); 1331 } 1332 1333 function isGroupClosed(groupId, messagesUI) { 1334 return messagesUI.includes(groupId) === false; 1335 } 1336 1337 /** 1338 * Returns true if the message shouldn't be hidden because of the network filter state. 1339 * 1340 * @param {object} message - The message to check the filter against. 1341 * @param {FilterState} filters - redux "filters" state. 1342 * @returns {boolean} 1343 */ 1344 function passNetworkFilter(message, filters) { 1345 // The message passes the filter if it is not a network message, 1346 // or if it is an xhr one, 1347 // or if the network filter is on. 1348 return ( 1349 message.source !== MESSAGE_SOURCE.NETWORK || 1350 message.isXHR === true || 1351 filters[FILTERS.NET] === true || 1352 (filters[FILTERS.ERROR] && isMessageNetworkError(message)) 1353 ); 1354 } 1355 1356 /** 1357 * Returns true if the message shouldn't be hidden because of the xhr filter state. 1358 * 1359 * @param {object} message - The message to check the filter against. 1360 * @param {FilterState} filters - redux "filters" state. 1361 * @returns {boolean} 1362 */ 1363 function passXhrFilter(message, filters) { 1364 // The message passes the filter if it is not a network message, 1365 // or if it is a non-xhr one, 1366 // or if the xhr filter is on. 1367 return ( 1368 message.source !== MESSAGE_SOURCE.NETWORK || 1369 message.isXHR === false || 1370 filters[FILTERS.NETXHR] === true || 1371 (filters[FILTERS.ERROR] && isMessageNetworkError(message)) 1372 ); 1373 } 1374 1375 /** 1376 * Returns true if the message shouldn't be hidden because of levels filter state. 1377 * 1378 * @param {object} message - The message to check the filter against. 1379 * @param {FilterState} filters - redux "filters" state. 1380 * @returns {boolean} 1381 */ 1382 function passLevelFilters(message, filters) { 1383 // The message passes the filter if it is not a console call, 1384 // or if its level matches the state of the corresponding filter. 1385 return ( 1386 (message.source !== MESSAGE_SOURCE.CONSOLE_API && 1387 message.source !== MESSAGE_SOURCE.JAVASCRIPT) || 1388 filters[message.level] === true || 1389 (filters[FILTERS.ERROR] && isMessageNetworkError(message)) 1390 ); 1391 } 1392 1393 /** 1394 * Returns true if the message shouldn't be hidden because of the CSS filter state. 1395 * 1396 * @param {object} message - The message to check the filter against. 1397 * @param {FilterState} filters - redux "filters" state. 1398 * @returns {boolean} 1399 */ 1400 function passCssFilters(message, filters) { 1401 // The message passes the filter if it is not a CSS message, 1402 // or if the CSS filter is on. 1403 return message.source !== MESSAGE_SOURCE.CSS || filters.css === true; 1404 } 1405 1406 /** 1407 * Returns true if the message shouldn't be hidden because of search filter state. 1408 * 1409 * @param {object} message - The message to check the filter against. 1410 * @param {FilterState} filters - redux "filters" state. 1411 * @returns {boolean} 1412 */ 1413 function passSearchFilters(message, filters) { 1414 const trimmed = (filters.text || "").trim(); 1415 1416 // "-"-prefix switched to exclude mode 1417 const exclude = trimmed.startsWith("-"); 1418 const term = exclude ? trimmed.slice(1) : trimmed; 1419 1420 // This regex matches a very basic regex with an optional i flag 1421 const regexMatch = /^\/(?<search>.+)\/(?<flags>i)?$/.exec(term); 1422 let regex; 1423 if (regexMatch !== null) { 1424 const flags = "m" + (regexMatch.groups.flags || ""); 1425 try { 1426 regex = new RegExp(regexMatch.groups.search, flags); 1427 } catch (e) {} 1428 } 1429 const matchStr = regex 1430 ? str => regex.test(str) 1431 : str => str.toLocaleLowerCase().includes(term.toLocaleLowerCase()); 1432 1433 // If there is no search, the message passes the filter. 1434 if (!term) { 1435 return true; 1436 } 1437 1438 const matched = 1439 // Look for a match in parameters. 1440 isTextInParameters(matchStr, message.parameters) || 1441 // Look for a match in location. 1442 isTextInFrame(matchStr, message.frame) || 1443 // Look for a match in net events. 1444 isTextInNetEvent(matchStr, message) || 1445 // Look for a match in stack-trace. 1446 isTextInStackTrace(matchStr, message.stacktrace) || 1447 // Look for a match in messageText. 1448 isTextInMessageText(matchStr, message.messageText) || 1449 // Look for a match in notes. 1450 isTextInNotes(matchStr, message.notes) || 1451 // Look for a match in prefix. 1452 isTextInPrefix(matchStr, message.prefix) || 1453 // Look for a match in displayName. 1454 isTextInDisplayName(matchStr, message.displayName); 1455 1456 return matched ? !exclude : exclude; 1457 } 1458 1459 /** 1460 * Returns true if given text is included in provided stack frame. 1461 */ 1462 function isTextInFrame(matchStr, frame) { 1463 if (!frame) { 1464 return false; 1465 } 1466 1467 const { functionName, line, column, source } = frame; 1468 const { short } = getSourceNames(source); 1469 const unicodeShort = getUnicodeUrlPath(short); 1470 1471 const str = `${ 1472 functionName ? functionName + " " : "" 1473 }${unicodeShort}:${line}:${column}`; 1474 return matchStr(str); 1475 } 1476 1477 /** 1478 * Returns true if given text is included in provided parameters. 1479 */ 1480 function isTextInParameters(matchStr, parameters) { 1481 if (!parameters) { 1482 return false; 1483 } 1484 1485 return parameters.some(parameter => isTextInParameter(matchStr, parameter)); 1486 } 1487 1488 /** 1489 * Returns true if given text is included in provided parameter. 1490 */ 1491 function isTextInParameter(matchStr, parameter) { 1492 const paramGrip = 1493 parameter && parameter.getGrip ? parameter.getGrip() : parameter; 1494 1495 if (paramGrip && paramGrip.class && matchStr(paramGrip.class)) { 1496 return true; 1497 } 1498 1499 const parameterType = typeof parameter; 1500 if (parameterType !== "object" && parameterType !== "undefined") { 1501 const str = paramGrip + ""; 1502 if (matchStr(str)) { 1503 return true; 1504 } 1505 } 1506 1507 const previewItems = getGripPreviewItems(paramGrip); 1508 for (const item of previewItems) { 1509 if (isTextInParameter(matchStr, item)) { 1510 return true; 1511 } 1512 } 1513 1514 if (paramGrip && paramGrip.ownProperties) { 1515 for (const [key, desc] of Object.entries(paramGrip.ownProperties)) { 1516 if (matchStr(key)) { 1517 return true; 1518 } 1519 1520 if (isTextInParameter(matchStr, getDescriptorValue(desc))) { 1521 return true; 1522 } 1523 } 1524 } 1525 1526 return false; 1527 } 1528 1529 /** 1530 * Returns true if given text is included in provided net event grip. 1531 */ 1532 function isTextInNetEvent(matchStr, { method, url } = {}) { 1533 if (!method && !url) { 1534 return false; 1535 } 1536 return matchStr(method) || matchStr(url); 1537 } 1538 1539 /** 1540 * Returns true if given text is included in provided stack trace. 1541 */ 1542 function isTextInStackTrace(matchStr, stacktrace) { 1543 if (!Array.isArray(stacktrace)) { 1544 return false; 1545 } 1546 1547 // isTextInFrame expect the properties of the frame object to be in the same 1548 // order they are rendered in the Frame component. 1549 return stacktrace.some(frame => 1550 isTextInFrame(matchStr, { 1551 functionName: 1552 frame.functionName || l10n.getStr("stacktrace.anonymousFunction"), 1553 source: frame.filename, 1554 lineNumber: frame.lineNumber, 1555 columnNumber: frame.columnNumber, 1556 }) 1557 ); 1558 } 1559 1560 /** 1561 * Returns true if given text is included in `messageText` field. 1562 */ 1563 function isTextInMessageText(matchStr, messageText) { 1564 if (!messageText) { 1565 return false; 1566 } 1567 1568 if (typeof messageText === "string") { 1569 return matchStr(messageText); 1570 } 1571 1572 const grip = 1573 messageText && messageText.getGrip ? messageText.getGrip() : messageText; 1574 if (grip && grip.type === "longString") { 1575 return matchStr(grip.initial); 1576 } 1577 1578 return true; 1579 } 1580 1581 /** 1582 * Returns true if given text is included in JS Trace display name. 1583 */ 1584 function isTextInDisplayName(matchStr, displayName) { 1585 return displayName && matchStr(displayName); 1586 } 1587 1588 /** 1589 * Returns true if given text is included in notes. 1590 */ 1591 function isTextInNotes(matchStr, notes) { 1592 if (!Array.isArray(notes)) { 1593 return false; 1594 } 1595 1596 return notes.some( 1597 note => 1598 // Look for a match in location. 1599 isTextInFrame(matchStr, note.frame) || 1600 // Look for a match in messageBody. 1601 (note.messageBody && matchStr(note.messageBody)) 1602 ); 1603 } 1604 1605 /** 1606 * Returns true if given text is included in prefix. 1607 */ 1608 function isTextInPrefix(matchStr, prefix) { 1609 if (!prefix) { 1610 return false; 1611 } 1612 1613 return matchStr(`${prefix}: `); 1614 } 1615 1616 function getDefaultFiltersCounter() { 1617 const count = DEFAULT_FILTERS.reduce((res, filter) => { 1618 res[filter] = 0; 1619 return res; 1620 }, {}); 1621 count.global = 0; 1622 return count; 1623 } 1624 1625 /** 1626 * Sort state.visibleMessages if needed. 1627 * 1628 * @param {MessageState} state 1629 * @param {boolean} sortWarningGroupMessage: set to true to sort warningGroup 1630 * messages. Default to false, as in some 1631 * situations we already take care of putting 1632 * the ids at the right position. 1633 * @param {boolean} timeStampSort: set to true to sort messages by their timestamps. 1634 */ 1635 function maybeSortVisibleMessages( 1636 state, 1637 sortWarningGroupMessage = false, 1638 timeStampSort = false 1639 ) { 1640 if (state.warningGroupsById.size > 0 && sortWarningGroupMessage) { 1641 state.visibleMessages.sort((a, b) => { 1642 const messageA = state.mutableMessagesById.get(a); 1643 const messageB = state.mutableMessagesById.get(b); 1644 1645 const warningGroupIdA = getParentWarningGroupMessageId(messageA); 1646 const warningGroupIdB = getParentWarningGroupMessageId(messageB); 1647 1648 const warningGroupA = state.mutableMessagesById.get(warningGroupIdA); 1649 const warningGroupB = state.mutableMessagesById.get(warningGroupIdB); 1650 1651 const aFirst = -1; 1652 const bFirst = 1; 1653 1654 // If both messages are in a warningGroup, or if both are not in warningGroups. 1655 if ( 1656 (warningGroupA && warningGroupB) || 1657 (!warningGroupA && !warningGroupB) 1658 ) { 1659 return getNaturalOrder(messageA, messageB); 1660 } 1661 1662 // If `a` is in a warningGroup (and `b` isn't). 1663 if (warningGroupA) { 1664 // If `b` is the warningGroup of `a`, `a` should be after `b`. 1665 if (warningGroupIdA === messageB.id) { 1666 return bFirst; 1667 } 1668 // `b` is a regular message, we place `a` before `b` if `b` came after `a`'s 1669 // warningGroup. 1670 return getNaturalOrder(warningGroupA, messageB); 1671 } 1672 1673 // If `b` is in a warningGroup (and `a` isn't). 1674 if (warningGroupB) { 1675 // If `a` is the warningGroup of `b`, `a` should be before `b`. 1676 if (warningGroupIdB === messageA.id) { 1677 return aFirst; 1678 } 1679 // `a` is a regular message, we place `a` after `b` if `a` came after `b`'s 1680 // warningGroup. 1681 return getNaturalOrder(messageA, warningGroupB); 1682 } 1683 1684 return 0; 1685 }); 1686 } 1687 1688 if (timeStampSort) { 1689 state.visibleMessages.sort((a, b) => { 1690 const messageA = state.mutableMessagesById.get(a); 1691 const messageB = state.mutableMessagesById.get(b); 1692 return getNaturalOrder(messageA, messageB); 1693 }); 1694 } 1695 } 1696 1697 /** 1698 * Returns if a given type of warning message should be grouped. 1699 * 1700 * @param {ConsoleMessage} warningGroupMessage 1701 * @param {MessageState} messagesState 1702 * @param {PrefsState} prefsState 1703 */ 1704 function shouldGroupWarningMessages( 1705 warningGroupMessage, 1706 messagesState, 1707 prefsState 1708 ) { 1709 if (!warningGroupMessage) { 1710 return false; 1711 } 1712 1713 // Only group if the preference is ON. 1714 if (!prefsState.groupSimilar) { 1715 return false; 1716 } 1717 1718 // We group warning messages if there are at least 2 messages that could go in it. 1719 const warningGroup = messagesState.warningGroupsById.get( 1720 warningGroupMessage.id 1721 ); 1722 if (!warningGroup || !Array.isArray(warningGroup)) { 1723 return false; 1724 } 1725 1726 return warningGroup.length > 1; 1727 } 1728 1729 exports.messages = messages;