Skip to content
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

Chrome Autofill does not trigger change #7058

Closed
VinceG opened this issue Nov 14, 2017 · 37 comments
Closed

Chrome Autofill does not trigger change #7058

VinceG opened this issue Nov 14, 2017 · 37 comments

Comments

@VinceG
Copy link

VinceG commented Nov 14, 2017

Version

2.5.3

Reproduction link

https://jsfiddle.net/n2kcp15b/

Steps to reproduce

  1. open any form that has a textfield
  2. auto complete it from chrome auto fill
  3. check the attached v-model and see that it didn't update

What is expected?

update the v-model from what ever chrome autofilled

What is actually happening?

no update is happening


This only happens on Chrome on iOS (safari works fine). I also got reports it has the same issue on Chrome on Android but i didn't confirm.

This seems to be still an issue and i saw a few github issues that reported the same thing months ago but are now closed.

@VinceG
Copy link
Author

VinceG commented Nov 14, 2017

Currently the workaround i have is:

setInterval(() => {
  let value = $('#' + this.id).val();
  if(value != '' && this.value != value) {
    this.value = value;
  }
}, 50);

@posva
Copy link
Member

posva commented Nov 16, 2017

It works fine on Android, cannot test on iOS but there's a known issue with iOS Safari not emitting events when autocompleting, so, unfortunately, I'm not sure we can do something.
@VinceG There must be an event you can listen to instead of checking the value every 50ms, Maybe listening to the blur event will work

@yyx990803
Copy link
Member

I don't think this is something we can fix at the framework level, because it's buggy behavior in one specific browser (not firing input events on autocomplete). A userland workaround seems to be the only way here - I'd suggest only adding the setInterval when you know you are in that specific browser, alternatively, perform the manual sync once before submitting.

@sanntanna
Copy link

Wrap elements in <form> to trigger OnChange event

@KyleMit
Copy link

KyleMit commented Nov 23, 2018

This issue is not unique to Vue and other front end frameworks have tried to tackle this as well

The General Problem

According to this post on Detecting Browser Autofill from StackOverflow

The problem is autofill is handled differently by different browsers. Some dispatch the change event, some don't. So it is almost impossible to hook onto an event which is triggered when browser autocompletes an input field.

Change event trigger for different browsers:

  • For username / password fields:
    • Firefox 4 & IE 7 - don't dispatch the change event.
    • Safari 5 & Chrome 9 - do dispatch the change event.
  • For other form fields:
    • IE 7 - don't dispatch the change event.
    • Firefox 4 - does dispatch the change change event, but only when users select a value from a list of suggestions and tab out of the field.
    • Chrome 9 - does not dispatch the change event.
    • Safari 5 - does dispatch the change event.

Hack For WebKit browsers

According to the MDN docs for the :-webkit-autofill CSS pseudo-class:

The :-webkit-autofill CSS pseudo-class matches when an element has its value autofilled by the browser

So, leveraging that property:

We can define a void transition css rule on the desired <input> element once it is :-webkit-autofilled. JS will then be able to hook onto the animationstart event.

Credit to the Klarna UI team for coming up with the solution here:

klarna/ui/Field/styles.scss:

:-webkit-autofill {
    /* Expose a hook for JavaScript when auto fill is shown. */
    /* JavaScript can capture 'animationstart' events */
    animation-name: onAutoFillStart;

    // Make the backgound color become yellow _really slowly_
    transition: background-color 50000s ease-in-out 0s;
 }

:not(:-webkit-autofill) {
    /* Expose a hook for JS onAutoFillCancel */
    /* JavaScript can capture 'animationstart' events */
    animation-name: onAutoFillCancel;
}

klarna/ui/Field/index.js:

this.refs.input.addEventListener('animationstart', (e) => {
  switch (e.animationName) {
    case defaultStyles.onAutoFillStart:
      return this.onAutoFillStart()

    case defaultStyles.onAutoFillCancel:
      return this.onAutoFillCancel()
  }
})

Outside of that, you're probably going to have to resort to polling for changes, since we can't guarantee an event will fire.

The library tbosch/autofill-event bills itself as "A polyfill to fire a change event when the browser auto fills form fields".

@VinceG
Copy link
Author

VinceG commented Nov 27, 2018

@KyleMit Thanks for the details explanation. It's a-shame that we still don't have a proper consistent API to work with across browsers.

@runnez
Copy link

runnez commented Dec 7, 2018

You may add "name" attribute. It works for me in last Chrome ☮️

