chromium/third_party/blink/web_tests/editing/inserting/typing-leaks-detached-input.html

<!DOCTYPE html>
<title>Tests that typing should not retain detached subtree</title>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/gc.js"></script>

<div id="root"></div>

<script>
const n = 100;

function createSubtree() {
  let last = document.createElement('div');
  last.spellcheck = false;
  last.contentEditable = true;
  last.id = 'target';
  for (let i = 0; i < n; ++i) {
    let div = document.createElement('div');
    div.appendChild(last);
    last = div;
  }
  root.appendChild(last);
}

function raf() {
  return new Promise(resolve => requestAnimationFrame(resolve));
}

promise_setup(async () => {
  assert_own_property(window, 'internals', 'This test requires internals')
})

promise_test(async () => {
  await asyncGC();
  let nodesBefore = internals.numberOfLiveNodes();

  createSubtree();
  document.getElementById('target').focus();
  document.execCommand('insertText', false, 'foo');
  assert_true(internals.hasLastEditCommand(document));

  root.firstChild.remove();

  await raf();
  await raf();
  await asyncGC();

  let nodesAfter = internals.numberOfLiveNodes();

  assert_equals(nodesAfter, nodesBefore);
  assert_false(internals.hasLastEditCommand(document));
}, 'Typing should not retain detached subtree');

promise_test(async () => {
  createSubtree();
  document.getElementById('target').focus();
  document.execCommand('insertText', false, 'foo');
  document.getElementById('target').blur();
  assert_true(internals.hasLastEditCommand(document));

  await raf();
  await raf();
  await asyncGC();

  assert_true(internals.hasLastEditCommand(document));
}, 'Last edit command should be retained if node is still connected');
</script>

</body>