Skip to content

Memory leak (detached DOM tree) when patching vnodes #5851

@bartsidee

Description

@bartsidee

Version

2.3.4

Reproduction link

https://jsfiddle.net/bartsidee/n1tvtwet/

Steps to reproduce

We identified that when a vnod is patched/replaced in the DOM e.g. after a vue-router route update the new vnode is marked with a DOM element reference (_refElm) of the previous vnode (in our case the previous route) causing a detached dom tree (memory leak)

Steps to reproduce:

  1. open attached jsfiddle
  2. open chrome dev tools -> memory tab
  3. select the 'Home' route
  4. make a heap snapshot
  5. select the 'Foo' route
  6. make a heap snapshot
  7. select the last snapshot and use the 'summary' tab
  8. in the search box enter 'detached'
  9. find the detached tree that has a retainer to '_refElm'
  10. if you hover the red HTMLDivElement you will notice it is the dom element of the 'Home' component

See below code reference in vuejs:

  1. "patch" method is triggered which trigger "patchVnode" to replace existing root component
    patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly)
  2. both root components have children, which triggers "updateChildren":
    if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
  3. there are no transitions so a new element is created, triggering "createElm":
    createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
  4. important is that "createElm" here is passed a DOM element reference "oldStartVnode.elm" of the old vnode (previous route) as 4th argument
  5. this in turn is link that the old dom element to the new vnode as "_refElm" property creating the detached binding causing the leak
    function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested) {

    opts._refElm = options._refElm

What is expected?

we would expect that VueJS would be conservative of memory usage and prevent detached dom nodes

What is actually happening?

when patching the vdom and replacing the root node, e.g. using the vue-router we see that the dom element of the previous route is indirectly linked to the new route causing a detached dom tree.


The leak has a limited impact for normal devices as the reference is only to the last component that is patched, but can have a large impact on for example embedded devices as it prevents to browser to garbage collect to last route increasing the overall memory needed to run the app.

So to be clear if we have 3 components: component 1, component 2, component 3 and component 1 is patched with component 2, vue will keep a detached dom of component 1. If component 2 is patched with component 3, component 2 will also be detached, but component 1 is in this case cleared because only the 'elm' reference transitions from component 2 to component 3.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions