Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/devtools/client/webconsole/reducers/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 52 kB image not shown  

Quelle  messages.js   Sprache: JAVA

 
/* 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 {
  isGroupType,
  isMessageNetworkError,
  l10n,
} = require("resource://devtools/client/webconsole/utils/messages.js");

const constants = require("resource://devtools/client/webconsole/constants.js");
const { DEFAULT_FILTERS, FILTERS, MESSAGE_TYPE, MESSAGE_SOURCE } = constants;

loader.lazyRequireGetter(
  this,
  "getGripPreviewItems",
  "resource://devtools/client/shared/components/reps/index.js",
  true
);
loader.lazyRequireGetter(
  this,
  "getUnicodeUrlPath",
  "resource://devtools/client/shared/unicode-url.js",
  true
);
loader.lazyRequireGetter(
  this,
  "getSourceNames",
  "resource://devtools/client/shared/source-utils.js",
  true
);
loader.lazyRequireGetter(
  this,
  [
    "areMessagesSimilar",
    "createWarningGroupMessage",
    "isWarningGroup",
    "getWarningGroupType",
    "getDescriptorValue",
    "getParentWarningGroupMessageId",
    "getNaturalOrder",
  ],
  "resource://devtools/client/webconsole/utils/messages.js",
  true
);

const {
  UPDATE_REQUEST,
} = require("resource://devtools/client/netmonitor/src/constants.js");

const {
  processNetworkUpdates,
} = require("resource://devtools/client/netmonitor/src/utils/request-utils.js");

const MessageState = overrides =>
  Object.freeze(
    Object.assign(
      {
        // List of all the messages added to the console. Unlike other properties, this Map
        // will be mutated on state changes for performance reasons.
        mutableMessagesById: new Map(),
        // Array of message ids, in chronological order. We use a dedicated property to store
        // the order (instead of relying on the order of insertion in mutableMessagesById)
        // as we might receive messages that need to be inserted at a specific index. Doing
        // so on the Map can be costly, especially when the Map holds lots of messages.
        mutableMessagesOrder: [],
        // List of elements matching the selector of CSS Warning messages(populated
        // on-demand via the UI).
        cssMessagesMatchingElements: new Map(),
        // Array of the visible messages.
        visibleMessages: [],
        // Object for the filtered messages.
        filteredMessagesCount: getDefaultFiltersCounter(),
        // List of the message ids which are opened.
        messagesUiById: [],
        // Map of the form {groupMessageId : groupArray},
        // where groupArray is the list of of all the parent groups' ids of the groupMessageId.
        // This handles console API groups.
        groupsById: new Map(),
        // Message id of the current console API group (no corresponding console.groupEnd yet).
        currentGroup: null,
        // This group handles "warning groups" (Content Blocking, CORS, CSP, …)
        warningGroupsById: new Map(),
        // Array of fronts to release (i.e. fronts logged in removed messages).
        // This array *should not* be consumed by any UI component.
        frontsToRelease: [],
        // Map of the form {messageId : numberOfRepeat}
        repeatById: {},
        // Map of the form {messageId : networkInformation}
        // `networkInformation` holds request, response, totalTime, ...
        networkMessagesUpdateById: {},
        // Id of the last messages that was added.
        lastMessageId: null,
        // List of the message ids which are disabled
        disabledMessagesById: [],
      },
      overrides
    )
  );

function cloneState(state) {
  return {
    visibleMessages: [...state.visibleMessages],
    filteredMessagesCount: { ...state.filteredMessagesCount },
    messagesUiById: [...state.messagesUiById],
    cssMessagesMatchingElements: new Map(state.cssMessagesMatchingElements),
    groupsById: new Map(state.groupsById),
    frontsToRelease: [...state.frontsToRelease],
    repeatById: { ...state.repeatById },
    networkMessagesUpdateById: { ...state.networkMessagesUpdateById },
    warningGroupsById: new Map(state.warningGroupsById),
    // no need to mutate the properties below as they're not directly triggering re-render
    mutableMessagesById: state.mutableMessagesById,
    mutableMessagesOrder: state.mutableMessagesOrder,
    currentGroup: state.currentGroup,
    lastMessageId: state.lastMessageId,
    disabledMessagesById: [...state.disabledMessagesById],
  };
}

/**
 * Add a console message to the state.
 *
 * @param {ConsoleMessage} newMessage: The message to add to the state.
 * @param {MessageState} state: The message state ( = managed by this reducer).
 * @param {FiltersState} filtersState: The filters state.
 * @param {PrefsState} prefsState: The preferences state.
 * @param {UiState} uiState: The ui state.
 * @returns {MessageState} a new messages state.
 */

