Skip to content
This repository has been archived by the owner on Sep 5, 2024. It is now read-only.

md-input-container: Password label not floating with Chrome password-manager auto-fill #1376

Closed
SebiH opened this issue Feb 2, 2015 · 66 comments
Labels
- Lots of Comments browser: Chrome needs: team discussion This issue requires a decision from the team before moving forward. P3: important Important issues that really should be fixed when possible.
Milestone

Comments

@SebiH
Copy link

SebiH commented Feb 2, 2015

When using the password manager in Chrome to automatically fill in username & password on two md-input-containers, the password label will not float until some action (e.g. clicking anywhere on the page) is done:

After initial page load:
image

After clicking anywhere:
image

Reproducing this isn't very easy, since services like Plnkr or CodePen either won't trigger the password manager or the auto-fill; however this GitHub page / repository should provide an environment where it should be reproducible:
Site: http://sebih.github.io/angular-material-pwmanagerbug/
Repo: https://github.com/SebiH/angular-material-pwmanagerbug

As there isn't any password authentification, simply type in something to (hopefully) trigger Chrome's password manager, save password and use the link to go back to the login page.

One interesting thing to note is that refreshing the login page does not seem to recreate the faulty state; however navigation via links breaks the password field again.

It seems to work properly in FireFox 35. I'm using Chrome 40, but noticed it in previous versions, too.

@ThomasBurleson ThomasBurleson added this to the 0.9.0 milestone Feb 5, 2015
@marcysutton
Copy link
Contributor

Pretty sure this is due to a bug in Chrome where password autofill doesn't trigger a change on inputs. Not sure there's much we can do about it until it gets resolved.

@morrissinger
Copy link

I know this is closed, but this has been a Chrome bug for some time and it may not be resolved for a while for all I know. So, I think, in the spirit of finding some solution as a workaround, this might be useful to people:

document.querySelector("input[type=password]").value = " ";
document.querySelector("input[type=password]").value = "";
document.querySelector("input[type=password]").blur()

This clears an autocomplete value, so there is some drawback to this approach. However, it looks nicer. You might also be able to use autocomplete="off", but I haven't been able to solve this issue that way.

@ThomasBurleson
Copy link
Contributor

Should be fixed in #master with SHA a64291b

@SebiH
Copy link
Author

SebiH commented Jul 7, 2015

Problem seems to persist on Chrome (v43). I've updated the test repo/site since it returned 404, but should be up now again. Will test the workaround later.

@morrissinger
Copy link

I don't think we are out of the woods just yet, even given a64291b. I tried a few things to determine this. I:

  • Bower installed angular-material: bower install --save angular-material. This did not fix the problem.
  • Bower installed angular-material#master: bower install --save angular-material#master. This did not fix the problem.
  • Bower installed a64291b: bower install --save angular-material#a64291b. This did not fix the problem.
  • Built angular-material from a64291b: git clone https://github.com/angular/material.git; cd material; npm install; gulp build and then I copied the build over to the project where I am using angular-material and rebuilt my project. This did not fix the problem.

@ThomasBurleson
Copy link
Contributor

@Splaktar - can you investigate this?

@morrissinger
Copy link

Another point: the bug appears to have an intermittent nature. I can refresh @SebiH's repo/site and sometimes see the issue and sometimes not see the issue.

Here is my screenshot:

intermittent

@SebiH
Copy link
Author

SebiH commented Jul 7, 2015

@morrissinger Seems to be related to keyboard events, possibly? On my machine, when refreshing via navigation or clicking the reload button with the mouse, the password label will not position itself correctly. When refreshing with F5, it works as expected.

@alirezamirian
Copy link
Contributor

This is because of this weird Chrome behavior:
The value of autofilled password field is not set until user do any kind of interaction with page, including clicking somewhere or touching almost anything in keyboard!
This is described in more detail here.
I don't know it's because of security or something but it becomes bolder in angular-material, because of the floating label, however it exists even in big websites like Dropbox
I tried a various kinds of workarounds including simulating mouse or keyboard interaction, but none of them worked! It seems to be such an intractable bug!
One possible amelioration is to use "md-no-float", but doing that, not only you will lose the awesomeness of floating label, but also if you want to use form invalidity state to enable or disable submit button, problem remains!

@Splaktar
Copy link
Member

As @SebiH stated in his last comment, this does have some interaction with exactly how the page is refreshed.

  • Doing a ctrl-R or F5 keyboard refresh causes the input to render correctly. This is likely due to the key release triggering Chrome's input detection.
  • Doing a soft refresh via the browser button causes the input to render incorrectly.
  • Doing a hard refresh via the browser button while holding shift causes the input to render correctly, in some cases. This is because the shift key is released, that event triggers Chrome to detect interaction and then process the auto filled input (i.e. float the text).

