ionic-framework icon indicating copy to clipboard operation
ionic-framework copied to clipboard

bug: ios, autofill not working properly in wkwebview

Open florian72810 opened this issue 2 years ago • 11 comments

Bug Report

I believe it is the same issue as #22682 and #22757, with reproduction instruction.

Ionic version:

[ ] 4.x [x] 5.x

Current behavior: On a login form using ion-input, the iOS Keychain does fill username and password. But the secondary ion-input item does not see changes. By "secondary" I mean the one that was not touched.

For example:

  1. Touch "email" input
  2. Autofill with iOS Keychain
  3. email and password are filled but only the ion-input email field is updated. The value of ion-input password is empty.

Or:

  1. Touch the "password" input
  2. Autofill with iOS Keychain
  3. email and password are filled but only the ion-input password field is updated. The value of ion-input email is empty.

Expected behavior: Ion-Input should see the change of the underlying input item.

Steps to reproduce:

Sample project based on ionic starter blank: https://github.com/florian72810/ion-input-password-ios-keychain

The issue happens on real device or simulator, I checked on "Simulator iPod touch 7th Generation - iOS 14.5", "Simulator iPod touch 7th Generation - iOS 13.0", and also on real device "iPhone XS Max - iOS 14.5".

The iOS Keychain need to have some content. You have to enable it first and put some data. (see screenshot at bottom)

If you are using a simulator, you have to disconnect the hardware keyboard in order to see the visual keyboard: I/O > Keyboard > Connect Hardware Keyboard (unchecked).

On the following video, you can see the bug.

  • touch "password" input
  • select "Passwords" in visual keyboard
  • select something from the keychain
  • now both fields show data, but you can see that only ion-input email value is still empty.

  • write the letter "a" in the end of email input
  • now the ion-input email value has data: the data from the keychain (here 'demo') and the letter added (here 'a')

https://user-images.githubusercontent.com/20518933/118987623-1e016c80-b989-11eb-890e-7611df8c0882.mp4

Other information:

  • The bug also happens with Ionic/Vue.
  • I have not tested with Capacitor.
  • I am 99% sure that it was working as expected before, around September 2020.

The AppStore validation team seem to be using the keychain to test app because they refused my app yesterday due to this bug.

It is still possible to access the native input field to bypass this bug.

Ionic info:


Ionic:

   Ionic CLI                     : 6.12.3 (/usr/local/lib/node_modules/@ionic/cli)
   Ionic Framework               : @ionic/angular 5.6.7
   @angular-devkit/build-angular : 0.1102.13
   @angular-devkit/schematics    : 11.2.13
   @angular/cli                  : 11.2.13
   @ionic/angular-toolkit        : 3.1.1

Cordova:

   Cordova CLI       : 10.0.0
   Cordova Platforms : ios 6.2.0
   Cordova Plugins   : cordova-plugin-ionic-keyboard 2.2.0, cordova-plugin-ionic-webview 4.2.1, (and 4 other plugins)

Utility:

   cordova-res : not installed
   native-run  : 1.3.0

System:

   Android SDK Tools : 26.1.1 (/Users/foxer/Library/Android/sdk)
   ios-deploy        : 1.11.4
   ios-sim           : 8.0.2
   NodeJS            : v14.15.4 (/Users/foxer/.nvm/versions/node/v14.15.4/bin/node)
   npm               : 6.14.10
   OS                : macOS Big Sur
   Xcode             : Xcode 12.5 Build version 12E262

Simulator Screen Shot - iPod touch (7th generation) - 2021-05-20 at 16 26 55

florian72810 avatar May 20 '21 14:05 florian72810

Thanks for the issue. I took a closer look at your GitHub repo and this appears to be a bug in WKWebView, which is the WebKit-based webview on iOS that Capacitor and Cordova applications use.

<ion-input> relies on the input event to be fired from the inner <input> element in order for the value property to be changed correctly since the autofill functionality sets the value on the inner <input>, not the <ion-input> Web Component. When autofilling on the password text field in WKWebView, this input event is not fired for the email text field, even though its value changes. When running your app in Safari, the input event is fired as expected.

I have reported this issue to the WebKit team for them to fix, and I have also noted that this is impacting your app's ability to get onto the iOS App Store: https://bugs.webkit.org/show_bug.cgi?id=226023.


I have put together this workaround you can use that should resolve the issue for now. Can you try it and let me know how it works in your application? I tested in your demo repo and it was working fine for me.

home.page.html

Email:
<ion-input #emailInput [(ngModel)]="email" type="email" name="email"></ion-input>

Password:
<ion-input #passwordInput [(ngModel)]="password" type="password" name="password"></ion-input>

home.page.ts

import { Component, ViewChild } from '@angular/core';
import { IonInput } from '@ionic/angular';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage {
  @ViewChild('emailInput', { static: true }) emailInput: IonInput;
  @ViewChild('passwordInput', { static: true }) passwordInput: IonInput;

  public email = '';
  public password = '';

  constructor() {}

  async ngOnInit() {
    const nativeEmailInput = await this.emailInput.getInputElement();
    const nativePasswordInput = await this.passwordInput.getInputElement();

    nativeEmailInput.addEventListener('change', (ev: Event) => {
      requestAnimationFrame(() => {
        this.email = (ev.target as HTMLInputElement).value;
      });
    });

    nativePasswordInput.addEventListener('change', (ev: Event) => {
      requestAnimationFrame(() => {
        this.password = (ev.target as HTMLInputElement).value;
      });
    });
  }
}

liamdebeasi avatar May 20 '21 16:05 liamdebeasi

The workaround works 👍

florian72810 avatar May 21 '21 06:05 florian72810

Any news on this ?

papagei-ma avatar Jul 28 '21 16:07 papagei-ma

We are waiting for Apple to implement a fix in iOS. I will update this thread when I have more to share.

liamdebeasi avatar Aug 02 '21 17:08 liamdebeasi

Any update?

Rom3ik avatar Sep 06 '21 12:09 Rom3ik

As I said in my last comment, we are waiting for Apple to implement a fix in iOS.

Locking this thread for now since we have a good understanding of the issue. I will update this thread when I have more to share.

liamdebeasi avatar Sep 07 '21 13:09 liamdebeasi

I've been struggling to make the workaround work for Ionic React. This is what I have so far but it still isn't right. When clicking the Log In button it prints stale values. Clicking it again gets the latest values. Any help would be greatly appreciated.

UPDATE: See next comment for solution. I'm leaving this here as I'm still curious how something like this should work.

import React, {useCallback, useEffect, useRef, useState} from "react";
import {IonButton, IonCard, IonInput, IonItem} from "@ionic/react";

const LogInForm: React.FC = () => {
  const emailRef = useRef<HTMLIonInputElement>(null);
  const passwordRef = useRef<HTMLIonInputElement>(null);
  const [email, setEmail] = useState<string>("");
  const [password, setPassword] = useState<string>("");

  // Workaround for https://github.com/ionic-team/ionic-framework/issues/23335
  // where filling in multiple inputs from a password manager only updates the input with focus.

  const onClickLogin = () => {
    console.log("login:", email, password)
    // logIn(email, password);
  }

  const emailChangeListener = useCallback(({target}) => {
    requestAnimationFrame(() => {
      setEmail((target as HTMLInputElement).value);
    });
    }, []);

  const passwordChangeListener = useCallback(({target}) => {
    requestAnimationFrame(() => {
      setPassword((target as HTMLInputElement).value);
    });
    }, []);

  useEffect( () => {
    let emailInput: HTMLInputElement;
    let passwordInput: HTMLInputElement;

    emailRef.current?.getInputElement().then(input => {
      emailInput = input;
      emailInput?.addEventListener("change", emailChangeListener);
    });

    passwordRef.current?.getInputElement().then(input => {
      passwordInput = input;
      passwordInput?.addEventListener("change", passwordChangeListener);
    });

    return () => {
      emailInput?.removeEventListener("change", emailChangeListener);
      passwordInput?.removeEventListener("change", passwordChangeListener);
    }
  }, [emailChangeListener, passwordChangeListener]);

  return (
    <IonCard>
      <IonItem>{email} / {password}</IonItem>
      <IonItem><IonInput type="email" placeholder="email" ref={emailRef} /></IonItem>
      <IonItem><IonInput type="password" placeholder="password" ref={passwordRef} /></IonItem>
      <IonButton onClick={onClickLogin}>Log In</IonButton>
    </IonCard>
  );
}

export default LogInForm;

jeffcjohnson avatar Apr 05 '22 08:04 jeffcjohnson

I think I have a much (much!) simpler solution:

import React, {useRef} from "react";
import {IonButton, IonCard, IonInput, IonItem} from "@ionic/react";

