Chrome自动填充锁定输入框,就像它们不能被点击一样。

43

我在Chrome中使用自动填充时遇到了问题和非常奇怪的行为。当我登录然后从应用程序注销时,输入字段(电子邮件、密码)被自动填充,但这些字段看起来像是被冻结的,无法点击。

这个bug不是每次都会出现,大约1/10的情况下会发生。我注意到在注销时,当字段被自动填充后,1秒钟后输入框的字体变小了,然后如果你点击输入框,似乎你没有点击过,什么也不会发生,但如果你输入一些文本(数字不起作用,保持冻结状态),输入框就会恢复正常。

这里有一个展示奇怪行为的gif:https://gifyu.com/image/kTkX

我尝试设置autocomplete="off",但没有效果。我还匹配了所有输入字段中的CSS类以查看是否有一些覆盖CSS,但一切看起来都很好。

<form [formGroup]="loginForm">

<input id="emailHeader" type="text" formControlName="email" placeholder="E-mail">

<input #password type="password" formControlName="password" placeholder="Lozinka">
<input type="submit" (click)="executeLogin()"  value="Prijava">

</form> 

我希望在自动填充后,这些字段不会被冻结。

public loginForm: FormGroup;
  public emailInput: ElementRef;
  public passwordInput: ElementRef;
  @ViewChild('email') set emailContent(content: ElementRef) {
    this.emailInput = content;
  }
  @ViewChild('password') set passwordContent(content: ElementRef) {
    this.passwordInput = content;
  }

  // UI helpers
  public showLoginForm: boolean;
  public showBalance: boolean;
  public player: PlayerModel = new PlayerModel({});
  public balanceInfo: BalanceInfoModel = new BalanceInfoModel({});
  public nxcsBalanceInfo: NXCSBalanceInfoModel = new NXCSBalanceInfoModel({});
  public dialogType = DialogType;
  public customMessageError = '';

  // Store
  private headerState$: Observable<any>;
  private loginState$: Observable<any>;
  private playerState$: Observable<any>;
  private emailInput$: Observable<any>;
  private passwordInput$: Observable<any>;
  private balanceState$: Observable<any>;
  private headerSubscription: Subscription;
  private loginSubscription: Subscription;
  private playerSubscription: Subscription;
  private emailSubscription: Subscription;
  private passwordSubscription: Subscription;
  private balanceSubscription: Subscription;
  // tslint:disable-next-line:no-inferrable-types
  private leftMenu: string = '';
  // tslint:disable-next-line:no-inferrable-types
  private rightMenu: string = '';

  constructor(
    private authService: AuthService,
    private fb: FormBuilder,
    private store: Store<any>,
    private route: Router,
    private localStorageService: LocalStorageService,
    private playerService: PlayerService,
    private notificationService: NotificationService,
    private dialogService: DialogService,
    private helpers: HelpersService,
    private translateCode: TranslateCode,
        private promotionService: PromotionService,
    ) {
    this.loginForm = this.buildLoginForm();
  }

  ngOnInit() {
    this.setupStore();
  }

  ngAfterViewInit() {
    this.formEventsAfterViewInit();
  }

  ngOnDestroy() {
    this.headerSubscription.unsubscribe();
    this.loginSubscription.unsubscribe();
    this.playerSubscription.unsubscribe();
    this.notificationService.closeConnection();
  }

  public executeLogin() {
    if(!this.loginForm.valid) {
      this.customMessageError = this.translateCode.transform("EMPTY_INPUT_MESSAGE");
      return;
    }

    this.authService.login(new LoginModel({...this.loginForm.value, details: this.helpers.sendSessionData()}))
      .subscribe(
        data => {
          this.localStorageService.setUserAfterLogin(data.token);
          this.customMessageError = '';
          this.loginForm.reset();
          this.route.navigate(['/app/casino']);
        },
        error => {
          error.message.includes('Račun je zaključan') ? this.store.dispatch(new PopupNotification(error.message)) : this.customMessageError = error.message
          this.addAfterErrorSubscription();
        }
      );
  }

  public openDialog(dialogType: string): void {
    switch (dialogType) {
      case DialogType.PAYMENT:
         this.openWithdrawalDialog()
        break;
      case DialogType.PAYMENT_DEPOSIT:
          this.checkRegistrationStep();
      break;
      case DialogType.TRANSACTION_HISTORY:
        this.store.dispatch(new OpenDialog({
          type: dialogType,
        }));
      break;
    }
  }

  public openInternalTransactionsDialog(): void {
    this.promotionService.getPromotionsByLocation('NXCS_DEPOSIT')
      .subscribe(
                data => this.dialogService.openDialog(MENU_DIALOGS.INTERNAL_TRANSACTION, { promotions: data }),
        error => this.dialogService.openDialog(MENU_DIALOGS.INTERNAL_TRANSACTION, { promotions: []}),
      );
  }

  public backToRegistrationStep() : void {
    switch (this.player.registrationStep) {
      case 1 :  this.route.navigate(['/auth/registration/step-two']);
                break;
      case 2 : this.route.navigate(['/auth/registration/step-three']);
                break;
      case 3 : this.route.navigate(['/auth/registration/step-four']);
                break;
      case 4 : this.route.navigate(['/auth/registration/step-five']);
                break;
      case 5 : this.route.navigate(['/auth/registration/step-six']);
                break;
      default : this.route.navigate(['/login']);
                break;
    } 
  }

  public toggleMenu(dialog): void {
    if (dialog === 'left') {
      this.leftMenu = this.leftMenu === dialog ? '' : dialog;
    }
    if (dialog === 'right') {
      this.rightMenu = this.rightMenu === dialog ? '' : dialog;
    }
    this.dispatchShadow();
  }

  private openWithdrawalDialog(_data: any = {}): void {
    const playerRole = this.localStorageService.getPlayer()['profileRole'];

    if (playerRole  === 'WITHDRAWAL_DISABLED' && this.player.uploadedAdditionalInfo) {
      this.store.dispatch(new OpenNotification({ type: NotificationType.WITHDRAWAL_DISABLED }));
      return;
    }

    playerRole  === 'WITHDRAWAL_DISABLED' ?
    this.store.dispatch(new OpenNotification({type: NotificationType.MONEY_LAUNDERING})) :
    this.dialogService.openDialog(MENU_DIALOGS.WHITDRAWALS, _data);
  }

  private openProceedToRegistration(): void {
    this.store.dispatch(new OpenNotification ({type: NotificationType.PROCEED_REGISTRATION}))
  }

  private checkRegistrationStep(): void {
    if(this.player.registrationStep < 6) {
      this.openProceedToRegistration();
    } else {
      this.dialogService.openDialog(MENU_DIALOGS.DEPOSITS, {});
    }
  }

  private dispatchShadow(): void {
    if (this.leftMenu !== '') {
      this.store.dispatch(new OpenedLeftMenu());
      this.leftMenu = '';
    }
    if (this.rightMenu !== '') {
      this.store.dispatch(new OpenedRightMenu());
      this.rightMenu = '';
    }
  }

  private buildLoginForm(): FormGroup {
    return this.fb.group({
      email: [
        '', Validators.compose([Validators.required, Validators.min(5)]),
      ],
      password: [
        '', Validators.compose([Validators.required, Validators.min(5)])
      ],
    });
  }

  private loadBalance(): void {
    this.playerService.getPlayerBalance().toPromise()
      .then(data => this.store.dispatch(new SetPlayerBalance({balanceInfo: new BalanceInfoModel(data) })))
      .then(() => {
        if (this.player.externalId) {
          this.playerService.getNXCSPlayerBalance()
            .subscribe(
              data => this.store.dispatch(new SetPlayerNXCSBalance({ nxcsBalanceInfo: new NXCSBalanceInfoModel(data) })),
              error => console.log(error),
            );
        }
      });
  }

  // Store methods
  private setupStore(): void {
    this.headerState$ = this.store.pipe(select('headerStore'));
    this.loginState$ = this.store.pipe(select('loginStore'));
    this.playerState$ = this.store.pipe(select('playerStore'));
    this.balanceState$ = this.store.pipe(select('balanceStore'));
    this.addSubscriptions();
  }

  private formEventsAfterViewInit(): void {
    if (this.emailInput && this.passwordInput) {
      this.emailInput$ = fromEvent(this.emailInput.nativeElement, 'focus');
      this.passwordInput$ = fromEvent(this.passwordInput.nativeElement, 'focus');
      this.addFormEventsSubscriptions();
    }
  }

  private addFormEventsSubscriptions(): void {
      this.emailSubscription = this.emailInput$.subscribe(() => this.triggerEmailFocus());
      this.passwordSubscription = this.passwordInput$.subscribe(() => this.triggerPasswordFocus());
  }

  private triggerEmailFocus(): void {
    this.emailInput.nativeElement.select();
    if (this.emailSubscription) {
      this.emailSubscription.unsubscribe();
    }
  }

  private triggerPasswordFocus(): void {
    this.passwordInput.nativeElement.select();
    if (this.passwordSubscription) {
      this.passwordSubscription.unsubscribe();
    }
  }

  private addSubscriptions(): void {
    this.addHeaderSubscription();
    this.addLoginSubscription();
    this.addPlayerSubscription();
    this.setBalanceSubscription();
  }

  private addHeaderSubscription(): void {
    this.headerSubscription = this.headerState$
    .subscribe(headerState => this.showLoginForm = headerState !== HeaderActionTypes.LoginPage);
  }

  private addLoginSubscription(): void {
    this.loginSubscription = this.loginState$
    .subscribe(loginState => {
      if (loginState) {
        this.loadBalance();
        this.notificationService.connect(localStorage.getItem('token'));
      } else {
        this.notificationService.closeConnection();
      }
      this.showBalance = loginState;
      this.formEventsAfterViewInit();
    });
  }

  private addPlayerSubscription(): void {
    this.playerSubscription = this.playerState$
    .subscribe(playerData => this.player = playerData);
  }

  private addAfterErrorSubscription(): void {
    this.passwordSubscription = this.passwordInput$.subscribe(() => {
      if (this.customMessageError !== '') {
        this.customMessageError = '';
        this.passwordSubscription.unsubscribe();
      }
    });
  }

}

