|
| 1 | +--- |
| 2 | +menu: Active Proposals |
| 3 | +name: Invoker Commands Future (Explainer) |
| 4 | +layout: ../../layouts/ComponentLayout.astro |
| 5 | +--- |
| 6 | + |
| 7 | +- Authors: [Keith Cirkel](https://github.com/keithamus), [Luke Warlow](https://github.com/lukewarlow) |
| 8 | + |
| 9 | +**NOTE:** See the original explainer for the core proposal [here](/components/invokers.explainer). |
| 10 | + |
| 11 | +{/* START doctoc generated TOC please keep comment here to allow auto update */} |
| 12 | +{/* DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE */} |
| 13 | + |
| 14 | +# Invoker Buttons |
| 15 | + |
| 16 | +## Introduction |
| 17 | + |
| 18 | +This document outlines potential future enhancements to [Invoker Commands](/components/invokers.explainer). |
| 19 | + |
| 20 | +All commands noted here are ideas only and are not included in the initial release of this feature. |
| 21 | + |
| 22 | +### Defaults |
| 23 | + |
| 24 | +Depending on the target set by `commandfor`, invoking the button will trigger |
| 25 | +additional behaviours alongside the event dispatch, depending on the value of |
| 26 | +`command`. The following table represents ideas on how built-in invocations on specific |
| 27 | +element types are handled. These need further design on exactly how |
| 28 | +they will behave based on implications such as accessibility, security, |
| 29 | +interactivity, and how the button may need to respond to such actions. |
| 30 | + |
| 31 | +| Invokee Element | `command` hint | Behaviour | |
| 32 | +| :-------------------- | :--------------------- | :------------------------------------------------------------------------------------------------------------- | |
| 33 | +| `<dialog>` | `'request-close'` | If the `<dialog>` is `open`, request to close and use the button `value` for returnValue. Similar to `.requestClose(value) | |
| 34 | +| `<* openable>` | `'toggle-openable'` | Opens the `openable` if closed, otherwise closes. Similar to `.toggleOpenable()` | |
| 35 | +| `<* openable>` | `'close-openable'` | Closes the `openable` if open, otherwise does nothing. Similar to `.closeOpenable()` | |
| 36 | +| `<* openable>` | `'open-openable'` | Opens the `openable` if closed, otherwise does nothing. Similar to `.openOpenable()` | |
| 37 | +| `<details>` | `'toggle'` | If the `<details>` is `open`, then close it, otherwise open it | |
| 38 | +| `<details>` | `'open'` | If the `<details>` is not `open`, then open it | |
| 39 | +| `<details>` | `'close'` | If the `<details>` is `open`, then close it | |
| 40 | +| `<dialog>` | `'toggle'` | If the `<dialog>` is `open`, then close it and use the button `value` for returnValue, otherwise open as modal | |
| 41 | +| `<select>` | `'show-picker'` | Shows the native picker. Similar to `.showPicker()` on the invokee | |
| 42 | +| `<input>` | `'show-picker'` | Shows the native picker. Similar to `.showPicker()` on the invokee | |
| 43 | +| `<video>` | `'play-pause'` | If the video is not playing, plays the video. Otherwise pauses it. Similar to `el.playing = !el.playing` | |
| 44 | +| `<video>` | `'pause'` | If the video is playing, pause the video. Similar `.playing = false` | |
| 45 | +| `<video>` | `'play'` | If the video is not playing, play the video. Similar to `.playing = true` | |
| 46 | +| `<video>` | `'toggle-muted'` | If the video is muted, it unmutes the video, otherwise it mutes it. Similar to `el.muted = !el.muted` | |
| 47 | +| `<audio>` | `'play-pause'` | If the audio is not playing, plays the audio. Otherwise pauses it. Similar to `el.playing = !el.playing` | |
| 48 | +| `<audio>` | `'pause'` | If the audio is playing, pause the audio. Similar `.playing = false` | |
| 49 | +| `<audio>` | `'play'` | If the audio is not playing, play the audio. Similar to `.playing = true` | |
| 50 | +| `<audio>` | `'toggle-muted'` | If the audio is muted, it unmutes the audio, otherwise it mutes it. Similar to `el.muted = !el.muted` | |
| 51 | +| `<*>` | `'toggle-fullscreen'` | If the element is fullscreen, then exit, otherwise request to enter | |
| 52 | +| `<*>` | `'request-fullscreen'` | Request the element to enter into 'fullscreen' mode | |
| 53 | +| `<*>` | `'exit-fullscreen'` | If the element is fullscreen, then exit | |
| 54 | +| `<input type=number>` | `'step-up'` | Call `.stepUp()` on the invokee | |
| 55 | +| `<input type=number>` | `'step-down'` | Call `.stepDown()` on the invokee | |
| 56 | + |
| 57 | +> Note: Ideas are welcome. Please submit an issue if you have one! |
| 58 | +
|
| 59 | +Further to the initial ship we're also exploring implicit `command` or implicit |
| 60 | +`commandfor` values where the value can easily be inferred. |
| 61 | + |
| 62 | +### Accessibility |
| 63 | + |
| 64 | +For built-in behaviours `command` attribute maps to specific accessibility |
| 65 | +mappings which are placed on the button. The button may also use the |
| 66 | +`commandfor` referenced element to gather other details (for example the state |
| 67 | +of the element) to reflect that state on the button. |
| 68 | + |
| 69 | +These mappings will happen implicitly on the browsers Accessible Nodes, and so |
| 70 | +while (for simplicity) this section refers to various `aria-` attributes, |
| 71 | +buttons will not sprout these attributes in the DOM, but the effective |
| 72 | +equivalent will be exposed to Assistive Technologies. |
| 73 | + |
| 74 | +#### Buttons with `command=request-close` |
| 75 | + |
| 76 | +Buttons with this dialog command will implicitly receive `aria-details=IDREF`, |
| 77 | +where `IDREF` matches that of the `commandfor` attribute, while the dialog is |
| 78 | +in the showing state, and the button is not a descendant of the dialog. |
| 79 | + |
| 80 | +<details> |
| 81 | +<summary>Why?</summary> |
| 82 | + |
| 83 | +A button that cancels a dialog is very typically found inside of the dialog, but |
| 84 | +in some cases it may be found outside, perhaps as a "toggle" style button which |
| 85 | +opens and closes a dialog as non-modal. This button may be used to close an open |
| 86 | +dialog that is shown as non-modal. It may be useful for a user to traverse into |
| 87 | +the dialog before closing it, for example to check if they have unsaved changes |
| 88 | +within the dialog. |
| 89 | + |
| 90 | +</details> |
| 91 | + |
| 92 | +<br/><br/> |
| 93 | + |
| 94 | +Buttons will also implicitly receive an `aria-expanded` value, if they are not a |
| 95 | +descendant of the `commandfor=` referenced element. The state of the |
| 96 | +`aria-expanded` value will map to the state of the dialog's openness. When the |
| 97 | +dialog is open the button will have `aria-expanded="true"`, when closed, |
| 98 | +`aria-expanded="false"`. This will be recomputed whenever the dialog changes |
| 99 | +state, such that the button always reflects the state of openness. |
| 100 | + |
| 101 | +<details> |
| 102 | +<summary>Why?</summary> |
| 103 | + |
| 104 | +A button that cancels a dialog is very typically found inside of the dialog, but |
| 105 | +in some cases it may be found outside, perhaps as a "toggle" style button which |
| 106 | +opens and closes a dialog as non-modal. |
| 107 | + |
| 108 | +Buttons outside of the dialog may be used to close an open dialog that is shown |
| 109 | +as non-modal. It may be useful for a user to traverse into the dialog before |
| 110 | +closing it, for example to check if they have unsaved changes within the dialog. |
| 111 | +It may also be useful to know if the dialog is already closed (as in its |
| 112 | +`aria-expanded` state is false), as this may help the user make a decision to |
| 113 | +whether or not they action the close button. |
| 114 | + |
| 115 | +</details> |
| 116 | + |
| 117 | +<br/><br/> |
| 118 | + |
| 119 | +<details> |
| 120 | +<summary>Other considerations not explicitly proposed.</summary> |
| 121 | + |
| 122 | +##### aria-pressed |
| 123 | + |
| 124 | +Given elements will have `aria-expanded`, adding `aria-pressed` would be |
| 125 | +confusing or redundant, and as such won't be proposed for these buttons. |
| 126 | + |
| 127 | +##### aria-controls |
| 128 | + |
| 129 | +While `aria-controls` attempts to establish a similar style of relationship to |
| 130 | +`aria-details`, `aria-details` sees broader support among various assistive |
| 131 | +technologies, and it would be redundant to add both. |
| 132 | + |
| 133 | +</details> |
| 134 | + |
| 135 | +<br/><br/> |
| 136 | + |
| 137 | +#### Other built-in `command=` types |
| 138 | + |
| 139 | +Further built-in commands will be proposed on a case-per-case basis, |
| 140 | +and additional aria or other logic will be considered with those at the time. |
| 141 | + |
| 142 | +### Security |
| 143 | + |
| 144 | +See the main [Invoker Commands explainer](/components/invokers.explainer#security) for an overview of the |
| 145 | +security considerations. |
| 146 | + |
| 147 | +**Note**: The security considerations for the proposed commands are not yet complete and will be specified in more detail |
| 148 | +in the future. |
| 149 | + |
| 150 | +Below is a brief summary of the security considerations for the commands proposed above: |
| 151 | + |
| 152 | +#### Details |
| 153 | + |
| 154 | +This proposal aims to allow opening and closing `<details>` elements with |
| 155 | +`invoketarget`. This is not considered to be new capability, as it is already |
| 156 | +possible to do this with the `<summary>` element. |
| 157 | + |
| 158 | +#### Input |
| 159 | + |
| 160 | +It should be noted that input pickers (like the file picker dialog) render native |
| 161 | +controls outside of the bounds of the document frame (iframe or browser window). |
| 162 | +Due to this, the security implications of invoking these elements should be |
| 163 | +carefully considered. |
| 164 | + |
| 165 | +Scripts can call `.showPicker()` on form elements, and the picker will open. |
| 166 | +There are some cross-origin restrictions, for example calling `.showPicker()` |
| 167 | +on elements in a cross-origin context only works for `<input type=file>` or |
| 168 | +`<input type=color>`. |
| 169 | + |
| 170 | +It is also possible to wrap an `<input>` in a `<label>`, where invoking the |
| 171 | +label will open the picker. This is quite common practice when trying to style |
| 172 | +`<input type=file>` for example; dressing the `<label>` to look like a |
| 173 | +`<button>` and hiding the `<input>`. |
| 174 | + |
| 175 | +This proposal allows showing the pickers of input elements, for example an |
| 176 | +invoker can target an `<input type=file>` and clicking the invoker will open |
| 177 | +the operating systems file picker dialog. As stated above, this is already |
| 178 | +possible with `<label>` elements, but care should be taken to ensure that |
| 179 | +the same cross-origin restrictions apply for Invokers as they do for scripting. |
| 180 | + |
| 181 | +#### Fullscreen elements |
| 182 | + |
| 183 | +Turning an element into a fullscreen needs to be carefully consdidered. |
| 184 | +Scripting already allows this via the `.requestFullscreen()` API which, as the |
| 185 | +name suggests, may not always be successful given the UA context. This API |
| 186 | +requires the document context to be active, for example requiring a user |
| 187 | +activation (this is true of invokers in the general case), it also requires |
| 188 | +the Permissions Policy to allow for this behaviour, and requires the element to |
| 189 | +not already be on the Top Layer. |
| 190 | + |
| 191 | +All of these constraints should also be true for invokers opening fullscreen |
| 192 | +elements. This means the only _significant_ new behavioural difference between |
| 193 | +an invoker opening a fullscreen element vs the existing behaviour which |
| 194 | +requires scripting, is that sanboxed iframes which disallow scripting but allow |
| 195 | +fullscreen may now declare a button that can fullscreen. Consider the following: |
| 196 | + |
| 197 | +```html |
| 198 | +<iframe sandbox allow="fullscreen"> |
| 199 | + <html id="invokee"> |
| 200 | + <button type="button" commandfor="invokee" command="toggleFullscreen">Invoke</button> |
| 201 | + <button onclick="invokee.requestFullscreen()">Go fullscreen</button> |
| 202 | + </html> |
| 203 | +</iframe> |
| 204 | +```` |
| 205 | + |
| 206 | +In this example, the `<button onclick>` will do nothing as the iframe is |
| 207 | +sandboxed and does not allow scripting. The `<button invoketarget>` will |
| 208 | +fullscreen the element, however, as the `allow=fullscreen` Permission Policy is |
| 209 | +enabled to allow fullscreen. |
| 210 | + |
| 211 | +This is considered to be a low security threat, as it requires opt-in to the |
| 212 | +fullscreen Permission Policy explitly via iframe attributes or scripting. We |
| 213 | +will, however, continue to explore if this should be a possibility. |
| 214 | + |
| 215 | +#### Media Elements |
| 216 | + |
| 217 | +This proposal allows video and audio elements to be controlled with buttons |
| 218 | +outside of the browsers native video controls. This includes playing, pausing, |
| 219 | +and toggling mute/unmute. Today this is only possible using scripting via the |
| 220 | +equivalent scripting APIs: `.play()` / `.pause()` / `.muted=`. These APIs are |
| 221 | +guarded by Permissions Policy, and so should the invoker equivalent. Some User |
| 222 | +Agents can be configured to reject calls to `.play()`, and this should also be |
| 223 | +true of invokers. As such the key new capability here is the ability to call |
| 224 | +these APIs without scripting enabled. This is effectively the same concern as |
| 225 | +fullscreen; the security model is guarded around the Permissions Policy. |
| 226 | + |
| 227 | +There is also additional concern around the media element invokers being able |
| 228 | +to circumvent autoplay policies. Invokers should not be able to circumvent |
| 229 | +these. By design, invokers require a user interaction (clicking the button) |
| 230 | +and so this should not circumvent autoplay policies. |
| 231 | + |
| 232 | +#### Further proposals... |
| 233 | + |
| 234 | +There are continued suggestions for new capabilities of Invokers, such as |
| 235 | +[invoking picture-in-picture](https://github.com/openui/open-ui/issues/916). |
| 236 | + |
| 237 | +Each of these will need to be taken into consideration on a case-per-case |
| 238 | +basis. The following questions will need to be answered for each of these: |
| 239 | + |
| 240 | +- Is it possible for the user to do this today with scripting? |
| 241 | +- Is it possible for the user to do this today without scripting? |
| 242 | +- Does this behaviour bypass scripting sandboxing rules, such as |
| 243 | + Permissions Policy or iframes? |
| 244 | +- Does this enable buttons to invoke content that appears outside of the |
| 245 | + browser frame? |
| 246 | + |
| 247 | +### PAQ (Potentially Asked Questions) |
| 248 | + |
| 249 | +#### Do we have to always supply both? Can't we make `command` or `commandfor` implicit? |
| 250 | + |
| 251 | +The original proposal had the concept of an "auto" `command` value which would |
| 252 | +determine an explicit command based on various heuristics, such as the target |
| 253 | +element. This has been deferred for the initial ship, but may be explored |
| 254 | +further. This is considered out of scope for the initial ship, however. |
| 255 | + |
| 256 | +We may also explore the possibility of making `commandfor` implicit, for example |
| 257 | +if a button is a descendant of a dialog, omitting `commandfor` may make sense. |
| 258 | +This is also considered out of scope for the initial ship. |
0 commit comments