@ElectroMyStyle
Copy link

You may add "name" attribute. It works for me in last Chrome

This trick works great, thanks!

@invisor
Copy link

invisor commented Dec 12, 2018

You may add "name" attribute. It works for me in last Chrome ☮️

Confirm, It works! It looks like all fields that can be autofiled should have "name" attribute

@WormGirl
Copy link

I have the same problem on Macos 10.13.3, chrome 70.0.3538.110.I find that just change when you click on any other non-input place, the change event will trigger, This seems to be triggered when the input's blur event fires.This is really weird.

@WormGirl
Copy link

WormGirl commented Dec 17, 2018

You may add "name" attribute. It works for me in last Chrome ☮️

This not works for me,I do form validation when the blur event fires. When the blur event is triggered for the first time,the model of username and password was empty string.But when you click on any other non-input place, the change event will trigger,The model will get the autofilled value, This is really weird

@WormGirl
Copy link

WormGirl commented Dec 17, 2018

It works fine on Android, cannot test on iOS but there's a known issue with iOS Safari not emitting events when autocompleting, so, unfortunately, I'm not sure we can do something.
@VinceG There must be an event you can listen to instead of checking the value every 50ms, Maybe listening to the blur event will work
I agree with you, I think it will trigger change when blur event is occurred
do something like this:

handleBlur ($evt) { 
   this.$nextTick().then(() => {
       // The model here will get the autofilled value
   })
}

@yyx990803 why need this $nextTick()?

@rmaclean-ee
Copy link

For me it worked fine, if there was a single form field. With multiple fields, it wouldn't work. I had to have them wrapped in <form> tag for it to work.

@WormGirl
Copy link

WormGirl commented Dec 18, 2018

@rmaclean-ee You do not really understand what I mean,If you just click the login button to login, there is no problem.But if you bind blur event to the form field, when the blur event trigger you get the value, the value is empty, I think that Chrome remembers the password may be set the value to form fied when the blur event is triggered or after blur event is triggered.

@rmaclean-ee
Copy link

I wasn't responding to your point on the nextTick, I was responding to the original requester about causes for it not filling in v-model.

As I said, making sure it is wrapped in a form tag solved my issue and my v-model is correctly filled in now without the need for any workarounds.

@clem109
Copy link

clem109 commented Jan 29, 2019

Has anyone got a working solution to this? I've tried a lot of the solutions proposed and still no luck

@rmaclean-ee
Copy link

@clem109 I totally have a working solution, but maybe you have hit something new? Can share some code of your page so maybe we can work out what specific aspect is not gelling

@clem109
Copy link

clem109 commented Jan 30, 2019

<script>
  data () {
      return {
        email: '',
        password: ''
      }
    },
  methods: {
        onBlur (event) {
        if (event.type === 'email') {
          this.email = event.target.value
        }
        if (event.type === 'password') {
          this.password = event.target.value
        }
      },

      handleSubmit () {
        this.$validator.validateAll().then(success => {
          if (success) {
            this.$emit('submitted', {
              email: this.email,
              password: this.password
            })
          }
        })
      }
    }
  }
  }
</script>

<template lang='pug'>

      form(@submit.prevent='handleSubmit')
        .login-form__fields
          text-input(
            id='email',
            type='email',
            name='email',
            ref='email'
            label='Email address',
            v-model='email',
            v-validate='"required|email"',
            @blur='onBlur',
            @change='onBlur',
            :error='errors.has("email")',
            :feedback='errors.first("email")'
          )

          password-input(
            id='password',
            label='Password',
            name='password',
            ref='password',
            @blur='onBlur',
            @change='onBlur',
            @focus='onBlur',
            v-model='password',
            v-validate='"required"',
            :error='errors.has("password")',
            :feedback='errors.first("password")'
          )

I've omitted some aspects which were not necessary, I have this working now on iOS safari but iOS chrome it won't work at all...

Thanks for the help @rmaclean-ee

@shengslogar
Copy link

shengslogar commented Mar 4, 2019

I know this issue has been closed for some time now, but just for the sake of documentation and future Googles:

I'm having the same problem that @WormGirl detailed on Chrome 72.0.3626.121, MacOS 10.14.3. Clicking anywhere or calling something as simple as offsetHeight (from the console, can't seem to get this to work from inside a component consistently) causes a redraw and properly triggers the expected events.

Interestingly enough, calling foo.matches(':webkit-autofill') also returns false during this state, which means the entire JavaScript side of things is not being updated in general (even polling the value attribute returns empty quotes).