const LogInForm: React.FC = () => {
  const emailRef = useRef<HTMLIonInputElement>(null);
  const passwordRef = useRef<HTMLIonInputElement>(null);

  // Workaround for https://github.com/ionic-team/ionic-framework/issues/23335

  const onClickLogin = async () => {
    const emailInput = await emailRef.current?.getInputElement()
    const passwordInput = await passwordRef.current?.getInputElement()
    console.log("login:", emailInput?.value, passwordInput?.value)
    // logIn(emailInput?.value as string, passwordInput?.value as string);
  }

  return (
    <IonCard>
      <IonItem><IonInput type="email" placeholder="email" ref={emailRef} /></IonItem>
      <IonItem><IonInput type="password" placeholder="password" ref={passwordRef} /></IonItem>
      <IonButton onClick={onClickLogin}>Log In</IonButton>
    </IonCard>
  );
}

export default LogInForm;

jeffcjohnson avatar Apr 05 '22 08:04 jeffcjohnson

Hi everyone,

I am posting here again as I am merging a related thread.

This thread is tracking a bug in WKWebView where the autofill feature in iOS is not updating the value property in ion-input. This bug impacts Cordova/Capacitor apps but does not impact apps running in mobile Safari.

I will post here when a fix has been shipped in iOS. In the meantime, if anyone has links to production apps that are impacted by this bug, please feel free to post them. This can sometimes help get WebKit bugs fixed faster.

liamdebeasi avatar Jul 08 '22 19:07 liamdebeasi

It's worth noting too that this issue may be worked around with the changes we have proposed in https://github.com/ionic-team/ionic-framework/discussions/25532.

liamdebeasi avatar Jul 08 '22 19:07 liamdebeasi

I'm having the same issues, but on Laravel with Livewire. Anyone that help with a javascript workaround?

mischasigtermans avatar Aug 08 '22 17:08 mischasigtermans

@liamdebeasi can you explain how the changes proposed in #25532 allow for a workaround?

And until then, should we use the workaround described at https://forum.ionicframework.com/t/vuejs-password-autofill-value-empty/217195/3 ? (the solution doesn't work on my app for some reason, I still have the bug it seems the "change" events of the inputs are not triggered)

maelp avatar Nov 17 '22 10:11 maelp

@liamdebeasi can you explain how the changes proposed in #25532 allow for a workaround?

And until then, should we use the workaround described at https://forum.ionicframework.com/t/vuejs-password-autofill-value-empty/217195/3 ? (the solution doesn't work on my app for some reason, I still have the bug it seems the "change" events of the inputs are not triggered)

In Ionic v6, the ionChange event is emitted as a result of the value property on the ion-input component changing. This value property is updated whenever the native input event fires: https://github.com/ionic-team/ionic-framework/blob/f642c29f928803b3a103d43ecde495b890f43159/core/src/components/input/input.tsx#L358. Since the input event does not fire in WKWebView when performing an autofill, the reported bug occurs.

In Ionic v7, we changed the ionChange event to only emit when the native change event fires on the <input> element inside of ion-input. The change event fires as expected in WKWebView which means this issue is can be avoided.

liamdebeasi avatar Nov 17 '22 14:11 liamdebeasi

Alright, I'm still using Ionic v6, that might be why I still have the bug, I did a workaround a bit similar to that linked in the forum above, by just reading the email and password values from the input fields using native API calls right when I needed them

is Ionic v7 ready for production?

maelp avatar Nov 17 '22 14:11 maelp

Ionic v7 is still in development, but we plan on having a public beta in the future.

liamdebeasi avatar Nov 17 '22 14:11 liamdebeasi

Hi everyone,

We recently merged our ion-input revisions which will avoid this issue. Since the code to fix this issue has been merged, I am going to close this. This fix will be available in Ionic 7 as it requires breaking changes to how we emit the ionChange event. As I mentioned in https://github.com/ionic-team/ionic-framework/issues/23335#issuecomment-1318729541, we will have a public beta of Ionic 7 so developers can test these changes and provide feedback. Please let me know if there are any questions. Thanks!

liamdebeasi avatar Nov 21 '22 22:11 liamdebeasi

Thanks for the issue! This issue is being locked to prevent comments that are not relevant to the original issue. If this is still an issue with the latest version of Ionic, please create a new issue and ensure the template is fully filled out.

ionitron-bot[bot] avatar Dec 21 '22 22:12 ionitron-bot[bot]