Repository navigation
Expand file tree
/
Copy pathindex.bs
More file actions
489 lines (397 loc) · 20.2 KB
/
Copy pathindex.bs
File metadata and controls
489 lines (397 loc) · 20.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
<pre class='metadata'>
Title: Autofill Event
Shortname: autofill-event
Level: 1
Status: CG-DRAFT
Group: WICG
URL: https://wicg.github.io/autofill-event/
Repository: WICG/autofill-event
Editor: Yoav Weiss, Shopify https://shopify.com, yoav.weiss@shopify.com, w3cid 58673
Abstract: This specification defines an event that fires when the user agent is about to autofill form fields, allowing developers to adapt forms dynamically based on the autofilled values.
Markup Shorthands: markdown yes
</pre>
<pre class='anchors'>
urlPrefix: https://html.spec.whatwg.org/multipage/; spec: HTML
type: dfn
urlPrefix: form-control-infrastructure.html
text: autofill; url: #autofill
text: autofill field name; url: #autofill-field
urlPrefix: forms.html
text: form; url: #the-form-element
text: form control; url: #form-associated-element
type: interface
urlPrefix: dom.html
text: Document; url: #document
type: attribute
urlPrefix: form-control-infrastructure.html
text: autocomplete; url: #attr-fe-autocomplete
urlPrefix: https://dom.spec.whatwg.org/; spec: DOM
type: interface
text: Event; url: #interface-event
text: EventTarget; url: #interface-eventtarget
type: dfn
text: dispatch; url: #concept-event-dispatch
text: fire an event; url: #concept-event-fire
</pre>
<pre class=link-defaults>
spec:dom; type:interface; text:Event
spec:html; type:dfn; text:autofill
spec:infra; type:dfn; text:list
spec:infra; type:dfn; text:boolean
spec:dom; type:dfn; for:/; text:document
</pre>
# Introduction # {#intro}
*This section is non-normative.*
Autofill is a key feature of the web that reduces friction for millions of users everyday.
It is widely used on login screens, e-commerce, and contact forms, to name a few examples.
For commerce and checkout flows in particular, autofill provides significant benefit to both
the buyer experience and merchant outcomes.
At the same time, autofill on the web has several deficiencies: partial or incomplete fill,
cross-browser interoperability issues, and high cost to implement and maintain for developers.
One key example is address autofill which, when implemented correctly, is a dynamic form:
different geographies have different shapes and requirements for address input. Selecting a
country requires changing the form (re-ordering fields, adding and removing fields) and depends
on input from the user, but [=autofill=] mediates this interaction and may not respond correctly
to the rendered form.
The current 'industry standard' solution requires use of hidden form fields that try to anticipate
and capture the right information and then surface it to the user. This solution is brittle and
complex. Worse, it entrenches use of hidden fields to power legitimate use cases, but the same
technique can be and is also abused by bad actors.
This specification introduces an {{AutofillEvent}} that fires before [=autofill=] values are
committed to form fields, allowing developers to:
1. Inspect the values that are about to be autofilled
2. Adapt the form dynamically based on those values (e.g., showing country-specific address fields)
3. Signal to the user agent when the form is ready to accept the autofill values
## Goals ## {#goals}
* **Users**: Make autofill more reliable and pave the path for potential future deprecation of filling hidden fields
* **Site developers**: Reduce complexity required to tackle address autofill using hidden fields, and reduce the need for implementation-specific hacks.
* **Autofill providers**: Give third-party autofill providers hooks that work seamlessly with the new API
## Example ## {#example}
<div class="example">
A checkout form that dynamically adds country-specific address fields based on the autofilled country value:
```html
<form id="checkout">
<input autocomplete="name" placeholder="Full name">
<input autocomplete="street-address" placeholder="Street address">
<input autocomplete="address-level2" placeholder="City">
<input autocomplete="postal-code" placeholder="Postal code">
<input autocomplete="country" placeholder="Country">
<!-- State/province field will be added dynamically for countries that need it -->
</form>
<script>
document.addEventListener('autofill', async function(event) {
// Find the country value in the autofill values
let countryValue = null;
let formElement = null;
// Find the country element and value
for (const [element, value] of event.autofillValues) {
if (element.autocomplete === 'country') {
countryValue = value;
formElement = element.form;
break;
}
}
// If filling a US address, we need to add a state selector
if (event.refill !== null) {
if (countryValue === 'US') {
// Check if we already have a state field
const existingState = formElement.querySelector('[autocomplete="address-level1"]');
if (!existingState) {
// Create and insert a state selector for US addresses
const stateSelect = document.createElement('select');
stateSelect.autocomplete = 'address-level1';
stateSelect.name = 'state';
stateSelect.innerHTML = `
<option value="">Select state...</option>
<option value="AL">Alabama</option>
<option value="AK">Alaska</option>
<option value="AZ">Arizona</option>
<option value="CA">California</option>
<option value="CO">Colorado</option>
<!-- ... other states ... -->
<option value="WY">Wyoming</option>
`;
// Insert before the postal code field
const postalCode = formElement.querySelector('[autocomplete="postal-code"]');
postalCode.parentNode.insertBefore(stateSelect, postalCode);
// Signal that the form has been modified and autofill should be refilled
await event.refill();
}
} else if (countryValue === 'UK') {
... Add UK-specific logic
}
} else {
// The UA doesn't support refilling. Extract values from hidden fields,
// or notify the user they need to complete values manually.
}
});
</script>
```
The {{AutofillEvent/autofillValues}} attribute returns a list of [=autofill value entries=], where each
entry is a tuple of the target {{HTMLElement}} and the value to be filled. The developer can
iterate over these entries to inspect the pending autofill data and determine if the form needs
to be adapted.
Once the form structure was changed (e.g., to add country-specific fields, potentially asynchronously),
the developer calls {{AutofillEvent/refill}}. This signals to the user agent that the form has been
modified and that it should retry the autofill operation with the updated form structure.
Note: The {{AutofillEvent/refill}} attribute is `null` on the second dispatch of the event
(after form modifications), preventing infinite loops.
</div>
# Concepts # {#concepts}
## Autofill Value Entries ## {#autofill-values-concept}
An <dfn>autofill value entry</dfn> is a tuple consisting of:
1. An {{HTMLElement}} — the [=form control=] that will receive the autofilled value
2. A {{DOMString}} — the value from the user's autofill profile to be filled
The user agent matches form controls to autofill data based on the control's
{{autocomplete}} attribute (see [=autofill field name=] in [[HTML]]).
It may also use [=implementation-defined=] heuristics.
## Refill Operation ## {#refill-concept}
A <dfn export>refill operation</dfn> allows the developer to signal that the form structure was
modified in response to the autofill values, and that the user agent should attempt to fill
the form again.
# The AutofillEvent Interface # {#autofill-event-interface}
<pre class="idl">
[Exposed=Window]
interface AutofillEvent : Event {
constructor(DOMString type, optional AutofillEventInit eventInitDict = {});
readonly attribute FrozenArray<AutofillValueEntry> autofillValues;
readonly attribute RefillCallback? refill;
};
callback RefillCallback = Promise<undefined> ();
dictionary AutofillEventInit : EventInit {
sequence<AutofillValueEntry> autofillValues = [];
boolean allowRefill = true;
};
typedef sequence<any> AutofillValueEntry;
// AutofillValueEntry is a tuple of [HTMLElement, DOMString]
// where the first element is the form control and the second is the value to fill
</pre>
The {{AutofillEvent}} interface represents an event that is [=dispatch|dispatched=] when the
user agent is about to [=autofill=] form fields.
## Attributes ## {#autofill-event-attributes}
The <dfn attribute for=AutofillEvent>autofillValues</dfn> attribute returns a list of
[=autofill value entries=]. Each entry is a tuple where the first element is an {{HTMLElement}}
(the form control to be filled) and the second element is a {{DOMString}} (the value to fill).
The <dfn attribute for=AutofillEvent>refill</dfn> attribute returns a {{RefillCallback}} or
`null`. When not `null`, calling this callback returns a {{Promise}} that, when awaited, signals
to the user agent that the form structure has been modified and autofill should be retried.
The {{AutofillEvent/refill}} attribute is `null` when:
* The event is dispatched as a retry after a previous call to {{AutofillEvent/refill}}, or
* The user agent determines that refilling is not appropriate for this autofill operation.
This prevents infinite loops where a page continuously modifies the form and requests refills.
Each {{AutofillEvent}} has an associated <dfn for=AutofillEvent>autofill values list</dfn>
(a [=list=] of [=autofill value entries=]), initially an empty [=list=].
Each {{AutofillEvent}} has an associated <dfn for=AutofillEvent>allow refill flag</dfn>
(a [=boolean=]), initially true.
Each {{AutofillEvent}} has an associated <dfn for=AutofillEvent>dispatch timestamp</dfn>
(a {{DOMHighResTimeStamp}}), initially 0.
Each {{AutofillEvent}} has an associated <dfn for=AutofillEvent>refill pending flag</dfn>
(a [=boolean=]), initially false.
# Processing Model # {#processing-model}
## Firing the Autofill Event ## {#firing-autofill-event}
<div algorithm>
To <dfn>fire an autofill event</dfn> given a [=document=] |document|, a [=list=] of [=autofill value entries=] |entries|, and a [=boolean=] |allowRefill|:
1. Let |event| be the result of [=creating an event=] using {{AutofillEvent}}.
1. Initialize |event|'s {{Event/type}} attribute to "<code>autofill</code>".
1. Initialize |event|'s {{Event/bubbles}} attribute to true.
1. Initialize |event|'s {{Event/cancelable}} attribute to false.
1. Set |event|'s [=AutofillEvent/autofill values list=] to |entries|.
1. Set |event|'s [=AutofillEvent/allow refill flag=] to |allowRefill|.
1. Set |event|'s [=AutofillEvent/dispatch timestamp=] to the [=current high resolution time=].
1. [=Dispatch=] |event| at |document|.
1. Perform the [=autofill=] operation on |document| with |entries|.
</div>
Note: The autofill operation is performed immediately after the event is dispatched. The
{{AutofillEvent/refill}} callback allows the page to request an additional autofill pass
after modifying the form structure, within an [=implementation-defined=] timeout window.
## Processing a Refill Request ## {#processing-refill}
<div algorithm="process a refill request">
To <dfn export>process a refill request</dfn> given an {{AutofillEvent}} |event|:
1. Let |now| be the [=current high resolution time=].
1. Let |elapsed| be |now| minus |event|'s [=AutofillEvent/dispatch timestamp=].
1. Let |refillTimeout| be an [=implementation-defined=] duration.
1. If |elapsed| is greater than |refillTimeout|, return [=a promise rejected with=] an "{{InvalidStateError}}" {{DOMException}}.
1. If |event|'s [=AutofillEvent/allow refill flag=] is false, return [=a promise rejected with=] an "{{InvalidStateError}}" {{DOMException}}.
1. If |event|'s [=AutofillEvent/refill pending flag=] is true, return [=a promise rejected with=] an "{{InvalidStateError}}" {{DOMException}}.
1. Set |event|'s [=AutofillEvent/refill pending flag=] to true.
1. Let |promise| be [=a new promise=].
1. Let |document| be |event|'s relevant [=document=].
1. Return |promise| and [=in parallel=]:
1. Let |entries| be the updated [=autofill value entries=] (re-matching the user's autofill data against the modified form).
1. [=Queue a task=] to:
1. [=Fire an autofill event=] with |document|, |entries|, and false.
1. Perform the [=autofill=] operation on |document| with |entries|.
1. [=Resolve=] |promise| with undefined.
</div>
Note: The timeout is [=implementation-defined=] to allow user agents flexibility in balancing
responsiveness with giving pages adequate time to call {{AutofillEvent/refill}}. User agents
SHOULD choose a timeout that provides a good user experience.
Note: On the retry dispatch (after {{AutofillEvent/refill}} was called), the
{{AutofillEvent/refill}} attribute is `null`, preventing infinite loops.
## Integration with HTML Autofill ## {#html-integration}
When a user agent's [=autofill=] mechanism is triggered (e.g., by user interaction with an
autofill UI), and the user selects values to fill, the user agent MUST [=fire an autofill event=]
before committing those values to form fields.
# The "full-address" Autocomplete Token # {#full-address}
This specification introduces a new [=autofill field name=]: "<dfn attr-value for=autocomplete>full-address</dfn>".
When a [=form control=] has the {{autocomplete}} attribute set to "<code>full-address</code>",
the user agent SHOULD request permission to access the user's complete address data, including
fields that may not be present in the current form.
This enables forms to receive comprehensive address information via the {{AutofillEvent}},
allowing them to dynamically adapt their structure to accommodate all relevant fields for the
user's address.
<div class="example">
Using `full-address` to enable comprehensive address autofill:
```html
<form autocomplete="full-address">
<input name="country" autocomplete="country">
<div id="dynamic-address-fields"></div>
</form>
```
</div>
# Security and Privacy Considerations # {#security-privacy}
The {{AutofillEvent}} exposes autofill values to JavaScript before they are committed to form
fields. User agents SHOULD ensure that the event is only fired after explicit user consent to
autofill has been given (e.g., by selecting an autofill suggestion from a dropdown).
The data passed to the event is limited to data that the user agent intends to fill into forms on the page, given that the API shape requires an element as the key to the autofill values.
Note that when the event fires after a `refill()` call, the form is likely to include new fields that weren't present the first time the user agent filled the form. The user agent should
still consider user consent before filling in the new form fields, as is already the case for automatic refills in user agents that support them.
## Third-Party Autofill Providers ## {#third-party-providers}
Browser extensions and third-party autofill providers (such as password managers) can
utilize this API by constructing and dispatching {{AutofillEvent}}s with the same structure,
ensuring consistent behavior regardless of the autofill source.
# WebDriver BiDi Integration # {#webdriver-bidi}
This section defines the integration with [[WEBDRIVER-BIDI]] for automated testing of autofill functionality.
## The autofill Module ## {#webdriver-bidi-module}
The <dfn export>autofill module</dfn> contains functionality for triggering autofill programmatically
in automated testing scenarios.
<pre highlight="cddl" class="cddl" data-cddl-module="remote-cddl">
AutofillCommand = (
autofill.Trigger
)
</pre>
### The autofill.trigger Command ### {#webdriver-bidi-trigger}
The <dfn export>autofill.trigger</dfn> command triggers [=autofill=] on a particular form field
using the provided data.
<dl>
<dt>Command Type</dt>
<dd>
<pre highlight="cddl" class="cddl" data-cddl-module="remote-cddl">
autofill.Trigger = (
method: "autofill.trigger",
params: autofill.TriggerParameters
)
autofill.TriggerParameters = {
context: browsingContext.BrowsingContext,
element: script.SharedReference,
? card: autofill.Card,
? address: autofill.Address
}
autofill.Card = {
number: text,
name: text,
expiryMonth: text,
expiryYear: text,
cvc: text
}
autofill.Address = {
fields: [* autofill.Field]
}
autofill.Field = {
name: autofill.FieldName,
value: text
}
autofill.FieldName = "name" /
"honorific-prefix" /
"given-name" /
"additional-name" /
"family-name" /
"honorific-suffix" /
"nickname" /
"organization" /
"street-address" /
"address-line1" /
"address-line2" /
"address-line3" /
"address-level4" /
"address-level3" /
"address-level2" /
"address-level1" /
"country" /
"country-name" /
"postal-code" /
"tel" /
"tel-country-code" /
"tel-national" /
"tel-area-code" /
"tel-local" /
"email"
</pre>
</dd>
<dt>Return Type</dt>
<dd><code>EmptyResult</code></dd>
</dl>
The `autofill.FieldName` values correspond to the [=autofill field names=] defined in [[HTML]].
### Processing Steps ### {#webdriver-bidi-steps}
<div algorithm="autofill.trigger processing steps">
The remote end steps with |session| and |command parameters| are:
1. Let |navigable id| be the value of the `context` field of |command parameters|.
1. Let |navigable| be the result of trying to get a navigable with |navigable id|.
1. Let |document| be |navigable|'s active document.
1. Let |environment settings| be the [=environment settings object=] whose [=relevant global object=]'s associated Document is |document|.
1. Let |realm| be |environment settings|'s [=realm execution context=]'s Realm component.
1. Let |element id| be the value of the `element` field of |command parameters|.
1. Let |element| be the result of trying to deserialize remote reference with |element id|, |realm|, and |session|.
1. If |element| is not a [=form control=] or |element|'s form owner is null, return an error with error code "invalid argument".
1. Let |card| be the value of the `card` field of |command parameters| if present, or null otherwise.
1. Let |address| be the value of the `address` field of |command parameters| if present, or null otherwise.
1. If both |card| and |address| are not null, return an error with error code "invalid argument".
1. If both |card| and |address| are null, return an error with error code "invalid argument".
1. If the user agent is unable to perform [=autofill=], return an error with error code "unsupported operation".
1. Let |parsed card| be the result of parsing |card| as an <code>autofill.Card</code>.
1. Let |parsed address| be the result of parsing |address| as an <code>autofill.Address</code>.
1. The user agent should [=autofill=] |element| and |element|'s form owner, while taking into account the contents of |parsed card| and |parsed address|.
1. Return success with data null.
</div>
<div class="example">
Using WebDriver BiDi to trigger address autofill:
```json
{
"method": "autofill.trigger",
"params": {
"context": "context-id-123",
"element": { "sharedId": "element-id-456" },
"address": {
"fields": [
{ "name": "given-name", "value": "John" },
{ "name": "family-name", "value": "Doe" },
{ "name": "street-address", "value": "123 Main St" },
{ "name": "address-level2", "value": "San Francisco" },
{ "name": "address-level1", "value": "CA" },
{ "name": "postal-code", "value": "94102" },
{ "name": "country", "value": "US" }
]
}
}
}
```
</div>
<div class="example">
Using WebDriver BiDi to trigger credit card autofill:
```json
{
"method": "autofill.trigger",
"params": {
"context": "context-id-123",
"element": { "sharedId": "cc-number-element-id" },
"card": {
"number": "4111111111111111",
"name": "John Doe",
"expiryMonth": "12",
"expiryYear": "2028",
"cvc": "123"
}
}
}
```
</div>