html - Memory Leak When Dynamically Updating <iframe> Content Using srcdoc in JavaScript - Stack Overflow

admin2025-04-22  2

We are experiencing memory leaks in a web application where we dynamically update elements with new HTML content using the srcdoc property. This issue becomes apparent after extended usage, especially when the content within the s changes frequently. The problem is worse with the highest diversity of html content. If the diversity of content is too high, it will consistently max out memory usage.

Our application uses two elements per container, switching between them to ensure a smooth transition. One is updated with new content using srcdoc, and once loaded, we switch its visibility with the currently active . The previously active is then cleared and hidden.

However, over time, this approach seems to cause a memory leak (e.g., Google Chrome, Edge, Opera). The memory usage keeps increasing, even after the unused is cleared. Firefox does not appear to suffer from the memory leak, however, video in the htmls seem to gradually suffer from slower playback.

Questions:

  • What’s the recommended way to dynamically update content without causing memory leaks in modern browsers?
  • Is there a more efficient pattern for managing and cleaning up resources when using srcdoc?

Current Implementation:

Here’s the logic for updating the srcdoc property and handling the onload event:

if (received_data.html) {
    var inactiveIframe = document.getElementById(iframeIds.inactiveIframe);
    
    // Update the srcdoc of the inactive iframe, which triggers content loading
    inactiveIframe.srcdoc = received_data.html;

    // Handle the iframe size if provided
    if (received_data.width && received_data.height) {
        inactiveIframe.style.width = received_data.width + "px";
        inactiveIframe.style.height = received_data.height + "px";
        inactiveIframe.parentNode.style.height = received_data.height + "px";
        inactiveIframe.parentNode.parentNode.style.height = received_data.height + "px";
    }

    // Set up the onload event handler to switch iframes after the new content has loaded
    inactiveIframe.onload = async function() {
        await switchIframe(iframeIds);
        // Optionally, remove the event listener after the switch
        inactiveIframe.onload = null;
    };

    validateResources(received_data.html);
}

The switchIframe function is responsible for swapping the active and inactive iframes:

function switchIframe(iframeIds) {
    // Swap the active and inactive iframe IDs
    let temp = iframeIds.activeIframe;
    iframeIds.activeIframe = iframeIds.inactiveIframe;
    iframeIds.inactiveIframe = temp;

    // Hide the previously active iframe
    const inactiveIframeElement = document.getElementById(iframeIds.inactiveIframe);
    inactiveIframeElement.style.zIndex = '999997';
    inactiveIframeElement.style.opacity = '0';

    // Clear the content of the inactive iframe
    inactiveIframeElement.srcdoc = '';
    let frameDoc = inactiveIframeElement.contentDocument || inactiveIframeElement.contentWindow.document;
    if (frameDoc) {
        frameDoc.documentElement.innerHTML = '';
    }

    // Remove event listeners
    inactiveIframeElement.onload = null;

    // Show the new active iframe
    const activeIframeElement = document.getElementById(iframeIds.activeIframe);
    activeIframeElement.style.zIndex = '999998';
    activeIframeElement.style.opacity = '1';
}

Result: The memory usage maxes out over time.

Alternative Approaches Tried:

We’ve tried different strategies to avoid memory leaks, but none were successful:

  1. Recreate the : Instead of reusing the inactive , we removed it from the DOM and re-added a new with the same ID:
const inactiveIframeElement = document.getElementById(iframeIds.inactiveIframe);
inactiveIframeElement.srcdoc = '';
inactiveIframeElement.parentElement.innerHTML = `
    <iframe id="${iframeIds.inactiveIframe}" class="iframe-class" scrolling="no" srcdoc=""></iframe>`;

Result: The memory usage maxes out over time.

  1. Clearing HTML with innerHTML: Cleared the ’s content using innerHTML, as shown below:
let frameDoc = inactiveIframeElement.contentDocument || inactiveIframeElement.contentWindow.document;
if (frameDoc) {
    frameDoc.documentElement.innerHTML = '';
}

Result: The memory usage maxes out over time.

We are experiencing memory leaks in a web application where we dynamically update elements with new HTML content using the srcdoc property. This issue becomes apparent after extended usage, especially when the content within the s changes frequently. The problem is worse with the highest diversity of html content. If the diversity of content is too high, it will consistently max out memory usage.

Our application uses two elements per container, switching between them to ensure a smooth transition. One is updated with new content using srcdoc, and once loaded, we switch its visibility with the currently active . The previously active is then cleared and hidden.

However, over time, this approach seems to cause a memory leak (e.g., Google Chrome, Edge, Opera). The memory usage keeps increasing, even after the unused is cleared. Firefox does not appear to suffer from the memory leak, however, video in the htmls seem to gradually suffer from slower playback.

Questions:

  • What’s the recommended way to dynamically update content without causing memory leaks in modern browsers?
  • Is there a more efficient pattern for managing and cleaning up resources when using srcdoc?

Current Implementation:

Here’s the logic for updating the srcdoc property and handling the onload event:

if (received_data.html) {
    var inactiveIframe = document.getElementById(iframeIds.inactiveIframe);
    
    // Update the srcdoc of the inactive iframe, which triggers content loading
    inactiveIframe.srcdoc = received_data.html;

    // Handle the iframe size if provided
    if (received_data.width && received_data.height) {
        inactiveIframe.style.width = received_data.width + "px";
        inactiveIframe.style.height = received_data.height + "px";
        inactiveIframe.parentNode.style.height = received_data.height + "px";
        inactiveIframe.parentNode.parentNode.style.height = received_data.height + "px";
    }

    // Set up the onload event handler to switch iframes after the new content has loaded
    inactiveIframe.onload = async function() {
        await switchIframe(iframeIds);
        // Optionally, remove the event listener after the switch
        inactiveIframe.onload = null;
    };

    validateResources(received_data.html);
}

The switchIframe function is responsible for swapping the active and inactive iframes:

function switchIframe(iframeIds) {
    // Swap the active and inactive iframe IDs
    let temp = iframeIds.activeIframe;
    iframeIds.activeIframe = iframeIds.inactiveIframe;
    iframeIds.inactiveIframe = temp;

    // Hide the previously active iframe
    const inactiveIframeElement = document.getElementById(iframeIds.inactiveIframe);
    inactiveIframeElement.style.zIndex = '999997';
    inactiveIframeElement.style.opacity = '0';

    // Clear the content of the inactive iframe
    inactiveIframeElement.srcdoc = '';
    let frameDoc = inactiveIframeElement.contentDocument || inactiveIframeElement.contentWindow.document;
    if (frameDoc) {
        frameDoc.documentElement.innerHTML = '';
    }

    // Remove event listeners
    inactiveIframeElement.onload = null;

    // Show the new active iframe
    const activeIframeElement = document.getElementById(iframeIds.activeIframe);
    activeIframeElement.style.zIndex = '999998';
    activeIframeElement.style.opacity = '1';
}

Result: The memory usage maxes out over time.

Alternative Approaches Tried:

We’ve tried different strategies to avoid memory leaks, but none were successful:

  1. Recreate the : Instead of reusing the inactive , we removed it from the DOM and re-added a new with the same ID:
const inactiveIframeElement = document.getElementById(iframeIds.inactiveIframe);
inactiveIframeElement.srcdoc = '';
inactiveIframeElement.parentElement.innerHTML = `
    <iframe id="${iframeIds.inactiveIframe}" class="iframe-class" scrolling="no" srcdoc=""></iframe>`;

Result: The memory usage maxes out over time.

  1. Clearing HTML with innerHTML: Cleared the ’s content using innerHTML, as shown below:
let frameDoc = inactiveIframeElement.contentDocument || inactiveIframeElement.contentWindow.document;
if (frameDoc) {
    frameDoc.documentElement.innerHTML = '';
}

Result: The memory usage maxes out over time.

Share Improve this question asked Jan 21 at 14:56 Joshua RhodeJoshua Rhode 1 4
  • Why does the code await the call to switchIframe()? The function is not async and it does not return a Promise. – Pointy Commented Jan 21 at 15:00
  • From memory, I think I either changed this to help with the memory leak or make the transition more seamless. But previous code had: inactiveIframe.onload = function() { switchIframe(iframeIds); // Optionally, remove the event listener after the switch inactiveIframe.onload = null; }; But this still suffered from the memory leak anyway – Joshua Rhode Commented Jan 21 at 15:04
  • Ah actually, it is because it is inside an async websocket onmessage function which was previously not async – Joshua Rhode Commented Jan 21 at 15:08
  • Reusing window or document objects risks browsers not releasing the global environment record(s) for the bindings of values declared using const, let and (probably) class. Without trying to prove it applies in this case, how does replacing the inactive iframe element with one freshly created with document.createElement affect the outcome? – traktor Commented Jan 21 at 21:17
Add a comment  | 

1 Answer 1

Reset to default 1

Although setting inactiveIframeElement.srcdoc = ''; should be effective, here are a couple of other methods you could try:

Setting the src property to an empty page:

inactiveIframeElement.src = 'about:blank';

Or Opening, then Closing the document. Calling open() clears the contents and provisions the contents for writing. Calling close() then closes it again.

inactiveIframeElement.contentDocument.open();
inactiveIframeElement.contentDocument.close();

However, the issue may not be with the iFrame, itself. I notice this line in your example:

validateResources(received_data.html);

This tells me you are continuing to use received_data.html after the code in question. What happens in validateResources? Is it possibly doing something that is not allowing this value to be disposed? Is there any other code that may be hanging on to the received_data object or this html property? Have you tried explicitly setting received_data.html = null after it is used to see if it corrects the issue?

转载请注明原文地址:http://anycun.com/QandA/1745302241a90594.html