Skip to content

Memory leak with component with input with v-model #10004

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
posva opened this issue May 9, 2019 · 20 comments · Fixed by #10085
Closed

Memory leak with component with input with v-model #10004

posva opened this issue May 9, 2019 · 20 comments · Fixed by #10085

Comments

@posva
Copy link
Member

posva commented May 9, 2019

Version

2.6.10

Reproduction

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <script src="https://unpkg.com/vue"></script>

    <div id="app">
      <div id="nav">
        <button @click="goHome">go to Home</button>
        <button @click="goAbout">go to About</button>
      </div>
      <component :is="current"></component>
    </div>

    <script>
      const Home = {
        name: 'Home',
        template: `
      <div>
        <h2>Home</h2>
      </div>
      `,
      }

      const About = {
        template: `
        <div class="about">
    <h1>This is an about page</h1>
    <input type="text" v-model="input">
  </div>
      `,
        name: 'about',
        data: () => ({
          input: '',
        }),
      }

      const vm = new Vue({
        el: '#app',
        data() {
          return {
            current: 'Home',
          }
        },

        methods: {
          goHome() {
            this.current = 'Home'
          },
          goAbout() {
            this.current = 'About'
          },
        },
        components: { Home, About },
      })
    </script>
  </body>
</html>

Screen Shot 2019-05-09 at 18 59 07

Steps to reproduce

  • go to the about page
  • type in the input
  • leave the page
  • collect garbage and take a snapshot with devtools

What is expected?

VueComponent count should be stable

What is actually happening?

VueComponent count keeps increasing.


seems to be related to typing in the input

@ricardo-lobo
Copy link

@posva After further testing. This might not be Vue's fault.

Testing the code above on Chrome Version 72.0.3626.121 (mac) seems to run without any issues and VueComponent count is stable.

@posva
Copy link
Member Author

posva commented May 10, 2019