// eslint-disable-next-line complexity
function addMessage(newMessage, state, filtersState, prefsState, uiState) {
  const { mutableMessagesById, groupsById, repeatById } = state;

  if (newMessage.type === constants.MESSAGE_TYPE.NAVIGATION_MARKER) {
    // We set the state's currentGroup property to null after navigating
    state.currentGroup = null;
  }
  const { currentGroup } = 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.
    state.currentGroup = getNewCurrentGroup(currentGroup, groupsById);
    return state;
  }

  const lastMessage = mutableMessagesById.get(state.lastMessageId);
  // It can happen that the new message was actually emitted earlier than the last message,
  // which means we need to insert it at the right position.
  const isUnsorted =
    lastMessage && lastMessage.timeStamp > newMessage.timeStamp;

  if (lastMessage && mutableMessagesById.size > 0) {
    if (
      lastMessage.groupId === currentGroup &&
      areMessagesSimilar(lastMessage, newMessage)
    ) {
      state.repeatById[lastMessage.id] = (repeatById[lastMessage.id] || 1) + 1;
      return state;
    }
  }

  // Store the id of the message as being the last one being added.
  if (!isUnsorted) {
    state.lastMessageId = newMessage.id;
  }

  // Add the new message with a reference to the parent group.
  const parentGroups = getParentGroups(currentGroup, groupsById);
  if (!isWarningGroup(newMessage)) {
    newMessage.groupId = currentGroup;
    newMessage.indent = parentGroups.length;
  }

  // Check if the current message could be placed in a Warning Group.
  // This needs to be done before setting the new message in mutableMessagesById so we have a
  // proper message.
  const warningGroupType = getWarningGroupType(newMessage);

  // If the preference for warning grouping is true, and the new message could be in a
  // warning group.
  if (prefsState.groupWarnings && warningGroupType !== null) {
    const warningGroupMessageId = getParentWarningGroupMessageId(newMessage);

    // If there's no warning group for the type/innerWindowID yet
    if (!state.mutableMessagesById.has(warningGroupMessageId)) {
      // We create it and add it to the store.
      const groupMessage = createWarningGroupMessage(
        warningGroupMessageId,
        warningGroupType,
        newMessage
      );
      state = addMessage(
        groupMessage,
        state,
        filtersState,
        prefsState,
        uiState
      );
    }

    // We add the new message to the appropriate warningGroup.
    state.warningGroupsById.get(warningGroupMessageId).push(newMessage.id);

    // If the warningGroup message is not visible yet, but should be.
    if (
      !state.visibleMessages.includes(warningGroupMessageId) &&
      getMessageVisibility(
        state.mutableMessagesById.get(warningGroupMessageId),
        {
          messagesState: state,
          filtersState,
          prefsState,
          uiState,
        }
      ).visible
    ) {
      // Then we put it in the visibleMessages properties, at the position of the first
      // warning message inside the warningGroup.
      // 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.mutableMessagesById.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
        );
      }
    }
  }

  // If we're creating a warningGroup, we init the array for its children.
  if (isWarningGroup(newMessage)) {
    state.warningGroupsById.set(newMessage.id, []);
  }

  const addedMessage = Object.freeze(newMessage);

  // If the new message isn't the "oldest" one, then we need to insert it at the right
  // position in the message map.
  if (isUnsorted) {
    let newMessageIndex = 0;
    // This is can be on a hot path, so we're not using `findIndex`, which could be slow.
    // Furthermore, there's a high chance the message beed to be inserted somewhere at the
    // end of the list, so we loop through mutableMessagesOrder in reverse order.
    for (let i = state.mutableMessagesOrder.length - 1; i >= 0; i--) {
      const message = state.mutableMessagesById.get(
        state.mutableMessagesOrder[i]
      );
      if (message.timeStamp <= addedMessage.timeStamp) {
        newMessageIndex = i + 1;
        break;
      }
    }

    state.mutableMessagesOrder.splice(newMessageIndex, 0, addedMessage.id);
  } else {
    state.mutableMessagesOrder.push(addedMessage.id);
  }
  state.mutableMessagesById.set(addedMessage.id, addedMessage);

  if (newMessage.type === "trace") {
    // We want the stacktrace to be open by default.
    state.messagesUiById.push(newMessage.id);
  } else if (isGroupType(newMessage.type)) {
    state.currentGroup = newMessage.id;
    state.groupsById.set(newMessage.id, parentGroups);

    if (newMessage.type === constants.MESSAGE_TYPE.START_GROUP) {
      // We want the group to be open by default.
      state.messagesUiById.push(newMessage.id);
    }
  }

  const { visible, cause } = getMessageVisibility(addedMessage, {
    messagesState: state,
    filtersState,
    prefsState,
    uiState,
  });

  if (visible) {
    // If the message is part of a visible warning group, we want to add it after the last
    // visible message of the group.
    const warningGroupId = getParentWarningGroupMessageId(newMessage);
    if (warningGroupId && state.visibleMessages.includes(warningGroupId)) {
      // Defaults to the warning group message.
      let index = state.visibleMessages.indexOf(warningGroupId);

      // We loop backward through the warning group's messages to get the latest visible
      // messages in it.
      const messagesInWarningGroup =
        state.warningGroupsById.get(warningGroupId);
      for (let i = messagesInWarningGroup.length - 1; i >= 0; i--) {
        const idx = state.visibleMessages.indexOf(messagesInWarningGroup[i]);
        if (idx > -1) {
          index = idx;
          break;
        }
      }
      // Inserts the new warning message at the wanted location "in" the warning group.
      state.visibleMessages.splice(index + 1, 0, newMessage.id);
    } else if (isUnsorted) {
      // If the new message wasn't the "oldest" one, then we need to insert its id at
      // the right position in the array.
      // This is can be on a hot path, so we're not using `findIndex`, which could be slow.
      // Furthermore, there's a high chance the message beed to be inserted somewhere at the
      // end of the list, so we loop through visibleMessages in reverse order.
      let index = 0;
      for (let i = state.visibleMessages.length - 1; i >= 0; i--) {
        const id = state.visibleMessages[i];
        if (
          state.mutableMessagesById.get(id).timeStamp <= newMessage.timeStamp
        ) {
          index = i + 1;
          break;
        }
      }
      state.visibleMessages.splice(index, 0, newMessage.id);
    } else {
      state.visibleMessages.push(newMessage.id);
    }
    maybeSortVisibleMessages(state, false);
  } else if (DEFAULT_FILTERS.includes(cause)) {
    state.filteredMessagesCount.global++;
    state.filteredMessagesCount[cause]++;
  }

  // Append received network-data also into networkMessagesUpdateById
  // that is responsible for collecting (lazy loaded) HTTP payload data.
  if (newMessage.source == "network") {
    state.networkMessagesUpdateById[newMessage.actor] = newMessage;
  }

  return state;
}