For reference, I have two inputs, email and password, and they both are wrapped in a <form> element and have name attributes. This doesn't make a difference as suggested by earlier comments.

I was able to get away with only CSS selectors (which work correctly), but this is definitely just a browser-specific bug.

@PinkiNice
Copy link

PinkiNice commented Mar 22, 2019

I've faced similar problem with autofill and v-model usage in Chrome browser on MacOS (Firefox is fine) and nothing mentioned above fixed the problem for me.
But my user-case is a little bit more specific.

I've been using v-model on VueComponent which passes its down to inner <input /> using :value="value" & @input binding. The problem which I've encountered is that autofilled value injected by chrome (without any events) is being overwritten by Vue's bindings (bug does not appear when used on plain <input /> with v-model).

The hack is instead of always binding value to <input /> use conditional binding:

<template>
   <input v-bind="{ /* any bindings you need */ ...valueBinding } @input="onInput" />
</template>
....
computed: {
  valueBinding() {
      /*
        Fix to Chrome autocomplete bug
        Autofilled value gets overriden by v-model or .sync binding 
        So if value is null we just dont bind it 
       and allow chrome to pass whatever it wants first 
      */
      let binding = {};

      if (this.value) { 
        binding.value = this.value;
      }

      return binding;
  }
}

It looks dirty, feels dirty and it is actually dirty. But it works for me and it took a lot of time to come up with solution. Hopes it will help somebody.

@pmochine
Copy link

@PinkiNice what is b? it's not defined (trying to fix my bug here )

@anhzhi
Copy link

anhzhi commented May 16, 2019

bug of chrome, not vue.
remove those css bound to the value of input-element, as a nowaday solution

head>
	<title>自动填充与JS-Stack</title>
	<meta charset="utf-8">
</head>

<form method="get">
	<input id="username" name="username" placeholder="姓名" type="text" onchange="onChange(this)"/>
	<input id="password" name="password" placeholder="密码" type="password" onchange="onChange(this)"/>
	<input type="submit" value="Submit"/>
</form>

<script>
	var	username = document.getElementById('username');
	var	password = document.getElementById('password');
	function show(tag){
		console.log(tag, 'username=', username.value, '!');
		console.log(tag, 'password=', password.value, '!');
	}
	function onChange(input){
		//alert会切换focus
		//alert(input.name+" changed!");
		console.log('input:', input.name, '=', input.value);  
		show('after change:');
	}

	show('step 1:');

	// focus切换触发JS-Stack运行
	if(false){
		username.onfocus = password.onfocus = window.onfocus=function(event){
			console.log("onfocus", event.target)
		}
		username.onblur = password.onblur = window.onblur=function(){
			console.log("onblur", event.target)
		}
	}

	// 通过定时器无法运行JS-Stack: autocomplete对应的focus|change|blur事件
	window.setTimeout(function(){
		show('step 2:'); 
		// window.setTimeout(function(){
		//	show('step 3:'); 
		//},1000);
	},1000);
</script>

@WormGirl
Copy link

WormGirl commented May 16, 2019

It works fine on Android, cannot test on iOS but there's a known issue with iOS Safari not emitting events when autocompleting, so, unfortunately, I'm not sure we can do something.
@VinceG There must be an event you can listen to instead of checking the value every 50ms, Maybe listening to the blur event will work
I agree with you, I think it will trigger change when blur event is occurred
do something like this:

handleBlur ($evt) { 
   this.$nextTick().then(() => {
       // The model here will get the autofilled value
   })
}

@yyx990803 why need this $nextTick()?

This is just like setTimeout.

@sploders101
Copy link

sploders101 commented Jul 26, 2019

Based on facebook/react#1159, it looks like the input fields have the value before page load, so for vuetify (can easily be adapted for anything else), I added this code to my login dialog, and put refs on both text fields.

Typescript w/ vue-property-decorator:

@Component export default class extends Vue {
	username = "";
	password = "";

	@Ref() readonly usernameBox!: Vue;
	@Ref() readonly passwordBox!: Vue;

	mounted() {
		this.username = this.usernameBox.$el.querySelector("input")!.value;
		this.password = this.passwordBox.$el.querySelector("input")!.value;
	}
}

Vanilla JS:

export default {
	data: () => ({
		username: "",
		password: "",
	}),
	mounted() {
		this.username = this.$refs.usernameBox.$el.querySelector("input").value;
		this.password = this.$refs.passwordBox.$el.querySelector("input").value;
	}
}

