diff --git a/README.md b/README.md index c1b2663..a79277f 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Various UI components have a "modal" or "popup" behavior. For example: An important common feature of these components is that they are designed to be easy to close, with a uniform interaction mechanism for doing so. Typically, this is the Esc key on desktop platforms, and the back button on some mobile platforms (notably Android). Game consoles also tend to use a specific button as their "close/cancel/back" button. Finally, accessibility technology sometimes provides specific close signals for their users, e.g. iOS VoiceOver's "dismiss an alert or return to the previous screen" [gesture](https://support.apple.com/guide/iphone/learn-voiceover-gestures-iph3e2e2281/ios#:~:text=Dismiss%20an%20alert%20or%20return%20to%20the%20previous%20screen). -We define a **close signal** as a _platform-mediated_ interaction that's intended to close an in-page component. This is distinct from _page-mediated_ interactions, such as clicking on an "x" or "Done" button, or clicking on the backdrop outside of the modal. +We define a **close signal** as a _platform-mediated_ interaction that's intended to close an in-page component. This is distinct from _page-mediated_ interactions, such as clicking on an "x" or "Done" button, or clicking on the backdrop outside of the modal. See our [platform-specific notes](./platform-implementation-notes.md) for more details on what close signals look like on various platforms. Currently, web developers have no good way to handle these close signals. This is especially problematic on Android devices, where the back button is the traditional close signal. Imagine a user filling in a twenty-field form, with the last item being a custom date picker modal. The user might click the back button hoping to close the date picker, like they would in a native app. But instead, the back button navigates the web page's history tree, likely closing the whole form and losing the filled information. But the problem of how to handle close signals extends across all operating systems; implementing a good experience for users of different browsers, platforms, and accessibility technologies requires a lot of user agent sniffing today. @@ -27,7 +27,7 @@ Our primary goals are as follows: - Discourage platform-specific code for handling modal close signals, by providing an API that abstracts away the differences. -- Allow future platforms to introduce new close signals (e.g., a "swipe away" gesture), such that code written against the API proposed here automatically works on such platforms. +- Allow future platforms to introduce new close signals (e.g., the introduction of the swipe-from-the-sides dismiss gesture in Android 10), such that code written against the API proposed here automatically works on such platforms. - Prevent abuse that traps the user on a given history state by disabling the back button's ability to actually navigate backward in history. @@ -47,7 +47,9 @@ The following is a goal we wish we could meet, but don't believe is possible to On desktop platforms, this problem is currently solved by listening for the `keydown` event, and closing the component when the Esc key is pressed. Built-in platform APIs, such as [``](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/showModal), [fullscreen](https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API), or [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date), will do this automatically. Note that this can be easy to get wrong, e.g. by accidentally listening for `keyup` or `keypress`. -On mobile platforms, getting the right behavior is significantly harder. First, platform-specific code is required, to do nothing on iOS (or, perhaps, try to detect VoiceOver and then intercept its "z" gesture?), capture the back button on Android, and listen for gamepad inputs on PlayStation browser. Next, capturing the back button on Android requires manipulating the history list using the [history API](https://developer.mozilla.org/en-US/docs/Web/API/History_API). This is a poor fit for several reasons: +On mobile platforms, getting the right behavior is significantly harder. First, platform-specific code is required, to do nothing on iOS, capture the back button or swipe-from-the-sides gesture on Android, and listen for gamepad inputs on PlayStation browser. + +Next, capturing the back button on Android requires manipulating the history list using the [history API](https://developer.mozilla.org/en-US/docs/Web/API/History_API). This is a poor fit for several reasons: - This UI state is non-navigational, i.e., the URL doesn't and shouldn't change when opening a component. When the page is reloaded or shared, web developers generally don't want to automatically re-open the same component. diff --git a/platform-implementation-notes.md b/platform-implementation-notes.md index ecbef06..75c01e3 100644 --- a/platform-implementation-notes.md +++ b/platform-implementation-notes.md @@ -1,17 +1,51 @@ -# Close watcher platform-specific implementation notes +# Close watchers on today's platforms -## Android: interaction with fragment navigation +## Close signals -Consider a popup which contains a lot of text, such as a terms of service with a table of contents, or a preview overlay that shows an interactive version of this very document. Within such a popup, it'd be possible to perform fragment navigation. Although these situations are hopefully rare in practice, we need to figure out how they impact the proposed API. +Although the specification is specifically designed to be future-extensible to new platforms, or to adapt if a platform changes how close signals are communicated, here is how we currently anticipate close signals being implemented. -In particular, the problem case is again Android, where the back button serves as both a potential close signal and a way of navigating the history list. We suggest that for Android, the back button still acts as a close signal whenever any `CloseWatcher` is active. That is, it should only do fragment navigation back through the history list when there are no active close watchers, even if fragment navigation has occurred since the popup was shown and the `CloseWatcher` was constructed. +### On platforms with keyboards -This does mean that the popup could be closed, while the page's current fragment and its history list still reflect fragment navigations that were performed inside the popup. The application would need to clean up such history entries to give a good user experience. We believe this is best done through a history-specific API, e.g. the existing `window.history` API or the proposed [app history API](https://github.com/WICG/app-history), since this sort of batching and reversion of history entries is a generic problem and not popup-specific. +Pressing down on the Esc key is a close signal. + +Notes: + +* This includes not just desktop platforms, but also mobile or tablet platforms with keyboards attached. In some cases these keyboards don't have an Esc key explicitly, but do have an equivalent [key combination](https://osxdaily.com/2019/04/12/how-type-escape-key-ipad/). +* This includes virtual keyboards with Esc keys or key combinations available, e.g. on Windows 11. +* From what we've seen, most operating systems specifically treat pressing down, and not releasing, as the close signal. + +### When assistive technology is being used + +The current plan of the [AOM group](https://github.com/WICG/aom) is that, when an assistive technology's dismiss or escape gesture is used, this will [synthesize an Esc keypress](https://github.com/WICG/aom/blob/gh-pages/explainer.md#user-action-events-from-assistive-technology). Then, implementations can treat the pressing-down part of this synthesized keypress as a close signal, giving [indistinguishable behavior](https://w3ctag.github.io/design-principles/#do-not-expose-use-of-assistive-tech) between assistive technology users and keyboard users. + +### On Android + +If in the 2-button navigation or 3-button navigation mode is being used for [system navigation](https://support.google.com/android/answer/9079644?hl=en), then the software back button at the bottom of the screen is a close signal. + +If gesture navigation mode is being used, then swiping from the left or right of the screen is a close signal. + +Notes: -The alternative is for implementations to treat back button presses as fragment navigations, only treating them as close signals once the user has returned to the same history entry as the popup was opened in. But discussion with framework authors indicated this would be harder to deal with and reason about. In particular, this would introduce significant divergences between platforms: on Android, closing a popup would not require any history-stack-cleaning, whereas on other platforms it would. So, in accordance with our [goal](#goals) of discouraging platform-specific code, we encourage implementations to go with the model suggested above, which enables web developers to use the same cleanup code without platform-specific branches. (Or they can avoid the problem altogether, by not providing opportunities for fragment navigation while popups are opened.) +* In the Chromium implementation, we found that we automatically got support for all three modes of system navigation from the same code path. Hooray! +* Android devices can also be have keyboards attached, or be used with assistive technology; the categories are not mutually exclusive. +* Because the back button is also used, in web apps, for navigating through the session history, there are a number of complications. Anti-back-trapping measures are discussed [in the README](./README.md#abuse-analysis), and below we have an appendix on [interaction with fragment navigation](#appendix-android-and-fragment-navigation). -## iOS: VoiceOver and keyboard integration +### On iOS -iOS does not have a system-provided back button like Android does. But it still provides some close signals. In particular, [VoiceOver users have a specific gesture they can use](https://support.apple.com/guide/iphone/learn-voiceover-gestures-iph3e2e2281/ios#:~:text=Dismiss%20an%20alert%20or%20return%20to%20the%20previous%20screen), and iOS users who have connected a keyboard to their mobile device can use the Esc key (or [key combination](https://osxdaily.com/2019/04/12/how-type-escape-key-ipad/)). +As of now, we expect that iOS would only trigger close signals if assistive technology is being used, or if a keyboard is attached to the device. Additionally, we note that the Apple Human Interface Guidelines [indicate](https://developer.apple.com/design/human-interface-guidelines/ios/app-architecture/modality/) that a swipe-down gesture can be used to close certain types of modals. However, we suspect this would not be suitable for integration with `CloseWatcher`, since there's no guarantee that a `CloseWatcher` is being used for a sheet-type modal (as opposed to a fullscreen-type modal, or a non-modal control like a sidebar or picker). We'd welcome more thoughts from iOS experts. + +### On game consoles + +We are not very confident in the desired user experience for console web browsers, or other apps implemented on consoles using web technology. But in our brief experience, most consoles have a button that is usually used as a "cancel" or "back" button. We expect that this being a close signal might work well. + +## Appendix: Android and fragment navigation + +Consider a popup which contains a lot of text, such as a terms of service with a table of contents, or a preview overlay that shows an interactive version of this very document. Within such a popup, it'd be possible to perform fragment navigation. Although these situations are hopefully rare in practice, we need to figure out how they impact the proposed API. + +We suggest that for Android, the back button still acts as a close signal whenever any `CloseWatcher` is active. That is, it should only do fragment navigation back through the history list when there are no active close watchers, even if fragment navigation has occurred since the popup was shown and the `CloseWatcher` was constructed. + +This does mean that the popup could be closed, while the page's current fragment and its history list still reflect fragment navigations that were performed inside the popup. The application would need to clean up such history entries to give a good user experience. We believe this is best done through a history-specific API, e.g. the existing `window.history` API or the proposed [app history API](https://github.com/WICG/app-history), since this sort of batching and reversion of history entries is a generic problem and not popup-specific. + +The alternative is for implementations to treat back button presses as fragment navigations, only treating them as close signals once the user has returned to the same history entry as the popup was opened in. But discussion with framework authors indicated this would be harder to deal with and reason about. In particular, this would introduce significant divergences between platforms: on Android, closing a popup would not require any history-stack-cleaning, whereas on other platforms it would. So, in accordance with our [goal](./README.md#goals) of discouraging platform-specific code, we encourage implementations to go with the model suggested above, which enables web developers to use the same cleanup code without platform-specific branches. (Or they can avoid the problem altogether, by not providing opportunities for fragment navigation while popups are opened.) diff --git a/spec.bs b/spec.bs index df98420..5e8deee 100644 --- a/spec.bs +++ b/spec.bs @@ -22,6 +22,7 @@ Assume Explicit For: yes
 spec: ui-events; urlPrefix: https://w3c.github.io/uievents/#
   text: keydown; type: event; url: event-type-keydown
+  text: keyup; type: event; url: event-type-keyup