5

In the example below:

  • If you right click the document, it will tell you it's listening.
  • If you click m1 it will replace the document element, but right clicking the document will still inform you that it's listening. You must right click near the top because the document has no contents.
  • If you click m2 it will overwrite the document contents and right clicking near the top no-longer does anything. Examining the document in the development tools verifies that the event handlers are gone.
  • After pressing one button, you must "run code snippet" again to try the next because this demonstration is destructive.

With this information. Is there a different way to destroy the document and replace it with a new one, in such a way that the event handlers are destroyed, without using the document.write() function?

document.write() is prone to errors and its usage is "strongly discouraged", but I would still like to be able to destroy the document and it's event listeners.

document.addEventListener('contextmenu', function (e) {
  alert('still listening');
  e.preventDefault();
})

function m1() {
  var doc = document.implementation.createHTMLDocument();
  document.replaceChild(
    document.importNode(doc.documentElement, true),
    document.documentElement
  );
}
function m2() {
  document.close();
  document.write('<html></html>');
}
<button onclick="m1()">m1</button>
<button onclick="m2()">m2</button>

To be clear, button/function "m1" fails my goals, because although the document element was replaced, the event handlers from the previous document element were preserved for some reason. I would like to achieve what m2 achieves, but without using document.write and document.close which are recommended against.

Addendum:

This question is strictly for the sake of better understanding the limits of the language and the engines that implement them. Please do not try to read between the lines or solve some alternate goal. It's just a question of whether something is possible or not.

I don't need to know how to remove event listeners or manage event listeners, or remove all child elements of a document. I would like to know if it's possible to destroy the document element itself, leaving no <html> tag whatsoever and leaving none of its event listeners behind. This is possible with document.write() but I simply want to know if there are alternate means of achieving this exact goal, not any other assumed goals.