// eslint-disable-next-line complexity
function messages(
  state = MessageState(),
  action,
  filtersState,
  prefsState,
  uiState
) {
  const {
    mutableMessagesById,
    cssMessagesMatchingElements,
    messagesUiById,
    networkMessagesUpdateById,
    groupsById,
    visibleMessages,
    disabledMessagesById,
  } = state;

  const { logLimit } = prefsState;

  let newState;
  switch (action.type) {
    case constants.MESSAGES_ADD:
      // If the action holds more messages than the log limit, we can preemptively remove
      // messages that will never be rendered.
      const batchHasMoreMessagesThanLogLimit =
        action.messages.length > logLimit;
      const list = batchHasMoreMessagesThanLogLimit ? [] : action.messages;
      if (batchHasMoreMessagesThanLogLimit) {
        let prunableCount = 0;
        let lastMessage = null;
        for (let i = action.messages.length - 1; i >= 0; i--) {
          const message = action.messages[i];
          if (
            !message.groupId &&
            !isGroupType(message.type) &&
            message.type !== MESSAGE_TYPE.END_GROUP
          ) {
            const messagesSimilar = areMessagesSimilar(lastMessage, message);
            if (!messagesSimilar) {
              prunableCount++;
            }
            // Once we've added the max number of messages that can be added, stop.
            // Except for repeated messages, where we keep adding over the limit.
            if (prunableCount <= logLimit || messagesSimilar) {
              list.unshift(action.messages[i]);
            } else {
              break;
            }
          } else {
            list.unshift(message);
          }
          lastMessage = message;
        }
      }

      newState = cloneState(state);
      for (const message of list) {
        newState = addMessage(
          message,
          newState,
          filtersState,
          prefsState,
          uiState
        );
      }

      return limitTopLevelMessageCount(newState, logLimit);

    case constants.MESSAGES_CLEAR:
      const frontsToRelease = [];
      for (const message of state.mutableMessagesById.values()) {
        // We want to minimize time spent in reducer as much as we can, so we're using
        // prototype.push.apply here as it seems faster than other solutions (e.g. the
        // spread operator, Array#concat, …)
        Array.prototype.push.apply(
          frontsToRelease,
          getAllFrontsInMessage(message)
        );
      }
      return MessageState({
        // Store all actors from removed messages. This array is used by
        // `releaseActorsEnhancer` to release all of those backend actors.
        frontsToRelease,
      });

    case constants.PRIVATE_MESSAGES_CLEAR: {
      const removedIds = new Set();
      for (const [id, message] of mutableMessagesById) {
        if (message.private === true) {
          removedIds.add(id);
        }
      }

      // If there's no private messages, there's no need to change the state.
      if (removedIds.size === 0) {
        return state;
      }

      return removeMessagesFromState(
        {
          ...state,
        },
        removedIds
      );
    }

    case constants.TARGET_MESSAGES_REMOVE: {
      const removedIds = new Set();
      for (const [id, message] of mutableMessagesById) {
        // Remove message from the target but not evaluations and their results, so
        // 1. we're consistent with the filtering behavior, i.e. we never hide those
        // 2. when switching mode from multiprocess to parent process and back to multi,
        //    if we'd clear evaluations we wouldn't have a way to get them back, unlike
        //    log messages and errors, which are still available in the server caches).
        if (
          message.targetFront == action.targetFront &&
          message.type !== MESSAGE_TYPE.COMMAND &&
          message.type !== MESSAGE_TYPE.RESULT
        ) {
          removedIds.add(id);
        }
      }

      return removeMessagesFromState(
        {
          ...state,
        },
        removedIds
      );
    }

    case constants.MESSAGES_DISABLE:
      return {
        ...state,
        disabledMessagesById: [...disabledMessagesById, ...action.ids],
      };

    case constants.MESSAGE_OPEN:
      const openState = { ...state };
      openState.messagesUiById = [...messagesUiById, action.id];
      const currMessage = mutableMessagesById.get(action.id);

      // If the message is a console.group/groupCollapsed or a warning group.
      if (isGroupType(currMessage.type) || isWarningGroup(currMessage)) {
        // We want to make its children visible
        const messagesToShow = [];
        for (const id of state.mutableMessagesOrder) {
          const message = mutableMessagesById.get(id);
          if (
            !visibleMessages.includes(message.id) &&
            ((isWarningGroup(currMessage) && !!getWarningGroupType(message)) ||
              (isGroupType(currMessage.type) &&
                getParentGroups(message.groupId, groupsById).includes(
                  action.id
                ))) &&
            getMessageVisibility(message, {
              messagesState: openState,
              filtersState,
              prefsState,
              uiState,
              // We want to check if the message is in an open group
              // only if it is not a direct child of the group we're opening.
              checkGroup: message.groupId !== action.id,
            }).visible
          ) {
            messagesToShow.push(id);
          }
        }

        // We can then insert the messages ids right after the one of the group.
        const insertIndex = visibleMessages.indexOf(action.id) + 1;
        openState.visibleMessages = [
          ...visibleMessages.slice(0, insertIndex),
          ...messagesToShow,
          ...visibleMessages.slice(insertIndex),
        ];
      }
      return openState;

    case constants.MESSAGE_CLOSE:
      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(mutableMessagesById.get(messageId).type)) {
        // Hide all its children, unless they're in a warningGroup.
        closeState.visibleMessages = visibleMessages.filter((id, i, arr) => {
          const message = mutableMessagesById.get(id);
          const warningGroupMessage = mutableMessagesById.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(mutableMessagesById.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;

    case constants.CSS_MESSAGE_ADD_MATCHING_ELEMENTS:
      return {
        ...state,
        cssMessagesMatchingElements: new Map(cssMessagesMatchingElements).set(
          action.id,
          action.elements
        ),
      };

    case constants.NETWORK_MESSAGES_UPDATE:
      const updatedState = {
        ...state,
        networkMessagesUpdateById: {
          ...networkMessagesUpdateById,
        },
      };
      let hasNetworkError = null;
      for (const message of action.messages) {
        const { id } = message;
        updatedState.mutableMessagesById.set(id, message);
        updatedState.networkMessagesUpdateById[id] = {
          ...(updatedState.networkMessagesUpdateById[id] || {}),
          ...message,
        };

        if (isMessageNetworkError(message)) {
          hasNetworkError = true;
        }
      }

      // If the message updates contained a network error, then we may have to display it.
      if (hasNetworkError) {
        return setVisibleMessages({
          messagesState: updatedState,
          filtersState,
          prefsState,
          uiState,
        });
      }

      return updatedState;

    case UPDATE_REQUEST:
    case constants.NETWORK_UPDATES_REQUEST: {
      newState = {
        ...state,
        networkMessagesUpdateById: {
          ...networkMessagesUpdateById,
        },
      };

      // Netmonitor's UPDATE_REQUEST action comes for only one request
      const updates =
        action.type == UPDATE_REQUEST
          ? [{ id: action.id, data: action.data }]
          : action.updates;
      for (const { id, data } of updates) {
        const request = newState.networkMessagesUpdateById[id];
        if (!request) {
          continue;
        }
        newState.networkMessagesUpdateById[id] = {
          ...request,
          ...processNetworkUpdates(data),
        };
      }
      return newState;
    }

    case constants.FRONTS_TO_RELEASE_CLEAR:
      return {
        ...state,
        frontsToRelease: [],
      };

    case constants.WARNING_GROUPS_TOGGLE:
      // There's no warningGroups, and the pref was set to false,
      // we don't need to do anything.
      if (!prefsState.groupWarnings && state.warningGroupsById.size === 0) {
        return state;
      }

      let needSort = false;
      for (const msgId of state.mutableMessagesOrder) {
        const message = state.mutableMessagesById.get(msgId);
        const warningGroupType = getWarningGroupType(message);
        if (warningGroupType) {
          const warningGroupMessageId = getParentWarningGroupMessageId(message);

          // If there's no warning group for the type/innerWindowID yet.
          if (!state.mutableMessagesById.has(warningGroupMessageId)) {
            // We create it and add it to the store.
            const groupMessage = createWarningGroupMessage(
              warningGroupMessageId,
              warningGroupType,
              message
            );
            state = addMessage(
              groupMessage,
              state,
              filtersState,
              prefsState,
              uiState
            );
          }

          // We add the new message to the appropriate warningGroup.
          const warningGroup = state.warningGroupsById.get(
            warningGroupMessageId
          );
          if (warningGroup && !warningGroup.includes(msgId)) {
            warningGroup.push(msgId);
          }

          needSort = true;
        }
      }

      // If we don't have any warning messages that could be in a group, we don't do
      // anything.
      if (!needSort) {
        return state;
      }

      return setVisibleMessages({
        messagesState: state,
        filtersState,
        prefsState,
        uiState,
        // If the user disabled warning groups, we want the messages to be sorted by their
        // timestamps.
        forceTimestampSort: !prefsState.groupWarnings,
      });

    case constants.MESSAGE_REMOVE:
      return removeMessagesFromState(
        {
          ...state,
        },
        new Set([action.id])
      );

    case constants.FILTER_TOGGLE:
    case constants.FILTER_TEXT_SET:
    case constants.FILTERS_CLEAR:
    case constants.DEFAULT_FILTERS_RESET:
      return setVisibleMessages({
        messagesState: state,
        filtersState,
        prefsState,
        uiState,
      });
  }

  return state;
}

function setVisibleMessages({
  messagesState,
  filtersState,
  prefsState,
  uiState,
  forceTimestampSort = false,
}) {
  const {
    mutableMessagesById,
    mutableMessagesOrder,
    visibleMessages,
    messagesUiById,
  } = messagesState;

  const messagesToShow = new Set();
  const matchedGroups = new Set();
  const filtered = getDefaultFiltersCounter();

  mutableMessagesOrder.forEach(msgId => {
    const message = mutableMessagesById.get(msgId);
    const groupParentId = message.groupId;
    let hasMatchedAncestor = false;
    const ancestors = [];

    if (groupParentId) {
      let ancestorId = groupParentId;

      // we track the message's ancestors and their state
      while (ancestorId) {
        ancestors.push({
          ancestorId,
          matchedFilters: matchedGroups.has(ancestorId),
          isOpen: messagesUiById.includes(ancestorId),
          isCurrentlyVisible: visibleMessages.includes(ancestorId),
        });
        if (!hasMatchedAncestor && matchedGroups.has(ancestorId)) {
          hasMatchedAncestor = true;
        }
        ancestorId = mutableMessagesById.get(ancestorId).groupId;
      }
    }

    const { visible, cause } = getMessageVisibility(message, {
      messagesState,
      filtersState,
      prefsState,
      uiState,
      hasMatchedAncestor,
    });

    // if the message is not visible but passes the search filters, we show its visible ancestors
    if (!visible && passSearchFilters(message, filtersState)) {
      const tmp = [];
      ancestors.forEach(msg => {
        if (msg.isCurrentlyVisible) {
          tmp.push(msg.ancestorId);
        }
      });
      tmp.reverse().forEach(id => {
        messagesToShow.add(id);
      });
    }
    if (visible) {
      // if the visible message is a child of a group, we add its ancestors to the visible messages
      if (groupParentId) {
        // We need to reverse the visibleAncestors array to show the groups in the correct order
        ancestors.reverse().forEach(msg => {
          messagesToShow.add(msg.ancestorId);
        });
      }

      // we keep track of matched startGroup and startGroupCollapsed messages so we don't filter their children
      if (
        message.type === "startGroup" ||
        message.type === "startGroupCollapsed"
      ) {
        matchedGroups.add(msgId);
      }

      messagesToShow.add(msgId);
    } else if (DEFAULT_FILTERS.includes(cause)) {
      filtered.global = filtered.global + 1;
      filtered[cause] = filtered[cause] + 1;
    }
  });

  const newState = {
    ...messagesState,
    visibleMessages: Array.from(messagesToShow),
    filteredMessagesCount: filtered,
  };

  maybeSortVisibleMessages(
    newState,
    // Only sort for warningGroups if the feature is enabled
    prefsState.groupWarnings,
    forceTimestampSort
  );

  return newState;
}

/**
 * Returns the new current group id given the previous current group and the groupsById
 * state property.
 *
 * @param {String} currentGroup: id of the current group
 * @param {Map} groupsById
 * @param {Array} ignoredIds: An array of ids which can't be the new current group.
 * @returns {String|null} The new current group id, or null if there isn't one.
 */

function getNewCurrentGroup(currentGroup, groupsById, ignoredIds = new Set()) {
  if (!currentGroup) {
    return null;
  }

  // Retrieve the parent groups of the current group.
  const parents = groupsById.get(currentGroup);

  // If there's at least one parent, make the first one the new currentGroup.
  if (Array.isArray(parents) && parents.length) {
    // If the found group must be ignored, let's search for its parent.
    if (ignoredIds.has(parents[0])) {
      return getNewCurrentGroup(parents[0], groupsById, ignoredIds);
    }

    return parents[0];
  }

  return null;
}

function getParentGroups(currentGroup, groupsById) {
  let groups = [];
  if (currentGroup) {
    // If there is a current group, we add it as a parent
    groups = [currentGroup];

    // As well as all its parents, if it has some.
    const parentGroups = groupsById.get(currentGroup);
    if (Array.isArray(parentGroups) && parentGroups.length) {
      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.mutableMessagesById.size
      : getToplevelMessageCount(newState);

  if (topLevelCount <= logLimit) {
    return newState;
  }

  const removedMessagesId = new Set();

  let cleaningGroup = false;
  for (const id of newState.mutableMessagesOrder) {
    const message = newState.mutableMessagesById.get(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;
    }

    // If we're not cleaning a group and the message count is below the logLimit,
    // we exit the loop.
    if (cleaningGroup === false && topLevelCount <= logLimit) {
      break;
    }

    // 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 && newState.groupsById.has(id)) {
      cleaningGroup = true;
    }

    if (!message.groupId) {
      topLevelCount--;
    }

    removedMessagesId.add(id);
  }

  return removeMessagesFromState(newState, removedMessagesId);
}

/**
 * Clean the properties for a given state object and an array of removed messages ids.
 * Be aware that this function MUTATE the `state` argument.
 *
 * @param {MessageState} state
 * @param {Set} removedMessagesIds
 * @returns {MessageState}
 */

function removeMessagesFromState(state, removedMessagesIds) {
  if (removedMessagesIds.size === 0) {
    return state;
  }

  const frontsToRelease = [];
  const visibleMessages = [...state.visibleMessages];
  removedMessagesIds.forEach(id => {
    const index = visibleMessages.indexOf(id);
    if (index > -1) {
      visibleMessages.splice(index, 1);
    }

    // We want to minimize time spent in reducer as much as we can, so we're using
    // prototype.push.apply here as it seems faster than other solutions (e.g. the
    // spread operator, Array#concat, …)
    Array.prototype.push.apply(
      frontsToRelease,
      getAllFrontsInMessage(state.mutableMessagesById.get(id))
    );
  });

  if (state.visibleMessages.length > visibleMessages.length) {
    state.visibleMessages = visibleMessages;
  }

  if (frontsToRelease.length) {
    state.frontsToRelease = state.frontsToRelease.concat(frontsToRelease);
  }

  const isInRemovedId = id => removedMessagesIds.has(id);
  const mapHasRemovedIdKey = map => {
    for (const id of removedMessagesIds) {
      if (map.has(id)) {
        return true;
      }
    }
    return false;
  };
  const objectHasRemovedIdKey = obj =>
    Object.keys(obj).findIndex(isInRemovedId) !== -1;

  const cleanUpMap = map => {
    const clonedMap = new Map(map);
    removedMessagesIds.forEach(id => clonedMap.delete(id));
    return clonedMap;
  };
  const cleanUpObject = object =>
    [...Object.entries(object)].reduce((res, [id, value]) => {
      if (!isInRemovedId(id)) {
        res[id] = value;
      }
      return res;
    }, {});

  removedMessagesIds.forEach(id => {
    state.mutableMessagesById.delete(id);

    state.mutableMessagesOrder.splice(
      state.mutableMessagesOrder.indexOf(id),
      1
    );
  });

  if (state.disabledMessagesById.find(isInRemovedId)) {
    state.disabledMessagesById = state.disabledMessagesById.filter(
      id => !isInRemovedId(id)
    );
  }

  if (state.messagesUiById.find(isInRemovedId)) {
    state.messagesUiById = state.messagesUiById.filter(
      id => !isInRemovedId(id)
    );
  }

  if (isInRemovedId(state.currentGroup)) {
    state.currentGroup = getNewCurrentGroup(
      state.currentGroup,
      state.groupsById,
      removedMessagesIds
    );
  }

  if (mapHasRemovedIdKey(state.cssMessagesMatchingElements)) {
    state.cssMessagesMatchingElements = cleanUpMap(
      state.cssMessagesMatchingElements
    );
  }
  if (mapHasRemovedIdKey(state.groupsById)) {
    state.groupsById = cleanUpMap(state.groupsById);
  }

  if (objectHasRemovedIdKey(state.repeatById)) {
    state.repeatById = cleanUpObject(state.repeatById);
  }

  if (objectHasRemovedIdKey(state.networkMessagesUpdateById)) {
    state.networkMessagesUpdateById = cleanUpObject(
      state.networkMessagesUpdateById
    );
  }

  return state;
}

/**
 * Get an array of all the fronts logged in a specific message.
 *
 * @param {Message} message: The message to get actors from.
 * @return {Array<ObjectFront|LongStringFront>} An array containing all the fronts logged
 *                                              in a message.
 */

function getAllFrontsInMessage(message) {
  const { parameters, messageText } = message;

  const fronts = [];
  const isFront = p => p && typeof p.release === "function";

  if (Array.isArray(parameters)) {
    message.parameters.forEach(parameter => {
      if (isFront(parameter)) {
        fronts.push(parameter);
      }
    });
  }

  if (isFront(messageText)) {
    fronts.push(messageText);
  }

  return fronts;
}

/**
 * Returns total count of top level messages (those which are not
 * within a group).
 */

function getToplevelMessageCount(state) {
  let count = 0;
  state.mutableMessagesById.forEach(message => {
    if (!message.groupId) {
      count++;
    }
  });
  return count;
}

/**
 * Check if a message should be visible in the console output, and if not, what
 * causes it to be hidden.
 * @param {Message} message: The message to check
 * @param {Object} option: An option object of the following shape:
 *                   - {MessageState} messagesState: The current messages state
 *                   - {FilterState} filtersState: The current filters state
 *                   - {PrefsState} prefsState: The current preferences state
 *                   - {UiState} uiState: The current ui state
 *                   - {Boolean} checkGroup: Set to false to not check if a message should
 *                                 be visible because it is in a console.group.
 *                   - {Boolean} checkParentWarningGroupVisibility: Set to false to not
 *                                 check if a message should be visible because it is in a
 *                                 warningGroup and the warningGroup is visible.
 *                   - {Boolean} hasMatchedAncestor: Set to true if message is part of a
 *                                 group that has been set to visible
 *
 * @return {Object} An object of the following form:
 *         - visible {Boolean}: true if the message should be visible
 *         - cause {String}: if visible is false, what causes the message to be hidden.
 */

// eslint-disable-next-line complexity
function getMessageVisibility(
  message,
  {
    messagesState,
    filtersState,
    prefsState,
    uiState,
    checkGroup = true,
    checkParentWarningGroupVisibility = true,
    hasMatchedAncestor = false,
  }
) {
  const warningGroupMessageId = getParentWarningGroupMessageId(message);
  const parentWarningGroupMessage = messagesState.mutableMessagesById.get(
    warningGroupMessageId
  );

  // 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(
      parentWarningGroupMessage,
      messagesState,
      prefsState
    )
  ) {
    return {
      visible: false,
      cause: "closedGroup",
    };
  }

  // If the message is a warningGroup, check if it should be displayed.
  if (isWarningGroup(message)) {
    if (!shouldGroupWarningMessages(message, messagesState, prefsState)) {
      return {
        visible: false,
        cause: "warningGroupHeuristicNotMet",
      };
    }

    // Hide a warningGroup if the warning filter is off.
    if (!filtersState[FILTERS.WARN]) {
      // We don't include any cause as we don't want that message to be reflected in the
      // message count.
      return {
        visible: false,
      };
    }

    // Display a warningGroup if at least one of its message will be visible.
    const childrenMessages = messagesState.warningGroupsById.get(message.id);
    const hasVisibleChild =
      childrenMessages &&
      childrenMessages.some(id => {
        const child = messagesState.mutableMessagesById.get(id);
        if (!child) {
          return false;
        }

        const { visible, cause } = getMessageVisibility(child, {
          messagesState,
          filtersState,
          prefsState,
          uiState,
          checkParentWarningGroupVisibility: false,
        });
        return visible && cause !== "visibleWarningGroup";
      });

    if (hasVisibleChild) {
      return {
        visible: true,
        cause: "visibleChild",
      };
    }
  }

  // Do not display the message if it can be in a warningGroup, and the group is
  // displayed but collapsed.
  if (
    parentWarningGroupMessage &&
    shouldGroupWarningMessages(
      parentWarningGroupMessage,
      messagesState,
      prefsState
    ) &&
    !messagesState.messagesUiById.includes(warningGroupMessageId)
  ) {
    return {
      visible: false,
      cause: "closedWarningGroup",
    };
  }

  // Display a message if it is in a warningGroup that is visible. We don't check the
  // warningGroup visibility if `checkParentWarningGroupVisibility` is false, because
  // it means we're checking the warningGroup visibility based on the visibility of its
  // children, which would cause an infinite loop.
  const parentVisibility =
    parentWarningGroupMessage && checkParentWarningGroupVisibility
      ? getMessageVisibility(parentWarningGroupMessage, {
          messagesState,
          filtersState,
          prefsState,
          uiState,
          checkGroup,
          checkParentWarningGroupVisibility,
        })
      : null;
  if (
    parentVisibility &&
    parentVisibility.visible &&
    parentVisibility.cause !== "visibleChild"
  ) {
    return {
      visible: true,
      cause: "visibleWarningGroup",
    };
  }

  // Some messages can't be filtered out (e.g. groups).
  // So, always return visible: true for those.
  if (isUnfilterable(message)) {
    return {
      visible: true,
    };
  }

  // Let's check all level filters (error, warn, log, …) and return visible: false
  // and the message level as a cause if the function returns false.
  if (!passLevelFilters(message, filtersState)) {
    return {
      visible: false,
      cause: message.level,
    };
  }

  if (!passCssFilters(message, filtersState)) {
    return {
      visible: false,
      cause: FILTERS.CSS,
    };
  }

  if (!passNetworkFilter(message, filtersState)) {
    return {
      visible: false,
      cause: FILTERS.NET,
    };
  }

  if (!passXhrFilter(message, filtersState)) {
    return {
      visible: false,
      cause: FILTERS.NETXHR,
    };
  }

  // This should always be the last check, or we might report that a message was hidden
  // because of text search, while it may be hidden because its category is disabled.
  // Do not check for search filters if it is part of a group and one of its ancestor
  // has matched the current search filters and set to visible
  if (!hasMatchedAncestor && !passSearchFilters(message, filtersState)) {
    return {
      visible: false,
      cause: FILTERS.TEXT,
    };
  }

  return {
    visible: true,
  };
}

function isUnfilterable(message) {
  return [
    MESSAGE_TYPE.COMMAND,
    MESSAGE_TYPE.RESULT,
    MESSAGE_TYPE.NAVIGATION_MARKER,
  ].includes(message.type);
}

function isInOpenedGroup(message, groupsById, messagesUI) {
  return (
    !message.groupId ||
    (!isGroupClosed(message.groupId, messagesUI) &&
      !hasClosedParentGroup(groupsById.get(message.groupId), messagesUI))
  );
}

function hasClosedParentGroup(group, messagesUI) {
  return group.some(groupId => isGroupClosed(groupId, messagesUI));
}

function isGroupClosed(groupId, messagesUI) {
  return messagesUI.includes(groupId) === false;
}

/**
 * Returns true if the message shouldn't be hidden because of the network filter state.
 *
 * @param {Object} message - The message to check the filter against.
 * @param {FilterState} filters - redux "filters" state.
 * @returns {Boolean}
 */

function passNetworkFilter(message, filters) {
  // The message passes the filter if it is not a network message,
  // or if it is an xhr one,
  // or if the network filter is on.
  return (
    message.source !== MESSAGE_SOURCE.NETWORK ||
    message.isXHR === true ||
    filters[FILTERS.NET] === true ||
    (filters[FILTERS.ERROR] && isMessageNetworkError(message))
  );
}

/**
 * Returns true if the message shouldn't be hidden because of the xhr filter state.
 *
 * @param {Object} message - The message to check the filter against.
 * @param {FilterState} filters - redux "filters" state.
 * @returns {Boolean}
 */

function passXhrFilter(message, filters) {
  // The message passes the filter if it is not a network message,
  // or if it is a non-xhr one,
  // or if the xhr filter is on.
  return (
    message.source !== MESSAGE_SOURCE.NETWORK ||
    message.isXHR === false ||
    filters[FILTERS.NETXHR] === true ||
    (filters[FILTERS.ERROR] && isMessageNetworkError(message))
  );
}

/**
 * Returns true if the message shouldn't be hidden because of levels filter state.
 *
 * @param {Object} message - The message to check the filter against.
 * @param {FilterState} filters - redux "filters" state.
 * @returns {Boolean}
 */

function passLevelFilters(message, filters) {
  // The message passes the filter if it is not a console call,
  // or if its level matches the state of the corresponding filter.
  return (
    (message.source !== MESSAGE_SOURCE.CONSOLE_API &&
      message.source !== MESSAGE_SOURCE.JAVASCRIPT) ||
    filters[message.level] === true ||
    (filters[FILTERS.ERROR] && isMessageNetworkError(message))
  );
}

/**
 * Returns true if the message shouldn't be hidden because of the CSS filter state.
 *
 * @param {Object} message - The message to check the filter against.
 * @param {FilterState} filters - redux "filters" state.
 * @returns {Boolean}
 */

function passCssFilters(message, filters) {
  // The message passes the filter if it is not a CSS message,
  // or if the CSS filter is on.
  return message.source !== MESSAGE_SOURCE.CSS || filters.css === true;
}

/**
 * Returns true if the message shouldn't be hidden because of search filter state.
 *
 * @param {Object} message - The message to check the filter against.
 * @param {FilterState} filters - redux "filters" state.
 * @returns {Boolean}
 */

function passSearchFilters(message, filters) {
  const trimmed = (filters.text || "").trim();

  // "-"-prefix switched to exclude mode
  const exclude = trimmed.startsWith("-");
  const term = exclude ? trimmed.slice(1) : trimmed;

  // This regex matches a very basic regex with an optional i flag
  const regexMatch = /^\/(?<search>.+)\/(?<flags>i)?$/.exec(term);
  let regex;
  if (regexMatch !== null) {
    const flags = "m" + (regexMatch.groups.flags || "");
    try {
      regex = new RegExp(regexMatch.groups.search, flags);
    } catch (e) {}
  }
  const matchStr = regex
    ? str => regex.test(str)
    : str => str.toLocaleLowerCase().includes(term.toLocaleLowerCase());

  // If there is no search, the message passes the filter.
  if (!term) {
    return true;
  }

  const matched =
    // Look for a match in parameters.
    isTextInParameters(matchStr, message.parameters) ||
    // Look for a match in location.
    isTextInFrame(matchStr, message.frame) ||
    // Look for a match in net events.
    isTextInNetEvent(matchStr, message) ||
    // Look for a match in stack-trace.
    isTextInStackTrace(matchStr, message.stacktrace) ||
    // Look for a match in messageText.
    isTextInMessageText(matchStr, message.messageText) ||
    // Look for a match in notes.
    isTextInNotes(matchStr, message.notes) ||
    // Look for a match in prefix.
    isTextInPrefix(matchStr, message.prefix) ||
    // Look for a match in displayName.
    isTextInDisplayName(matchStr, message.displayName);

  return matched ? !exclude : exclude;
}

/**
 * Returns true if given text is included in provided stack frame.
 */

function isTextInFrame(matchStr, frame) {
  if (!frame) {
    return false;
  }

  const { functionName, line, column, source } = frame;
  const { short } = getSourceNames(source);
  const unicodeShort = getUnicodeUrlPath(short);

  const str = `${
    functionName ? functionName + " " : ""
  }${unicodeShort}:${line}:${column}`;
  return matchStr(str);
}

/**
 * Returns true if given text is included in provided parameters.
 */

function isTextInParameters(matchStr, parameters) {
  if (!parameters) {
    return false;
  }

  return parameters.some(parameter => isTextInParameter(matchStr, parameter));
}

/**
 * Returns true if given text is included in provided parameter.
 */

function isTextInParameter(matchStr, parameter) {
  const paramGrip =
    parameter && parameter.getGrip ? parameter.getGrip() : parameter;

  if (paramGrip && paramGrip.class && matchStr(paramGrip.class)) {
    return true;
  }

  const parameterType = typeof parameter;
  if (parameterType !== "object" && parameterType !== "undefined") {
    const str = paramGrip + "";
    if (matchStr(str)) {
      return true;
    }
  }

  const previewItems = getGripPreviewItems(paramGrip);
  for (const item of previewItems) {
    if (isTextInParameter(matchStr, item)) {
      return true;
    }
  }

  if (paramGrip && paramGrip.ownProperties) {
    for (const [key, desc] of Object.entries(paramGrip.ownProperties)) {
      if (matchStr(key)) {
        return true;
      }

      if (isTextInParameter(matchStr, getDescriptorValue(desc))) {
        return true;
      }
    }
  }

  return false;
}

/**
 * Returns true if given text is included in provided net event grip.
 */

function isTextInNetEvent(matchStr, { method, url } = {}) {
  if (!method && !url) {
    return false;
  }
  return matchStr(method) || matchStr(url);
}

/**
 * Returns true if given text is included in provided stack trace.
 */

function isTextInStackTrace(matchStr, stacktrace) {
  if (!Array.isArray(stacktrace)) {
    return false;
  }

  // isTextInFrame expect the properties of the frame object to be in the same
  // order they are rendered in the Frame component.
  return stacktrace.some(frame =>
    isTextInFrame(matchStr, {
      functionName:
        frame.functionName || l10n.getStr("stacktrace.anonymousFunction"),
      source: frame.filename,
      lineNumber: frame.lineNumber,
      columnNumber: frame.columnNumber,
    })
  );
}

/**
 * Returns true if given text is included in `messageText` field.
 */

function isTextInMessageText(matchStr, messageText) {
  if (!messageText) {
    return false;
  }

  if (typeof messageText === "string") {
    return matchStr(messageText);
  }

  const grip =
    messageText && messageText.getGrip ? messageText.getGrip() : messageText;
  if (grip && grip.type === "longString") {
    return matchStr(grip.initial);
  }

  return true;
}

/**
 * Returns true if given text is included in JS Trace display name.
 */

function isTextInDisplayName(matchStr, displayName) {
  return displayName && matchStr(displayName);
}

/**
 * Returns true if given text is included in notes.
 */

function isTextInNotes(matchStr, notes) {
  if (!Array.isArray(notes)) {
    return false;
  }

  return notes.some(
    note =>
      // Look for a match in location.
      isTextInFrame(matchStr, note.frame) ||
      // Look for a match in messageBody.
      (note.messageBody && matchStr(note.messageBody))
  );
}

/**
 * Returns true if given text is included in prefix.
 */

function isTextInPrefix(matchStr, prefix) {
  if (!prefix) {
    return false;
  }

  return matchStr(`${prefix}: `);
}

function getDefaultFiltersCounter() {
  const count = DEFAULT_FILTERS.reduce((res, filter) => {
    res[filter] = 0;
    return res;
  }, {});
  count.global = 0;
  return count;
}

/**
 * Sort state.visibleMessages if needed.
 *
 * @param {MessageState} state
 * @param {Boolean} sortWarningGroupMessage: set to true to sort warningGroup
 *                                           messages. Default to false, as in some
 *                                           situations we already take care of putting
 *                                           the ids at the right position.
 * @param {Boolean} timeStampSort: set to true to sort messages by their timestamps.
 */

function maybeSortVisibleMessages(
  state,
  sortWarningGroupMessage = false,
  timeStampSort = false
) {
  if (state.warningGroupsById.size > 0 && sortWarningGroupMessage) {
    state.visibleMessages.sort((a, b) => {
      const messageA = state.mutableMessagesById.get(a);
      const messageB = state.mutableMessagesById.get(b);

      const warningGroupIdA = getParentWarningGroupMessageId(messageA);
      const warningGroupIdB = getParentWarningGroupMessageId(messageB);

      const warningGroupA = state.mutableMessagesById.get(warningGroupIdA);
      const warningGroupB = state.mutableMessagesById.get(warningGroupIdB);

      const aFirst = -1;
      const bFirst = 1;

      // If both messages are in a warningGroup, or if both are not in warningGroups.
      if (
        (warningGroupA && warningGroupB) ||
        (!warningGroupA && !warningGroupB)
      ) {
        return getNaturalOrder(messageA, messageB);
      }

      // If `a` is in a warningGroup (and `b` isn't).
      if (warningGroupA) {
        // If `b` is the warningGroup of `a`, `a` should be after `b`.
        if (warningGroupIdA === messageB.id) {
          return bFirst;
        }
        // `b` is a regular message, we place `a` before `b` if `b` came after `a`'s
        // warningGroup.
        return getNaturalOrder(warningGroupA, messageB);
      }

      // If `b` is in a warningGroup (and `a` isn't).
      if (warningGroupB) {
        // If `a` is the warningGroup of `b`, `a` should be before `b`.
        if (warningGroupIdB === messageA.id) {
          return aFirst;
        }
        // `a` is a regular message, we place `a` after `b` if `a` came after `b`'s
        // warningGroup.
        return getNaturalOrder(messageA, warningGroupB);
      }

      return 0;
    });
  }

  if (timeStampSort) {
    state.visibleMessages.sort((a, b) => {
      const messageA = state.mutableMessagesById.get(a);
      const messageB = state.mutableMessagesById.get(b);
      return getNaturalOrder(messageA, messageB);
    });
  }
}

/**
 * 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
  );
  if (!warningGroup || !Array.isArray(warningGroup)) {
    return false;
  }

  return warningGroup.length > 1;
}

exports.messages = messages;

Messung V0.5
C=88 H=93 G=90

¤ Dauer der Verarbeitung: 0.21 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.