it isn't in 74. It could be a bug on Chrome. I've found leaks in the past (https://bugs.chromium.org/p/chromium/issues/detail?id=949587). I didn't test with this one though as it looks like Vue is retaining the component and the browser cannot free it

@kizivat
Copy link

kizivat commented May 10, 2019

I can confirm this happens in Chrome 74. When trying to replicate this in Safari, I cannot even see any Vue related objects in their JS allocations inspector, but that is probably just my mistake somehow.

However, if this doesn't happen in the earlier versions of Chrome with the same Vue version, this could be probably forwarded to Chromium project and closed here?

@andysoa
Copy link

andysoa commented May 10, 2019

This is especially critical when we talk about large SPA's with tones of data. As far as I can see Chrome 74 is not able to collect much...

@andysoa
Copy link

andysoa commented May 14, 2019

https://bugs.chromium.org/p/chromium/issues/detail?id=961494

There is some development regarding this issue.

@KyLeoHC
Copy link

KyLeoHC commented May 22, 2019

Recently I have found that there is a problem while input element work with 'v-model'. If I type the content quickly, it cause the high CPU usage.
3E5A8D54478379E333140DDA975D8E0F

@diazemiliano-zz
Copy link

diazemiliano-zz commented May 23, 2019

@posva or anyone knows how to test this with FF devtools and EDGE devtools?
Because we don't have the constructors names in those.
This is happening in other browsers like chrome and ff too.
Thanks.

@zrh122
Copy link
Contributor

zrh122 commented May 30, 2019

@posva, i just found out PR #10085 can fix it, my Chrome Version is 74.0.3729.169.

@posva posva added the has PR label May 30, 2019
@posva
Copy link
Member Author

posva commented May 30, 2019

Thanks for checking it out!

@clopezcapo
Copy link

clopezcapo commented Jul 1, 2019

Does anybody know if this also happens in the PROD version? As I am not able to filter by "vue" components in Chrome DevTools. I see the same code as the dev version though...

@ricardo-lobo
Copy link

ricardo-lobo commented Jul 1, 2019

Does anybody know if this also happens in the PROD version? As I am not able to filter by "vue" components in Chrome DevTools. I see the same code as the dev version though...

@clopezcapo Yes it does.

@clopezcapo
Copy link

Bufff, and isn't that a huge issue for PROD environments? It looks like is a serious potential killer to me.

Does anybody know if they are aware?

@clopezcapo
Copy link

@posva can you please update us on the status of this fix? As still happens in PROD.

@istobran
Copy link

istobran commented Jul 2, 2019

this issue is too serious, it take me a long time to find it out...

@lianzhao
Copy link

lianzhao commented Aug 1, 2019

So... any workarounds so far? Downgrade to v2.6.9?

@novakjcn
Copy link

I can confirm that PR #10085 fixes it for us as well. Is there a plan to merge this?

@bigghhz
Copy link

bigghhz commented Mar 3, 2020

I also tried this solution, but only half of it worked. chrome80/vue2.6.11/vue-router 3.0.
While vue component has been successfully recycled, three additional listeners have not been removed and dom cannot be recycled. Looking at the heap snapshot,the VueComponent increment is 0, but htmlInputElement has not been recycled, as Performance tools proves. I tracked that these 3 listeners comes from "vue/src/platforms/web/runtime/directs/model.js"-->function onCompositionStart and onCompositionEnd, But I'm not familiar with vue source code .
when input element is deep in a large component, this leads to a large memory leak.

@catCarrot

This comment has been minimized.

@MarMun
Copy link

MarMun commented Nov 16, 2020

As a workaround, I wrap my inputs to:

  • manually add / remove event listeners
  • 'detach' the input from component DOM on beforeDestroy

This way 'only' the inputs (which have been edited) itself leak and not the whole component / view.

Simplified:

<template>

  <div id="nativeInputContainer">
    <input
      ref="nativeInput"
      id="nativeInput"
      v-bind:type="(password) ? 'password' : ''"
      v-bind:placeholder="placeholder"
      v-bind:value="workingValue"
    />
  </div>

</template>

<script>

export default {
  name: 'BaseInput',
  inheritAttrs: false,
  components: {},
  props: {
    placeholder: {
      type: String,
      required: false,
      default: () => { return '' }
    },
    value: {
      type: String,
      required: true
    },
    password: {
      type: Boolean,
      required: false,
      default: () => { return false }
    }
  },
  data () {
    return {
      workingValue: ''
    }
  },
  watch: {

    value: {
      immediate: true,
      handler () {
        this.workingValue = this.value
      }
    }

  },
  methods: {

    onInput (event) {
      this.workingValue = event.target.value
      this.$emit('input', this.workingValue)
    },

    onChange (event) {
      this.$emit('change', this.workingValue)
    }

  },
  mounted () {
    // to avoid memory leak (from vue)
    // we assign event listeners manually
    this.$nextTick(() => {
      this.$refs.nativeInput.addEventListener('input', this.onInput)
      this.$refs.nativeInput.addEventListener('change', this.onChange)
    })
  },
  beforeDestroy () {
    // to avoid memory leak (from vue)
    // we remove event listeners manually
    this.$refs.nativeInput.removeEventListener('input', this.onInput)
    this.$refs.nativeInput.removeEventListener('change', this.onChange)

    // to avoid memory leak (from input itself)
    // which causes 'this' to be not available for gc
    // (undo history keeps input alive)
    // see: https://bugs.chromium.org/p/chromium/issues/detail?id=961494#c14
    this.$el
      .querySelector('#nativeInputContainer')
      .removeChild(
        this.$el.querySelector('#nativeInput')
      )
    // maybe not necessary but "always Double Tap"
    this.$refs.nativeInput = null
  }

}
</script>

<style scoped>
</style>

@yosinch
Copy link

yosinch commented Jun 9, 2021

Thanks for using Chrome! And sorry for bothering you... m(_ _)m

Chrome Canary 93.0.4535.2 (at leas) clears undo stack for removed , <textarea> content editable. So, undo stack is no more source of memory leak. (http://crbug.com/961494)

I attempt to take memory snapshot for posva's sample. Retainers of "Detached HTMLInputElemnt" are:

  1. ShadowRoot
  2. Detached HTMLInputElement
  3. Detached HTMLDivElement
  4. Detached Text
  5. elm in VNode from vue:767
  6. Blink roots
  7. Blink roots

Note: You can make ShadowRoot to detached by <input>.value = '' (remove Text node) and <input>.type ='hidden' (remove ShadowRoot)

I'm not sure Blink roots of 6 and 7. One of Blink roots may be caused by <input>.focus().

Anyway, Detached HTMLInputElement is only one, so memory issue is mitigated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.