Electron embedded React App in Iframe does not release memory on closing

245 Views Asked by At

I have long term running Electron/react app with another react app inside an iframe. The problem arise when i open and close iframe many times and remove it from the DOM. The embedded app doesn't get unloaded all the way, there is always some residue left inside the memory, witch over time kips growing till the point that electron cherishes.

Electron V.18.2.0 React-scripts v.18.2.0

What i have done/tried so far:

  1. replaceWith(a.cloneNode(true)),

  2. unmountComponentAtNode(a),

  3. setting src of an iframe to "about:blank",

  4. replaced entire iframe with a new one,

  5. disabled cash in electron, disabled cash in client app,

  6. replaced entire document with blank one ,

  7. removed scripts that are loaded in client app before unload,

  8. setting state to null on component will unmount

  9. , "clearCache", "clearHostResolverCache", "clearStorageData", "clearAuthCache", "clearCodeCaches" when iframe unmounts (inside electron main process),

  10. global.gc(),

  11. AbortController() on all fetch requests, iframe.contentWindow.close(),

  12. setting comandLine switches to '--max-old-space-size=4096' & '--disable-http-cache, setting meta header for pragma:


<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />

What were i expecting:

cashed code to be removed from memory compliantly

What i saw:

after closing and removing iframe element there is some cashed residue left in memory all-tho no cashing is enabled snap:snap of memory leak

code: mainApp:

   componentWillUnmount() {
        let a = document.getElementById('clientApp')
        a.contentWindow.close();
        a.replaceWith(a.cloneNode(true));
        a.src = "about:blank"
        unmountComponentAtNode(a)
        logger.info("Unmounting/Closing client app.")
        this.setState(null)
        document.removeEventListener('vnd.interaction.finished', this.onInteractionFinished)
        window.unregisterActionButton()
        if (this.state.config.ClearSelectedCashes.length > 0) {
            ipcRenderer.send('ClearSelectedCashes', this.state.url, this.state.config.ClearSelectedCashes)
        }

    }
    urlAppendParams() {
        if (this.state.interactionId !== undefined && this.state.interactionId !== "") {
            const urlAppendParams = new URL(this.state.url)
            urlAppendParams.searchParams.append('interactionId', this.state.interactionId)
            urlAppendParams.searchParams.append('selectedLanguage', this.state.selectedLanguage)
            urlAppendParams.searchParams.append('scriptsPath', this.state.scriptsPath)
            urlAppendParams.searchParams.append('screenSize', this.state.screenSize)
            urlAppendParams.searchParams.append('fontSize', this.state.fontSize)
            logger.debug(`Open url in iframe: ${urlAppendParams}.`)
            return urlAppendParams
        }
    }

    render() {
        if (this.state.interactionId !== null && this.state.interactionId !== undefined && this.state.interactionId !== "") {
            return (
                <React.StrictMode>
                    <div className='iframe'>
                        <iframe
                            sandbox="allow-scripts"
                            id="clientApp"
                            title='clientApp'
                            src={this.urlAppendParams()}
                            className='iframeContent'
                            onLoad={this.clientAppLoaded}
                        >
                        </iframe>
                    </div>
                </React.StrictMode>
            )
        } else return <></>
    }

ClientApp:

   componentWillUnmount() {
        document.removeEventListener("mainApp_customerPreferencesChanged", this.onCustomerPreferencesChanged)
        document.removeEventListener('mainApp_userInteraction', this.onUserInteraction)
        document.removeEventListener('vnd.mainAppScreen.Active', this.mainAppActive)
        document.removeEventListener('vnd.mainAppScreen.Inactive', this.mainAppInactive)
        this.setState(this.initialState)
        document.head.remove()
        document.body.remove()
    }

Any info would be appreciated thanks in advance. :)

1

There are 1 best solutions below

0
On BEST ANSWER

In case someone is reading this keep in mind to remove console logs also in embedded application because console logs are keeping the reference to the objects that are logged. Because of this the GC never releases memory associated with them, so even if the embedded app is closed it stays loaded in memory.

SOLVED