Webtips

Explore the world of web technologies through a series of tutorials

Follow publication

Detecting Chrome autofill on page load

--

Detecting Chrome autofill on page load
Photo by Richy Great on Unsplash

Unless you have been living under a rock for the past 20 years, or in some kind of an all-technology-is-evil death cult, chances are you have probably encountered browsers’ data saving and autofill features. It’s a neat feature for end-users, lifting the burden of remembering or writing down passwords and authorization data, but sometimes it turns out to be a nightmare for the developers.

For the impatient, if you want to see the code right away, jump to the section “The solution”. However, if you want to read about the journey to it, keep reading.

You see, as the internet and technology progress, developers also want to give users something functional, notable, and in most cases, something different. That’s why we design and build different looking websites, forms, and UI’s, that will create memorable experiences for people, make use of our product a breeze, and our users happy.

For example, in recent years, floating labels in forms became popular, mainly because of libraries such as MaterialUI. Perhaps you already developed your own, and so you know how it usually goes: you start making it, let's say for login form, fit all the bits and pieces, then finish it, you may even do a11y right, try to enter login info a few times, and then on page reload you are treated with a spectacle of something like this:

Well isn’t that lovely? Your labels are in the wrong place, although the data is filled. You suspect that you made some mistake, but even after a long, hard look, you cannot find it.

You then realize that after clicking anywhere on the page, your inputs seem to wake up, and your labels jump right where they should be. Interesting…

So, what’s the deal with that? Let me make a little intro first — screenshots above show an example of my custom VueJS input components. The floating labels are “floated” when input is focused or when there’s a value in the input’s v-model. Should be straightforward, right? Well, not so much when it comes to Chrome autofill.

At first, I googled, and found pretty much the same answer everywhere, about using the CSS animations to detect browser autofill (there is a solid article about it here), but that didn’t actually solve my problem, which was detecting autofill on page load. CSS animations are not triggered in this case, and as I would find out, some other weird things are happening too.

So I made a few experiments.

Remember how I said the labels would “wake up” and work as intended after clicking anywhere on the page. Well, my first hacky approach was to try and click with JS on multiple elements on the page — buttons, inputs, <html> itself, but that turned out to be a dead-end, it doesn’t work that way. My guess is that is how Chrome is supposed to function, it’s waiting for the actual user interaction on the page and prevents Javascript from accessing the values from inputs until the user has clicked or touched something on the page. And I was OK with that, seems like a sane thing to do.

In retrospect, I should’ve assumed that the next thing I tried would turn out to be also unsuccessful, but I saw a post where someone recommended that on StackOverflow, so I said what the hell, let me try this far-fetched thing that a complete stranger on the internet said it works. It was about polling the input field several times, checking for value until you found if it has it or not.

So I made another experiment — I created a little function with setInterval that would run on mounted() and check my input fields for value, let it run for some time, and then some more, but it said that values in those fields don’t exist, it’s empty. I was literally staring into a filled input, but Javascript was unaware of it.

So it hit me that this must be also a part of Chrome’s features — values of auto-filled inputs on page load are not accessible through JS until a user interacts with a page.

But where are those values stored? They must be somewhere, I’m looking at them. That is the question I’m still unable to answer, so drop me a comment if you have any idea, I’m curious and I would like to know.

I was close to despair, thinking I won't be able to detect this after all, but then I started to closely inspect everything about these inputs in Dev tools and saw this little section that might be useful, I thought.

How about I check for some of these CSS properties, if they exist, might that mean that the input is auto-filled?

Experiment time again!

To check the CSS properties of an element you cannot just use something like document.querySelector("#email-input").appearance , it won’t work, as this is not part of the element’s style, but their CSS. What you need to use is getComputedStyle and it goes something like this:

window.getComputedStyle(document.querySelector("#email-input"), null).getPropertyValue('appearance')

When I tried that on auto-filled input, it returned menulist-button but if I manually filled an input, or tried that on an input that was not auto-filled, the result was auto . Awesome, that’s something I can use.

Take one

Armed with this new insight, I made a small function that could check this for me on the input component mount

// detectAutofill.js
export const detectAutofill = (element) => {
return window.getComputedStyle(element, null).getPropertyValue('appearance') === 'menulist-button'
}

// my input component
import { detectAutofill } from '@/utils/detectAutofill'mounted () {
this.inputFilled = detectAutofill(this.$refs.input)
}

Saved this, opened it in Chrome aaaaand… nothing happened.

What the actual frak? Well, Chrome is not doing this instantly, it waits for the whole page to load, then takes some time to recognize the inputs and whether it should fill them, and only then it does the auto-filling.

Trouble is, Chrome is not emitting any events when autofill occurs (if it would, we probably wouldn’t have this problem), so the only solution is to wait some time and then check the inputs.

The solution

So I changed my function to include the setTimeout function, and turned it into a promise, because why not. I tinkered with the wait time, tried 150ms, 300ms, but the least timeout I found that always works is 600ms.

Now the function looks like this:

export const detectAutofill = (element) => {
return new Promise(resolve => {
setTimeout(() => {
resolve(window.getComputedStyle(element, null).getPropertyValue('appearance') === 'menulist-button')
}, 600)
})
}

And then in my input component, I have this method that I would run on the mounted hook

async testAutoFill () {
this.autofilled = await detectAutofill(this.$refs.input)
}
...mounted () {
this.$nextTick(() => {
this.testAutoFill()
})
}

And it works like a charm.

Tested it on Chrome, Edge, and Firefox. Seems like Chrome and Edge behave the same, which is not surprising, considering it’s the same engine in both of those, and as for Firefox, I haven’t encountered the same issue, auto-filling seems to work differently there. I don’t think this will work on IE11, but if you still need to support that… thing, you should probably check and then not run this function at all there.

Please note that you cannot use this if you set appearance: none for inputs in your CSS, which is a common practice for some reason. In case that you still want to have this property manually defined in your CSS, try using some of the other properties found in pseudo-class :-internal-autofill-selected .

The described approach was just for the cases when you need to know if the inputs are filled on page load, for other cases of browser autofill, there are lots of articles written on how to handle it, like the one I mentioned above. I’m using this same approach in my components.

I prepared a small project for all of you, so you can see it in action, with a similar use case — not using this check for floating labels, but to enable or disable a Submit button in a form.

The project is written in VueJS, but you can use this approach in any other framework.

You can find it on Github https://github.com/chameleonmind/detecting-autofill and see it live here https://detecting-autofill.netlify.app/.

Thanks for reading!

--

--

Webtips
Webtips

Published in Webtips

Explore the world of web technologies through a series of tutorials

Miloš Milošević
Miloš Milošević

Written by Miloš Milošević

Front end developer, focused on VueJS, at the moment. Writing about Javascript, VueJS and other front end technologies. Find me on twitter @chameleon_mind

Responses (6)

Write a response