Edit:

This approach works for material design inputs, preventing overlapping text, but the referenced issue also has an excellent comment by @thomasjulianstoelen describing why this behavior is, and how to fix it. Simply encasing things in form elements does not (usually) work. The form must have a submit button, or the user must hit enter within the form before the values become readable. I suggest reading the entire comment, but here's a summary:

The reason password auto-fills can't be read is because it would be quite easy to setup a phishing attack, sending the user a malicious page, which can immediately read their passwords that the browser has auto-filled. To remedy this, you must wrap your auto-filled password boxes in a form and have a proper submit button. Once the form is submitted, the browser acknowledges this as user confirmation that the site can have access to their credentials, which then become readable.

@jkiss
Copy link

jkiss commented Jan 23, 2020

You could set autocomplete="off" attribute(HTML5), or record input value in url hash and update v-modal by it.

@Neoniklain
Copy link

The best way for me:

const input = this.$refs.input;
      this.autofilled = window.getComputedStyle(input, ':-webkit-autofill').animationName === 'onAutoFillStart';

@laukstein
Copy link

This is old Chrome bug, I just open https://bugs.chromium.org/p/chromium/issues/detail?id=1166619 for it.

@tannerbaum
Copy link

Seemingly still an issue, and after trying every possible combination it seems specific to IOS's native keychain password management + chromium browsers. No other combination has the issue, not IOS with safari, not chrome on android, nothing.

@ven0ms99
Copy link

ven0ms99 commented Aug 9, 2022

Bug is still present.

@tafelnl
Copy link

tafelnl commented Dec 28, 2022

With this code, it works on both Chrome and Safari for me:

<template>
  <input
    ref="input"
    :value="value"
    type="password"
    @input="onInput"
    @change="onInput"
  />
</template>

<script lang="ts" setup>
import { ref } from 'vue';

const value = ref<string | null>(null);

function onInput(e: Event) {
  // We should also fire this at the `@change` event on the input.
  // Because `@input` does not always gets triggered when autofilling.
  // This happens for example on iOS Safari when autofilling credentials from the keychain.

  if (e.target instanceof HTMLInputElement && e.target.value) {
    value.value = e.target.value;
  } else {
    value.value = '';
  }
}
</script>

It combines the following:

@Cheaterman
Copy link

Bug is still present.

It is. Why is this closed?

@sploders101
Copy link

Bug is still present.

It is. Why is this closed?

My understanding is that it was closed because it's an issue with the browser, not Vue, and that it likely won't get solved because there are security implications that come with exposing auto-filled login details to JS

@tafelnl
Copy link

tafelnl commented Feb 9, 2024

because there are security implications that come with exposing auto-filled login details to JS

I'm curious what made you come to that conclusion. Because if you don't have access to the auto-filled value, how are you going to send it to the server? Sooner or later you will have to read the value in JS

@sploders101
Copy link

sploders101 commented Feb 9, 2024

because there are security implications that come with exposing auto-filled login details to JS

I'm curious what made you come to that conclusion. Because if you don't have access to the auto-filled value, how are you going to send it to the server? Sooner or later you will have to read the value in JS

It's not that you never get the value. It's that it doesn't show up right away. I haven't run into this issue in a while so my memory is a little foggy, but if I remember correctly, the value is postponed until the user interacts with the page. If the user, for example, clicks the submit button, then the value is populated and the click event is fired in quick succession.

I'm not at my computer right now though, so try it before taking my word for it. I don't remember there being anything functionally wrong when I came across this; the issue for me was that I was using material design, and the name of the textbook was overlapping the value until the user clicked "Login" since the event was postponed. It was just a design issue.

Also, I don't remember what the rules are for interactions that count, but if you're still having issues, make sure that your button is an <input type="submit">

@Cheaterman
Copy link

Some people indeed report that "interacting with the page" is enough for onChange/onInput to be triggered, but that's not what we observed (on Firefox - Chrome seems to work fine).

@sploders101
Copy link

Ok. This isn't an issue with Vue though; if you register the event handler yourself you'll get the same result. The issue lies within Firefox. I believe someone here opened a bug report on their tracker. Try checking there and see if anyone has offered a solution or status update.

@michaelzulimar
Copy link

michaelzulimar commented Apr 10, 2024

just wanted to share really cool and slicky solution https://stackoverflow.com/a/73633659/24207152
compliments to https://stackoverflow.com/users/3276256/3rdsenna
att. @WormGirl

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

No branches or pull requests