ADJenks
  • 2,973
  • 27
  • 38
  • 1
    `window.reload()` would destroy the document. – Taplar Sep 17 '20 at 19:08
  • 2
    `document.write` is recommended against generally *because* it's a confusing trap newbies can easily fall into for some reason. But there *are* use-cases for it, as long as you're knowledgeable and careful - this may well be one of them. – CertainPerformance Sep 17 '20 at 19:08
  • 2
    Another thing to keep in mind - listeners can be added even higher than the document, to the `window` – CertainPerformance Sep 17 '20 at 19:09
  • @Taplar I get "Uncaught TypeError: window.reload is not a function" – ADJenks Sep 17 '20 at 19:09
  • Ah, sorry, `window.location.reload()` – Taplar Sep 17 '20 at 19:10
  • @CertainPerformance good point about window. I've run into strange errors using `document.write()` so I'd like to avoid it. It does say you may experience unpredictable results. For example, I randomly got this: `Uncaught DOMException: An attempt was made to use an object that is not, or is no longer, usable` and it only happened once and I couldn't reproduce it. – ADJenks Sep 17 '20 at 19:11
  • There are a variety of ways to [remove all of the root element's children](https://stackoverflow.com/a/3955238/636077). – ray Sep 17 '20 at 19:12
  • @Taplar Ah, thank you. I'd like to do this without reloading the window. I just want to destroy the document. – ADJenks Sep 17 '20 at 19:12
  • @rayhatfield this is not quite my goal. I can easily remove the root element's children. – ADJenks Sep 17 '20 at 19:13
  • @CertainPerformance Interestingly the second method appears to destroy window listeners as well, at least on Firefox. – ADJenks Sep 17 '20 at 19:17
  • @ScottMarcus That would remove all d the children of the existing document, but not destroy the root and it's event listeners. It's similar to ray's response. – ADJenks Sep 17 '20 at 19:28
  • What about document.create? Delete the document and create it with document.create – user13806962 Sep 30 '20 at 17:42
  • How about using an iframe as a container and just dropping and replacing the iframe when you need to destory it? It will allow you to sandbox everything including the window. Im assuming that when you want to remove the document, you also want to remove any global state you might have such as timers, pending promises, ect. – Brenden Sep 30 '20 at 18:06
  • 3
    I have a sneaking suspicion this is an [XY Problem](http://xyproblem.info/). Regardless, you cannot remove event listeners unless you add a hook and track calls onto `document.addEventListener` prior to any registrations. [`document.open()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/open) followed immediately by `document.close()` _will_ remove all document event listeners, but will ***not*** cancel pending promises, `setInterval`s or `setTimeout`s; you could cancel intervals by using `clearInterval` from `0` to `setInterval(undefined)`, though. I can make a demo if needed. – concision Sep 30 '20 at 23:41
  • That's okay @concision, I know how to clear intervals and timeouts fine thanks. The pending promises is an interesting point. I'd still like to know if there's an alternate way to destroy the document. If there isn't, I'd be happy with an answer that says "There is no alternate way to do this." – ADJenks Oct 01 '20 at 20:12
  • @ADJenks I genuinely think there might not be an alternative way to do this. This does not seem to be a use case that is supported by mainstream browsers. Any solution will likely be unconventional in some way - pick your poison. Good luck, though! – concision Oct 01 '20 at 20:26
  • @Brended, I'd like to do this while preserving the JavaScript environment state. Removing an iframe would destroy everything. – ADJenks Oct 01 '20 at 20:26
  • @concision I just find it odd that there is no alternate method to achieving something that only `document.write()` can do, given that using this function is highly recommended against because it has potentially erratic behavior. Erratic behavior which I have actually encountered. If you really feel like there is no alternate method, then feel free to add that as an answer and I will accept it as correct until proven otherwise. – ADJenks Oct 01 '20 at 20:32
  • **NEVER** use `document.write` or `innerHTML` as they are both non-standard and buggy as hell in browsers. "But I never have any problem with them!" - until you're required to do more than you've been doing this whole time. – John Oct 07 '20 at 09:03
  • @John yeah, the whole point of this question was to find alternatives. I've never heard anything back about innerHTML though, that property is used everywhere. – ADJenks Oct 07 '20 at 16:47
  • @ADJenks `innerHTML` doesn't serialize the DOM so when dumping "HTML" an id isn't registered properly. It *appears* to work until you're forced to use stricter code and then suddenly half of everything stops working. If you embrace strict coding policies you eventually adapt decisive success. – John Oct 07 '20 at 17:35
  • @John Interesting, do you have an example case of this that I can see somewhere and/or a reference to this behavior? – ADJenks Oct 07 '20 at 21:25
  • I'm pretty sure that innerHTML is part of the standard though. Maybe 10 years ago it wasn't. – ADJenks Oct 07 '20 at 21:28
  • @ADJenks My entire platform uses the XML parser and I dropped `innerHTML` faster than a body over the Brooklyn Bridge when I discovered `id`s don't register in the DOM. That garbage has no business being in a standard of any kind but don't worry, I'll rip the garbage out of the standards once I give the jokers the boot. Another example: `scrollbar-width: thin;` - because standards should *give* us ambiguity right? – John Oct 08 '20 at 09:12

3 Answers3

2

I just realised that actually you can, and that the first example, m1, actually does replace the documentElement and removes its event listeners. The interesting thing is that there were no event listeners on it in the first place, they were on the document itself not the document element. The developer tools (in FireFox) tricks you and shows them as being attached to the <html> element, even though they aren't technically attached to an element. If you modify the example to actually attach the events to the document.documentElement instead of the document itself the event will be disabled when you click m1:

document.documentElement.addEventListener('contextmenu', function (e) {
  alert('still listening');
  e.preventDefault();
})

function m1() {
  var doc = document.implementation.createHTMLDocument();
  document.replaceChild(
    document.importNode(doc.documentElement, true),
    document.documentElement
  );
}
function m2() {
  document.close();
  document.write('<html></html>');
}
<button onclick="m1()">m1</button>
<button onclick="m2()">m2</button>

Originally I was going to say no, and that you can't do it, as user Livingston Samuel states in this similar but not identical question: "You cannot replace the current document object or any document object with the Document object created with createHTMLDocument method." Sticking to my original question though, it is not the document I was asking about, it was the documentElement. So given the modified code above, I will say that Yes, it is possible.

It is interesting to see that the event listeners are displayed on the html element in FireFox whether they are attached to the document object or the documentElement object, as of version 81. I might expect that they would instead not display them at all when attached to document, or put them on some abstract object. An iframe does have an abstract object available labeled #document, but it is not used for this and has no event label:

Document object in FireFox dev tools.

It is also interesting that document.write() actually destroys the events on the document object, not just the document.documentElement. It might be documented somewhere in here but I can't seem to find it.

After testing Chrome I noticed that the developer tools distinguishes between events being attached to the documentElement:

documentElement contextmenu listener

and the document itself:

document contextmenu listener

ADJenks
  • 2,973
  • 27
  • 38
0

I'm not sure what's behind this question but I may guess, that you are having issues when the objects and events are re-created in the page.

If this is the case, as can be seen in MDN (https://developer.mozilla.org/en-US/docs/Web/API/Node/removeChild), despite you remove the elements on the HTML, they still could remain in the memory for a while, waiting for the garbage collector to clean them up.

So in case you wish to remove safely a piece of code that contains events, the safest way is to remove the events first using your favorite library (like jQuery) or using the DOM through native JavaScript: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener

Once you have removed all events, the you could remove all elements, and the safest way to do this (from a memory point of view) is through a loop, removing the inner elements first and the parent elements then, and so on.

Carles
  • 418
  • 2
  • 18
-1

You can use document.getElementById('id-here').innerHTML = "", like so:

<html id="the-website">

<body>
  <input type="button" onclick="document.getElementById('the-website').innerHTML = '';" value="Don't click me plz!">
</body>

</html>
Seth
  • 2,214
  • 1
  • 7
  • 21