Just pressing any key after reloading the page w/ a problem causes the input to float the text properly.

Based on the two Chrome bugs referenced in this issue plus an additional duplicate Chrome bug (https://code.google.com/p/chromium/issues/detail?id=398805), this appears to be Working as Intended in Chrome.

There are hacks available. TBosch came up with https://github.com/tbosch/autofill-event just for this kind of issue. Unfortunately, I was not able to get that to work for me with the latest Angular about 9 months ago.

I was able to solve this in one project for Firefox by using a $timeout, details here: http://stackoverflow.com/a/23919364/633107

But this hack does not appear to work for Chrome due to the above mentioned bugs related to Chrome AutoFill. The ngModel is never updated with the value from AutoFill until the user interacts with the page in some way.

Also as for the reason that the Placeholder text does not behave properly, this is related to the fact that md-input uses a <label> instead of a placeholder. Chrome doesn't handle labels like a placeholders as per the spec.

@Splaktar
Copy link
Member

Same issue with using md-label's placeholder attribute: http://splaktar.github.io/angular-material-pwmanagerbug/

It doesn't actually use the browser's placeholder feature. It just adds it as a label with float feature enabled via onClick handler.

@alirezamirian suggested using md-no-float for type="password" inputs. This might be the best option available at this time.

@sjlynch
Copy link

sjlynch commented Jul 28, 2015

md-no-float doesn't appear to fix the label issue with password fields. Using Angular Materials v0.10.0

@webdesignberlin
Copy link

After click on any Element, also the body - the password field regonized a change, and the input moved to top. I have tried to trigger the click event, unfortunately without success.

$timeout(function(){
        angular.element(document).find('body').triggerHandler('click');
        console.log('click but no changes');
      }, 150);

Does anyone have any idea why this is a failure?

@Splaktar
Copy link
Member

@webdesignberlin Yes, Chrome browser is purposely keeping the value from being given to the element until the user interacts with the page. This is done for security reasons. They only make it look like the value has been entered, but they don't actually pass the value to the element until interaction happens. They have patched up all of the hacks to get around this (like your example) that I have been able to find. I tried at least 10 different approaches without any luck.

@Splaktar Splaktar modified the milestones: 0.11.0, 0.9.0 Jul 31, 2015
@webdesignberlin
Copy link

@Splaktar That's not so great. Thanks for the information. Safety first. The only pity is that we live in a world where we have to pay attention to such things.

@alirezamirian
Copy link
Contributor

@sjlynch you should use it as class on label.

@fxck
Copy link

fxck commented Aug 12, 2015

God I wish there was a way to disable that stupid autofill completely. Along with it's annoying yellow background.

@jelbourn
Copy link
Member

Status: need more discussion about how to address this

@sjlynch
Copy link

sjlynch commented Aug 17, 2015

@alirezamirian Thanks, that fixed it and it's a pretty acceptable solution.

@simison
Copy link

simison commented Aug 27, 2015

Setting focus to the password field seems to work as well (with some 1s delay):

angular.element('#password').focus();

Edit: actually it seems to help sometimes, sometimes with delay, sometimes not at all. Might work with tuning $timeouts or something but meh...

@matpag
Copy link

matpag commented Sep 2, 2015

same problem for me :(

@mikila85
Copy link

@Frank3K
i fixed the firefox problem by only activating this to the problematic browser ("chrome"):
(used is_chrome)

$provide.decorator('mdInputContainerDirective', function ($mdTheming, $delegate, $interval) {
                    var is_chrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1;
                    var directive = $delegate[0];
                    directive.compile = function () {
                        return {
                            post: function ($scope, element, attr, ctrl) {
                                $mdTheming(element);
                                if (is_chrome && ctrl.input && ctrl.input.length && ctrl.input[0].type === 'password') {
                                    var count = 0;
                                    var interval = $interval(function () {
                                        if (count > 10) {
                                            $interval.cancel(interval);
                                        }
                                        if (ctrl.input.parent()[0].querySelector('input:-webkit-autofill')) {
                                            ctrl.element.addClass('md-input-has-value');
                                            $interval.cancel(interval);
                                        }
                                        count++;
                                    }, 25);
                                }
                            }
                        };
                    };
                    return $delegate;
                });

@Frank3K
Copy link
Contributor

Frank3K commented Mar 23, 2016

Please note that Microsoft Edge also has Chrome in its user agent string. I'm currently testing for WebkitAppearance instead.

@mikila85
Copy link

Isnt the autofill problem is a chrome only issue? Anyway I will check it tomorrow at work on edge too. Please let us know if you find anything.

@Frank3K
Copy link
Contributor

Frank3K commented Mar 23, 2016

It is Chrome (or maybe Webkit)-only. I was just pointing out that the method now also runs on Microsoft Edge, where it is not needed.

@blaise-io
Copy link

I simplified the workaround:

app.directive('mdInputContainer', function($timeout) {
    return function($scope, element) {
        var ua = navigator.userAgent;
        if (ua.match(/chrome/i) && !ua.match(/edge/i)) {
            $timeout(function() {
                if (element[0].querySelector('input[type=password]:-webkit-autofill')) {
                    element.addClass('md-input-has-value');
                }
            }, 100);
        }
    };
});

@mikila85
Copy link

Thx for sharing :)
..a non TypeScript ver for those who need it:

.directive('mdInputContainer', function ($timeout) {
    return function ($scope, element) {
        var ua = navigator.userAgent;
        if (ua.match(/chrome/i) && !ua.match(/edge/i)) {
            $timeout(function () {
                if (element[0].querySelector('input[type=password]:-webkit-autofill')) {
                    element.addClass('md-input-has-value');
                }
            }, 100);
        }
    };
});

@ThomasBurleson ThomasBurleson modified the milestones: Backlog, Deprecated Apr 20, 2016
@alirezamirian
Copy link
Contributor

alirezamirian commented May 24, 2016

The workaround works pretty fine, but there are some issues:

  • The 100ms delay is not guaranteed to work. For me even 200ms delay doesn't work sometimes.
  • required validator does not behave correctly in the case of auto-filling and if you want to disable your form submission based on form invalidity, it causes a poor user experience (user see button disabled but it gets enabled as soon as they interact with the page).

So I changed the code a little to make it more reliable and added a new required directive which overrides the default required validator to take Chrome auto-filling into account. I wrapped it into a tiny module angular-chrome-autofill-fix and made it available on bower under the same name.

(function(angular){
    "use strict";

    var MAX_TRIES = 5;
    var TRY_INTERVAL = 100;

    angular.module("angular-chrome-autofill-fix", [])
        .directive('mdInputContainer', mdInputContainerDirective)
        .directive("required", requiredDirective);

    function requiredDirective($interval, $log) {
        return {
            priority: 100,
            require: "?ngModel",
            link: linkFn
        };
        function linkFn(scope, elem, attrs, ngModel) {
            var originalValidator = ngModel.$validators.required;
            ngModel.$validators.required = validator;

            // try validating until
            var tries = 0;
            var timer = $interval(function () {
                tries++;
                if (tries > MAX_TRIES) {
                    $interval.cancel(timer);
                }
                ngModel.$validate();
            }, TRY_INTERVAL);

            function validator(modelValue, viewValue) {

                if (isChrome() && elem.is("input[type=password]:-webkit-autofill")) {
                    $log.info("bypassing required validator because of Chrome auto-filling");
                    $interval.cancel(timer);
                    return true;
                }
                return originalValidator(modelValue, viewValue);
            }
        }
    }

    function mdInputContainerDirective($interval) {
        return {
            restrict: "E",
            link: linkFn
        };
        function linkFn($scope, elem) {
            if (isChrome()) {
                var tries = 0;
                var timer = $interval(function () {
                    tries++;
                    if (tries > MAX_TRIES) {
                        $interval.cancel(timer);
                    }
                    if (elem[0].querySelector('input[type=password]:-webkit-autofill')) {
                        elem.addClass('md-input-has-value');
                        $interval.cancel(timer);
                    }
                }, TRY_INTERVAL);
            }
        }
    }


    function isChrome(){
        return navigator.userAgent.match(/chrome/i) && !navigator.userAgent.match(/edge/i);
    }


})(angular);

@camden-kid
Copy link

Can there be a simple fix for this soon?

@tilwinjoy
Copy link

tilwinjoy commented Jun 13, 2016

@camden-kid the project angular-chrome-autofill-fix mentioned just above your comment seems to work fine for me. Give it a try...

@mikila85
Copy link

still all those fixes over fixes.. just take care of it framework wise already...

@iposton
Copy link

iposton commented Jun 20, 2016

To delete the autofill password go to chrome://settings/passwords search the desired url you wish to avoid autofill and click the little x to the right of the stored password. That's the chrome way to fix it.

None of those directives worked. Fortunately @antoinebrault simple adjustment putting label under input and adding this simple css line to your custom css file that should be loaded under the angular-material styles in index.

HTML:

<md-input-container>
    <input type="password" ng-model="password" />
    <label>Password</label>
</md-input-container>

CSS:

.md-input[type=password]:-webkit-autofill ~ label:not(.md-no-float) {
        transform: translate3d(0, 6px, 0) scale(0.75);
        transition: transform cubic-bezier(0.25, 0.8, 0.25, 1) 0.4s, width cubic-bezier(0.25, 0.8, 0.25, 1) 0.4s;
        width: ~"calc((100% - 18px) / 0.75)";
        color: rgba(0,0,0,0.54);
    }

From what I can tell there are two classes that should be added dynamically if there is an autofill value in the input one class md-input-has-value is added to the md-input-container and the second class ng-not-empty is added to the input. why not with type=password?

@Rushi-Pandya
Copy link

Rushi-Pandya commented Jul 28, 2016

This is very easy solution with pure CSS only. Put both Label as well as placeholder and play with the CSS as below:

HTML:

<md-input-container >
   <label>Password</label>
  <input ng-model="vm.password"  placeholder="Password"                            id="password" name="password"  type="password" required>
</md-input-container>

CSS:

.md-input-has-placeholder input.ng-empty::-webkit-input-placeholder{ opacity: 0;}
.md-input-invalid input.ng-empty::-webkit-input-placeholder{ opacity: 1;}
.md-input-invalid label{ display: none;}
.md-input-focused label{ display: block;}
.md-input-invalid.md-input-focused input.ng-empty::-webkit-input-placeholder{ opacity: 0;}
.md-input-invalid input.ng-empty::-webkit-input-placeholder{ opacity: 1;}
.md-input-focused input::-moz-placeholder{  opacity: 0;}
@-moz-document url-prefix() { .md-input-has-placeholder label{ display: none;}}
.md-input-has-placeholder.md-input-focused label,.md-input-has-placeholder.md-input-has-value label{ display: block;}

md-input-container.md-input-focused ::-webkit-input-placeholder:not(.md-no-float),md-input-container.md-input-has-placeholder ::-webkit-input-placeholder:not(.md-no-float), md-input-container.md-input-has-value ::-webkit-input-placeholder:not(.md-no-float) {
    -webkit-transform: translate3d(0, 6px, 0) scale(0.75);
    transform: translate3d(0, 6px, 0) scale(0.75);}

md-input-container.md-input-focused label::-webkit-input-placeholder:not(.md-no-float), md-input-container.md-input-has-placeholder label::-webkit-input-placeholder:not(.md-no-float),md-input-container.md-input-has-value label::-webkit-input-placeholder:not(.md-no-float) {-webkit-transform: translate3d(0, 6px, 0) scale(0.75);
    transform: translate3d(0, 6px, 0) scale(0.75);}

@Head
Copy link

Head commented Jan 25, 2017

Thx @iposton this was the only version that worked for me. The placeholder thing from @Splaktar did not work here.

@isherwood
Copy link

isherwood commented Mar 7, 2017

The solution by @iposton worked to fix the layout issue for me, but it doesn't address the submit button remaining disabled. Still, it's an improvement, and a click on the submit button still results in the form submitting.

Thanks.

@KimAlexander
Copy link

KimAlexander commented Apr 30, 2017

+1

@kylehuff
Copy link

kylehuff commented May 3, 2017

I use a similar method to @antoinebrault, absent the timeout.

My form has an autofocus username, with ng-class defined on the form to call my checkAutoFillValues method defined in my controller. This seems to reliably correct the issue for me, without using any timeouts or CSS rules. The result of ng-class is basically NOOP, it is just used to trigger checkAutoFillValues after various events on the form.

html:

  <form ng-class="{'auto-fill-checked': checkAutoFillValues()}">
      <md-input-container>
          <label>Username</label>
          <input name="username" required ng-model="auth.username" autofocus/>
          ... md-messages validation ...
      </md-input-container>
      <md-input-container>
          <label>Password</label>
          <input name="password" type="password" required ng-model="auth.password"/>
          ... md-messages validation ...
     </md-input-container>
  </form>

controller:

  ...
  $scope.checkAutoFillValues = function() {
    // Check if there are auto-filled values on the page and mark them as having a value
    $('input:-webkit-autofill').each(function(i, el) {
      $(el).parent().addClass('md-input-has-value');
    });
  }
  ...

@puja-m-patil
Copy link

puja-m-patil commented Jun 7, 2017

@antoinebrault
This work for me..!!

.input-container input:-webkit-autofill ~ label {
  color: #757575;
  -webkit-transform: translate(-12%, -50%) scale(0.75);
          transform: translate(-12%, -50%) scale(0.75);
}

Thank You.

@NiceGuyNimni
Copy link

I had this issue with regular css floating label trick and @SayChamp saved my day :) 👍

@ghost
Copy link

ghost commented May 17, 2018

Trigger resize event on page / app load also fixed this issue

window.dispatchEvent(new Event('resize'));

@Splaktar Splaktar added - Lots of Comments P3: important Important issues that really should be fixed when possible. labels May 19, 2018
@angular angular locked as resolved and limited conversation to collaborators May 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
- Lots of Comments browser: Chrome needs: team discussion This issue requires a decision from the team before moving forward. P3: important Important issues that really should be fixed when possible.
Projects
None yet
Development

No branches or pull requests