Repository: wagtail/wagtail
File: client/src/entrypoints/admin/core.js (JavaScript)

Code before:
    }
  };

  // Delay snapshotting the form’s data to avoid race conditions with form widgets that might process the values.
  // User interaction with the form within that delay also won’t trigger the confirmation message.
  if (!alwaysDirty) {
    setTimeout(() => {
      const initialFormData = new FormData($form[0]);
      initialData = new Map();
      Array.from(initialFormData.keys())
        .filter((key) => !key.startsWith('comments-'))
        .forEach((key) => initialData.set(key, initialFormData.getAll(key)));

      const updateDirtyCheck = () => {
        clearTimeout(updateIsDirtyTimeout);
        // If the form is dirty, it is relatively unlikely to become clean again, so
        // run the dirty check on a relatively long timer that we reset on any form update
        // otherwise, use a short timer both for nicer UX and to ensure widgets
        // like Draftail have time to serialize their data
        updateIsDirtyTimeout = setTimeout(updateIsDirty, isDirty ? 3000 : 300);
      };

      $form.on('change keyup', updateDirtyCheck).trigger('change');

      const validInputNodeInList = (nodeList) => {
        nodeList.forEach((node) => /* eslint-disable-line consistent-return */ {
          if (
            node.nodeType === node.ELEMENT_NODE &&
            ['INPUT', 'TEXTAREA', 'SELECT'].includes(node.tagName)
          ) {
            return true;
          }
        });
        return false;
      };

      const observer = new MutationObserver((mutationList) => {
        mutationList.forEach((mutation) => {
          if (
            validInputNodeInList(mutation.addedNodes) ||
            validInputNodeInList(mutation.removedNodes)
          ) {
            updateDirtyCheck();
          }
        });
      });
      observer.observe($form[0], {
        childList: true,
        attributes: false,
        subtree: true,
      });

Review comment (suggestion):
Be careful here with this change. 

The for loop return statement on line 160 below is actually returning from this function - when you use the forEach the rest of the results still go through the mapping. 

You either need to set a variable above the loop like `let isValidNode = false;` and then set the variable to true when the condition matches. 
Alternatively you can use array.some 

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some

Code after:

  const updateIsDirty = () => {
    const previousIsDirty = isDirty;
    isDirty = isFormDirty();
    if (previousIsDirty !== isDirty) {
      updateCallback(isDirty, isCommentsDirty);
    }
  };

  // Delay snapshotting the form’s data to avoid race conditions with form widgets that might process the values.
  // User interaction with the form within that delay also won’t trigger the confirmation message.
  if (!alwaysDirty) {
    setTimeout(() => {
      const initialFormData = new FormData($form[0]);
      initialData = new Map();
      Array.from(initialFormData.keys())
        .filter((key) => !key.startsWith('comments-'))
        .forEach((key) => initialData.set(key, initialFormData.getAll(key)));

      const updateDirtyCheck = () => {
        clearTimeout(updateIsDirtyTimeout);
        // If the form is dirty, it is relatively unlikely to become clean again, so
        // run the dirty check on a relatively long timer that we reset on any form update
        // otherwise, use a short timer both for nicer UX and to ensure widgets
        // like Draftail have time to serialize their data
        updateIsDirtyTimeout = setTimeout(updateIsDirty, isDirty ? 3000 : 300);
      };

      $form.on('change keyup', updateDirtyCheck).trigger('change');

      const isValidInputNode = (node) =>
        node.nodeType === node.ELEMENT_NODE &&
        ['INPUT', 'TEXTAREA', 'SELECT'].includes(node.tagName);

      const observer = new MutationObserver((mutationList) => {
        const hasMutationWithValidInputNode = mutationList.some(
          (mutation) =>
            mutation.addedNodes.some(isValidInputNode) ||
            mutation.removedNodes.some(isValidInputNode),
        );

        if (hasMutationWithValidInputNode) {
          updateDirtyCheck();
        }
      });

      observer.observe($form[0], {
        childList: true,
        attributes: false,
        subtree: true,
      });