1
发生在我身上的事情,你不是孤例。+1 - Christian Vincenzo Traina
3
你好,欢迎来到 stackoverflow! 如果您能提供一些(简化的)应用程序代码,那么帮助您将会更加容易。 - Christophe Le Besnerais
2
听起来有点像 JS 在呈现表单后仍在进行计算,会阻塞 UI 直到停止。你是否正在获取自动填充建议或其他必须等待才能使表单工作的内容?因此,网络延迟可能会影响呈现吗? - Shilly
12
哦该死,这不是你应该与Angular表单配合工作的方式,这里有很多问题可能会出现,“@ViewChild”声明,我甚至不确定那个是否有效。当你使用“formControl”时,不需要“@ViewChild()”。而且你有太多的订阅,这将产生内存泄漏和无限循环的高风险,很容易冻结UI界面。 - Nerijus Pamedytis
2
在这种情况下,总是有一种简单的方法来找到错误。创建一个没有监听器或其他内容的简单表单。检查是否存在错误。如果仍然存在纯HTML,则表示这是Chrome的错误。如果在添加一些监听器后出现,则表示您的监听器存在问题。 - Arseniy-II
显示剩余18条评论
4个回答

3

我在Angular中遇到了同样的问题,并找到了一个解决方法,即在输入框被点击时重置输入框的值,这似乎可以解决锁定问题。

HTML:

<input (click)="unfreezeInput($event)">

TS:
unfreezeInput(el) {
    // setting input value again makes it editable
    el.target.value = el.target.value;
}

1
每次在允许谷歌Chrome保存登录凭据的表单上自动填充字段时,都会出现这个问题。这也曾经困扰过我。 第一次遇到它时,我试着尝试了一两次,但我渐渐地接受了它作为每个Chrome用户必须处理的不太重要的错误。 因此,总之,这与您的代码无关。如果您想“解冻”输入字段,请在其中键入内容以更改其值。 希望这可以帮助您和您的神经接受这种与浏览器相关的行为。

0

4
欢迎来到 Stack Overflow。感谢您的回复。但是,它更接近于评论而不是答案。您可以在这里了解如何编写一个好的答案:https://stackoverflow.com/help/how-to-answer - Ozgur Sar

0
与上述问题相同,一个简单的表单,包含用户名和密码输入框,登录和退出功能。Chrome会自动填充保存的值到输入框中,但是表单无法点击,并且无法弹出选择用户名和密码的选项,光标也不可见,但是在输入内容后似乎可以正常工作。
而且unfreezeInput对我来说有效。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接