Bug 254861

Summary: iPadOS: Viewport doesn't correctly restore after dismissing software keyboard for installed web apps
Product: WebKit Reporter: ik
Component: Layout and RenderingAssignee: Nobody <webkit-unassigned>
Status: NEW ---    
Severity: Normal CC: bfulgham, formularsumo, megan_gardner, philipp.pirrung+apple, simon.fraser, tomac, webkit-bug-importer, wenson_hsieh, zalan
Priority: P2 Keywords: InRadar
Version: Safari 16   
Hardware: iPhone / iPad   
OS: iOS 16   
Attachments:
Description Flags
Screenshot of twitter web app after dismissing the keyboard none

Description ik 2023-04-01 01:21:32 PDT
Created attachment 465722 [details]
Screenshot of twitter web app after dismissing the keyboard

Reproduce:

1. On an iPad Mini (5th gen), go to twitter.com, install the web app to the home screen
2. Open the installed web app, log in, navigate to a DM / chat
3. Tap the message input on the bottom (the software keyboard pops up, pushing the page/viewport up
4. Dismiss the keyboard via the button in the lower right corner

Alternative:

The same issue can be observed in the testcase I made for another issue:
https://testcase.rejh.nl/webkit-safe-area-inset-does-not-update/

What happens:

The viewport jumps down, then back up to where it was before the keyboard was dismissed. 

See attached screenshot (1, red).

See attachment.

Notes:

1. Elements with position: fixed *do* restore to the correct position (like the menu bar on the left) but it's only visually. Touch events still fire relative to the offset of the viewport. Example: After following the steps, tap the 'twitter' icon in the top left corner. It triggers a click event on the Bookmarks icon (which was positioned at that position when the keyboard was still visible). See screenshot (2, yellow)

2. The viewport jumps back to the correct position if you slightly scroll somewhere on the page - unless it has `overscroll-behavior: none (I assume this is because it prevents the scroll to bubble up to the root)

3. If the entire page (or element that fills the entire page) has `position: fixed`, everything *seems* to be in the correct position but touch events fire in the wrong place. Touch events in the lower part of the screen (where the keyboard was displayed) do not respond at all. This includes scrolling: the entire lower part of the viewport can not be scrolled.

4. When 2 and 3 are combined, it completely breaks the site. The only way to restore normal behavior is to re-open the keyboard and dismiss it via a tap somewhere in the document. 

I'm not sure if all iPads are affected, I only have an iPad Mini (5th gen). iPad (10th gen) simulator works as expected. iPhone also unaffected. It only affects installed web apps, the same steps in regular Safari work as expected.
Comment 1 ik 2023-04-01 11:06:32 PDT
A couple of other/additional observations, maybe this will help or they may be separate issues (let me know plz)

I've managed to mostly work around the issue by using the Visual Viewport API and resize the html and body via javascript. Weird thing is that I have to (re)register the listener every time the input is focused or it won't fire more than once:

1. On input focus: add a visualViewport 'resize' listener (it will fire almost immediately)

2. In the resize listener, set the height of html and body to match visualViewport.height, then scroll both to '0'.

3. On input blur: unregister the 'resize' listener and unset html and body height, then scroll both to '0'

The second thing I found out is that the document loses focus when I dismiss the keyboard using the dedicated button, at least according to the page lifecycle library I use (https://github.com/GoogleChromeLabs/page-lifecycle). This doesn't happen on iPhone, just on iPad (Mini, again, I don't have other devices to test this with). Scrolling doesn't re-focus it, I have to really tap something.
Comment 2 Radar WebKit Bug Importer 2023-04-01 11:16:12 PDT
<rdar://problem/107512434>
Comment 3 philipp.pirrung+apple 2023-04-19 06:18:57 PDT
I can confirm that this issue exists on iPad Pro (11-inch) (3rd generation) with iPadOS 16.3.1.

Furthermore I was able to work around this behaviour with the following:
window.visualViewport.addEventListener("resize", () => {
    window.requestAnimationFrame(() => {
        if (window.visualViewport.height == document.documentElement.getBoundingClientRect().height) {
            document.documentElement.scrollTop = 0;
        }
    });
});

It seems that setting the scrollTop after the resize has finished, updates the visualViewport. Checking if the height of the visualViewport matches the height of the document ensures that the scrollTop is only "updated" when the full screen is available again (i.e. the keyboard has been dismissed).
While I'm in no way happy with this work-around, it seems to fix the originally described behaviour in all use-cases I've tested.
Comment 4 ik 2023-04-20 01:06:15 PDT
Just wanted to add to philipp's comment (#3):

One important thing to note here is that the rAF() is needed because the viewport resize event can fire before the visualViewport.height reflects the new height. I can't test right now, but I remember that the behavior was different between regular Safari and installed web apps; one of them seemed to always work as expected without rAF(), the other needed it.

So instead of this:

```
visualViewport.addEventListener('resize', () => {
  console.log( visualViewport.height );
});
```

You need this:

```
visualViewport.addEventListener('resize', () => {
  requestAnimationFrame( () => {
    console.log( visualViewport.height );
  });
});
```

I'll try to find some time to file a separate bug for this but if someone beats me to it: please do :))
Comment 5 James 2024-09-18 08:51:28 PDT
Just wanted to add that this is an issue I've experienced before in the Whatsapp PWA (Web App), on an iPad Air 3 (2019) running iOS 17.6.