From 63c77c01567e7f6e46955ecfc2e380198f0d835c Mon Sep 17 00:00:00 2001 From: Brian Banerjee Date: Tue, 27 Jan 2026 02:01:35 -0700 Subject: [PATCH 1/6] Update and clarify Permissions-Policy usage (#42534) * Update and clarify Permissions-Policy usage * Minor language tweaks --- .../permissions-policy/bluetooth/index.md | 27 +++++++------- .../permissions-policy/fullscreen/index.md | 27 +++++++------- .../permissions-policy/geolocation/index.md | 37 ++++++++----------- 3 files changed, 43 insertions(+), 48 deletions(-) diff --git a/files/en-us/web/http/reference/headers/permissions-policy/bluetooth/index.md b/files/en-us/web/http/reference/headers/permissions-policy/bluetooth/index.md index 487ef2455fa37b2..03ec559ab74a330 100644 --- a/files/en-us/web/http/reference/headers/permissions-policy/bluetooth/index.md +++ b/files/en-us/web/http/reference/headers/permissions-policy/bluetooth/index.md @@ -30,36 +30,37 @@ Permissions-Policy: bluetooth=; ## Default policy -The default allowlist for `bluetooth` is `self`. +The default allowlist for `bluetooth` is `self`. The top-level browsing context and same-origin iframes are allowed access to the `bluetooth` feature by default. ## Examples -### General example +### Basic usage -SecureCorp Inc. wants to disable the Web Bluetooth API within all browsing contexts except for its own origin and those whose origin is `https://example.com`. -It can do so by delivering the following HTTP response header to define a Permissions Policy: +SecureCorp Inc. wants to disallow `bluetooth` within all cross-origin iframes except those whose origin is `https://example.com`. It can do so by delivering the following HTTP response header to define a Permissions Policy: ```http Permissions-Policy: bluetooth=(self "https://example.com") ``` -### With an \ ``` -Then include an {{HTMLElement('iframe','allow','#Attributes')}} attribute on the ` ``` -` ``` -Then include an {{HTMLElement('iframe','allow','#Attributes')}} attribute on the ` ``` -iframe attributes can selectively enable features in certain frames, and not in others, even if those frames contain documents from the same origin. - ## Specifications {{Specifications}} diff --git a/files/en-us/web/http/reference/headers/permissions-policy/geolocation/index.md b/files/en-us/web/http/reference/headers/permissions-policy/geolocation/index.md index da49e81ed03d96b..6f21ec0b96111b8 100644 --- a/files/en-us/web/http/reference/headers/permissions-policy/geolocation/index.md +++ b/files/en-us/web/http/reference/headers/permissions-policy/geolocation/index.md @@ -21,10 +21,6 @@ Specifically, where a defined policy blocks use of this feature, calls to callbacks to be invoked with a {{domxref('GeolocationPositionError')}} code of `PERMISSION_DENIED`. -By default, the Geolocation API can be used within top-level documents and their -same-origin child frames. This directive allows or prevents cross-origin frames from -accessing geolocation. This includes same-origin frames. - ## Syntax ```http @@ -36,40 +32,37 @@ Permissions-Policy: geolocation=; ## Default policy -The default allowlist for `geolocation` is `self`. +The default allowlist for `geolocation` is `self`. The top-level browsing context and same-origin iframes are allowed access to the `geolocation` feature by default. ## Examples -### General example +### Basic usage -SecureCorp Inc. wants to disable the Geolocation API within all browsing contexts -except for its own origin and those whose origin is `https://example.com`. It -can do so by delivering the following HTTP response header to define a Permissions Policy: +SecureCorp Inc. wants to disallow `geolocation` within all cross-origin iframes except those whose origin is `https://example.com`. It can do so by delivering the following HTTP response header to define a Permissions Policy: ```http Permissions-Policy: geolocation=(self "https://example.com") ``` -### With an \ ``` -Then include an {{HTMLElement('iframe','allow','#Attributes')}} attribute on the -` + ``` -Interestingly, `allow` attributes can selectively enable features in certain frames, and not in others, -even if those frames contain documents from the same origin. - ## Specifications {{Specifications}} From ad20400adefa435897ee28e55cb8dfa7419e29f8 Mon Sep 17 00:00:00 2001 From: Ahamad Ali <122612964+AhamadAlii@users.noreply.github.com> Date: Tue, 27 Jan 2026 17:49:19 +0530 Subject: [PATCH 2/6] Fix incorrect @custom-media override behavior description (#42928) * Fix incorrect @custom-media override behavior description * Fix @custom-media override description in Description section * Update files/en-us/web/css/reference/at-rules/@custom-media/index.md Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Clarify @custom-media override example and finalize note wording * Update files/en-us/web/css/reference/at-rules/@custom-media/index.md Co-authored-by: Chris Mills * Remove duplicated @custom-media override paragraph --------- Co-authored-by: Chris Mills Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../reference/at-rules/@custom-media/index.md | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/files/en-us/web/css/reference/at-rules/@custom-media/index.md b/files/en-us/web/css/reference/at-rules/@custom-media/index.md index 05743d37359c0db..1db5f8cae5b5f1f 100644 --- a/files/en-us/web/css/reference/at-rules/@custom-media/index.md +++ b/files/en-us/web/css/reference/at-rules/@custom-media/index.md @@ -42,7 +42,10 @@ The `@custom-media` at-rule solves this problem by letting you define **named al Custom media queries can be composed from others by referencing their alias names inside the media query features. This enables building more expressive, layered conditions. However, a custom media query cannot refer to itself, nor can it form part of a circular chain of references. Any circular dependency — direct or indirect — invalidates all custom media queries involved in that loop. -If multiple `@custom-media` rules define the same `` name, only the last declaration in the source order applies. All earlier declarations are ignored. +If multiple `@custom-media` rules define the same `` name, the rule +that is in scope at the time a `@media` rule is evaluated is used. Earlier +references are not retroactively updated when a later `@custom-media` rule is +declared. ### Evaluating media queries with logical operators @@ -222,7 +225,20 @@ In this example, one `@custom-media` rule is overridden by another `@custom-medi @custom-media --mobile-breakpoint (width < 480px); ``` -The initial definition of `--mobile-breakpoint` is overridden and therefore ignored. The final declaration becomes the active value used by all references to that custom media query. +When multiple `@custom-media` rules use the same name, the rule that is in scope +at the time a `@media` rule is evaluated is used. Earlier references are not +retroactively updated when a later `@custom-media` rule is declared. + +For example, in the code above, the `--mobile-breakpoint` reference inside the +`@media` rule is evaluated as `(width < 320px)`, so the `.container` rule is only +applied when the viewport is less than 320px wide, even though +`--mobile-breakpoint` is redefined as `(width < 480px)` later in the stylesheet. + +> [!NOTE] +> The overriding behavior of `@custom-media` is still under discussion in the CSS +> specification and may change in the future. See the +> [Browser compatibility](#browser_compatibility) section for current support +> status. ## Specifications From 545c1267ae9642d850ef539956442c2bb4de280a Mon Sep 17 00:00:00 2001 From: wbamberg Date: Tue, 27 Jan 2026 04:28:27 -0800 Subject: [PATCH 3/6] Add passkeys guide (#42338) * Add passkeys guide * Updates * Updates * ... * ... * fix note syntax, maybe * Missing word... * ... * ... * Talk about platform authenticators; better account of resident keys * Remove HTML :( * ... * ... * ... * Revert TLS change * Add passkeys guide * Updates * Updates * ... * ... * fix note syntax, maybe * Missing word... * ... * ... * Talk about platform authenticators; better account of resident keys * Remove HTML :( * ... * ... * ... * Revert TLS change * Add sections on migrating from passwords * Add see also and intro bits * Fix some links * Update files/en-us/web/security/authentication/passkeys/index.md * Update files/en-us/web/security/authentication/passkeys/index.md * Linkify * Apply suggestions from code review Co-authored-by: Hamish Willee * Give example authenticators * Rewrite discoverable credentials section * Review comment * Review comment * Add a section on managing passkeys * Expand on security properties compared with passwords * Better description of where to get AAGUID * Extra linkage * Apply suggestions from code review Co-authored-by: Florian Scholz * Update files/en-us/web/security/authentication/passkeys/index.md * Update files/en-us/web/security/authentication/passkeys/index.md Co-authored-by: Simone Onofri * Update files/en-us/web/security/authentication/passkeys/index.md Co-authored-by: Simone Onofri * Update files/en-us/web/security/authentication/passkeys/index.md Co-authored-by: Simone Onofri * Update files/en-us/web/security/authentication/passkeys/index.md Co-authored-by: Simone Onofri * Update files/en-us/web/security/authentication/passkeys/index.md Co-authored-by: Simone Onofri * Update files/en-us/web/security/authentication/passkeys/index.md * Update files/en-us/web/security/authentication/passkeys/index.md * Update files/en-us/web/security/authentication/passkeys/index.md * More review comments * Update files/en-us/web/security/authentication/passkeys/index.md Co-authored-by: Simone Onofri * Remove review comment para * Update files/en-us/web/security/authentication/passkeys/index.md Co-authored-by: Simone Onofri * Review comment * Remove explicit recommendation to adopt passkeys --------- Co-authored-by: Hamish Willee Co-authored-by: Florian Scholz Co-authored-by: Simone Onofri --- .../security/authentication/passkeys/index.md | 374 ++++++++++++++++++ .../passkeys/passkeys-register.svg | 3 + .../passkeys/passkeys-sign-in.svg | 3 + .../authentication/passwords/index.md | 4 +- 4 files changed, 382 insertions(+), 2 deletions(-) create mode 100644 files/en-us/web/security/authentication/passkeys/index.md create mode 100644 files/en-us/web/security/authentication/passkeys/passkeys-register.svg create mode 100644 files/en-us/web/security/authentication/passkeys/passkeys-sign-in.svg diff --git a/files/en-us/web/security/authentication/passkeys/index.md b/files/en-us/web/security/authentication/passkeys/index.md new file mode 100644 index 000000000000000..496ffb4fc530fa0 --- /dev/null +++ b/files/en-us/web/security/authentication/passkeys/index.md @@ -0,0 +1,374 @@ +--- +title: Passkeys +slug: Web/Security/Authentication/Passkeys +page-type: guide +sidebar: security +--- + +Passkeys enable websites to authenticate users without the user having to enter any passwords or other secret codes on the site itself. They address [many of the most serious weaknesses of other authentication methods](#security_properties_of_passkeys) such as passwords. + +They're considered [the most secure authentication method available to websites](#security_properties_of_passkeys), and we recommend that sites should adopt passkeys as their preferred authentication method, and [phase out the use of passwords](#migrating_from_passwords). + +Instead of a shared secret, passkeys rely on public-key cryptography. A passkey is a {{glossary("Public-key cryptography", "public/private key pair")}} bound to a specific user's account on a particular website. + +The private key is stored in a module called an _authenticator_, that's [in, or attached to, the user's device](#platform_and_roaming_authenticators). An authenticator might be built into the platform, or a separate hardware key like a [YubiKey](https://en.wikipedia.org/wiki/YubiKey), or a credential manager app like [KeePassXC](https://keepassxc.org/). + +The public key is stored on the website's server. When the user signs in, the authenticator uses the private key to {{glossary("digital signature", "digitally sign")}} a [_challenge_](#challenges) value from the server, together with contextual information such as the requesting {{glossary("origin")}}. The resulting object is called an _assertion_. The website's server can use the public key to verify the assertion's signature, and sign the user in. + +In this guide we'll: + +- Introduce the [Web Authentication API (WebAuthn)](/en-US/docs/Web/API/Web_Authentication_API), which enables web apps to use passkeys. +- Go through the two main flows supported by WebAuthn: [registration](#registration) and [sign-in](#sign_in). +- Explore some of the main [features of the WebAuthn API](#features_of_webauthn). +- Summarize the [security properties of passkeys](#security_properties_of_passkeys). +- Explore some good practices to help prevent users from being locked out if they [lose their passkeys](#handling_lost_passkeys), to help users [manage their passkeys](#managing_passkeys), and to help users [migrate from passwords](#migrating_from_passwords). + +## The WebAuthn API + +To interact with an authenticator, a website uses the [Web Authentication API (WebAuthn)](/en-US/docs/Web/API/Web_Authentication_API). In the WebAuthn specification, a website that uses passkeys to authenticate users is called a _Relying Party_ (RP), and we'll use that term in this guide. + +WebAuthn is an extension of the [Credential Management API](/en-US/docs/Web/API/Credential_Management_API), which is a framework for managing {{glossary("credential", "credentials")}} for various authentication methods, including [passwords](/en-US/docs/Web/Security/Authentication/Passwords) and [federated identity](/en-US/docs/Web/Security/Authentication/Federated_identity), as well as passkeys. + +The two main functions used by RPs are: + +- {{domxref("CredentialsContainer.create()")}}, which you use to create a new passkey when a user registers on your site. +- {{domxref("CredentialsContainer.get()")}}, which you use to generate an assertion from the user's stored passkey, when the user signs into your site. + +## Registration + +In this section we'll walk through the flow used to create a new passkey and use it to set up a new user account. + +![Overview of user registration with passkeys.](passkeys-register.svg) + +When the user asks to register on a site, the RP's front-end code first asks its server for a [_challenge_](#challenges): this is a random value generated on the server, that the server will later use to ensure that the resulting passkey was generated in response to this request. + +Next, the RP's front-end code calls {{domxref("CredentialsContainer.create()")}}. It can specify various options, including: + +- **Attestation preferences**: Whether the RP is interested in authenticator [attestation](#attestation) (a mechanism that helps the RP decide if it should trust the authenticator), and if so, what form the attestation should take. + +- **Authenticator preferences**: What [type of authenticator](#platform_and_roaming_authenticators) to use, and whether the authenticator should perform [user verification](#user_verification) before creating the passkey. + +- **Challenge**: The [challenge](#challenges) generated by the RP's server. This helps protect against {{glossary("replay attack", "replay attacks")}}. + +- **Website information**: A human readable name and ID for the RP that will be associated with the new passkey. The ID determines the [scope](#passkey_scope) of the resulting passkey. + +- **User information**: Information about the user that will be associated with the new passkey, including a human-readable display name, an account identifier, and a human-readable account identifier such as an email address or username. + +Depending on the authenticator capabilities and the preferences of the RP, the authenticator may ask the user to authorize passkey creation via some [user verification](#user_verification) method: for example, using a biometric such as a fingerprint. + +The authenticator then creates a passkey for the account. It stores the private key locally and returns an object containing the public key, challenge, and some additional information. If the authenticator is performing attestation, then this is all {{glossary("digital signature", "digitally signed")}} with either the private key or an [attestation](#attestation) key belonging to the authenticator. + +The RP's front-end code sends this to the server, which: + +- Verifies the attestation, if attestation is taking place +- Verifies that the challenge is the expected value +- Creates a new user account and stores the public key in it along with with the user's account information. + +## Sign in + +In this section we'll walk through the flow used to sign a user in with a passkey. + +![Overview of user sign-in with passkeys.](passkeys-sign-in.svg) + +When the user tries to sign in, the RP's front-end code again asks the server for a [challenge](#challenges) value. + +Next, the RP's front-end code calls {{domxref("CredentialsContainer.get()")}}. It can specify various options, including: + +- **Allowed credentials**: An array of identifiers for the passkeys that the RP will accept. This array may be empty or omitted, in which case any suitable passkeys may be used. + +- **Challenge**: The [challenge](#challenges) generated by the RP's server. + +- **Website ID**: The ID of the RP which is trying to sign the user in. See [Passkey scope](#passkey_scope). + +- **User verification**: Whether the authenticator should perform [user verification](#user_verification) before using the passkey. + +Next, the browser finds passkeys matching the given criteria: if it finds more than one, it may ask the user to choose one. The authenticator which stores this passkey will typically ask the user to authorize the use of this passkey, including [user verification](#user_verification) if this is requested by the RP and supported by the authenticator. + +The authenticator will then use the passkey's private key to create a digitally signed [assertion](#assertions), including the challenge and other data. + +The RP's front-end sends the assertion to the server, which verifies the signature using the public key it stored. If verification is successful, then the user can be signed in. + +## Features of WebAuthn + +In this section we'll go into some more detail about various aspects of the WebAuthn API. + +### Platform and roaming authenticators + +The WebAuthn API distinguishes two types of authenticators: + +- **Platform authenticators** + - : These authenticators are not removable from the device. For example, authenticators built into the device's operating system, like the [Touch ID](https://en.wikipedia.org/wiki/Touch_ID) system in Apple devices or the [Windows Hello](https://en.wikipedia.org/wiki/Windows_10#System_security) system. +- **Roaming authenticators** + - : These authenticators can be removed from the device and attached to a different device. The classic example of this is an authenticator implemented in a USB key, like a [YubiKey](https://en.wikipedia.org/wiki/YubiKey). + +When an RP creates a new passkey it can ask which type of authenticator it wants to use, as part of the [`authenticatorSelection`](/en-US/docs/Web/API/PublicKeyCredentialCreationOptions#authenticatorselection) option that it passes to {{domxref("CredentialsContainer.create()")}}. + +The main advantage of a platform authenticator is that it's convenient for the user: they don't have to keep track of a separate piece of hardware. The main disadvantage is that it can only be used with its host device. + +Platform authenticators can sometimes function as roaming authenticators: for example, a platform authenticator on a mobile device might be available to a laptop as a roaming authenticator, via a Bluetooth connection. + +Although platform authenticators can't be removed from their device, they can often share their passkeys with other authenticators via cloud sync or import/export functions. For example, a platform provider might enable users to share their passkeys across all the devices belonging to their product family. + +### Discoverable and non-discoverable credentials + +The WebAuthn specification distinguishes between _discoverable_ and _non-discoverable_ credentials. + +- **Discoverable credentials**, also known as _resident keys_, are those that can be used without the RP first needing to identify the user who is being authenticated: that is, the "allowed credentials" array passed into {{domxref("CredentialsContainer.get()")}} can be empty. With a discoverable credential, all the signing key material is stored in the authenticator, so the authenticator is able to generate signatures without needing any input from the RP. + +- **Non-discoverable credentials**, also known as _non-resident keys_, are those for which the RP must first identify the user who is being authenticated (for example, by having them enter their username), and then pass the associated credential ID into {{domxref("CredentialsContainer.get()")}}, in the "allowed credentials" array. + + Non-discoverable credentials need the credential ID because they do not store the signing key itself in the authenticator, but instead generate the signing key every time it is needed, from an internal seed and the credential ID value. That is, the account key is not resident in the authenticator. + +The advantage of using non-discoverable credentials is that an authenticator with limited storage can support a potentially unlimited number of accounts, because the key material for each account is not stored in the authenticator. + +The advantage of using discoverable credentials is that they enable a browser to implement [autofill](/en-US/docs/Web/Security/Authentication/Passkeys#autofill_ui) with public key credentials, which makes it much easier for users to sign in, especially when they might have both public key credentials and passwords for a given site. + +**For this reason, passkeys must always be discoverable credentials, so RPs implementing passkey-based authentication should always make them discoverable**. + +To create a discoverable credential, the RP should set the `residentKey` option to `"required"` and the `requireResidentKey` option to `true` when it creates a new credential in the {{domxref("CredentialsContainer.create()")}} call. + +### Challenges + +When an RP asks an authenticator to create a new passkey or to use an existing passkey, it must provide a _challenge_. This is a random value, specific to the request, that would not be predictable by an attacker. The challenge must be generated in a trusted environment (which generally means, on the server, not the front end). + +The RP's front-end code passes the challenge into the `create()` or `get()` call, and the browser includes the same value in the object returned by these methods. In the case of `get()`, the challenge value is also part of the input to the digital signature calculated by the authenticator. + +When the web server verifies the response from the authenticator, the web server needs to check that the challenge is the same value it originally provided. + +The web server should also invalidate the challenge value after about 10 minutes, and reject any responses containing the challenge that have arrived after this time. + +The challenge represents evidence that the authenticator's response was a response to _this_ request, and not an old response to some previous request that an attacker has managed to steal. This kind of attack is known as a {{glossary("replay attack")}}. + +### Attestation + +The security of a passkey depends in part on the reliability of the authenticator used. For example, if an authenticator does not protect the private keys it stores, then an attacker could steal the keys and impersonate users. WebAuthn defines an optional mechanism called _attestation_, in which an authenticator can provide verifiable evidence to the RP about the authenticator and the data it produces (such as key pairs or signed assertions). This can help the RP decide whether it wants to rely on the authenticator to authenticate its users. + +To implement attestation, the authenticator contains a key pair called an _attestation key_, which was built into the device at manufacturing time, and which is {{glossary("digital certificate", "certified")}} as belonging to the organization that made this authenticator. For example, the certificate could state that this authenticator was produced by "Acme Authenticator Incorporated". + +When the authenticator creates a new passkey, it signs the resulting object with its attestation key. The RP verifies the signature and the associated certificate, and then has evidence that the passkey was made by an authenticator produced by "Acme Authenticator Incorporated". + +Not all authenticators support attestation, and RPs may indicate that they are not interested in attestation. In these situations, the object returned by a call to {{domxref("CredentialsContainer.create()")}} may not be signed at all, or it may be signed using the passkey itself (this is referred to as _self attestation_). In these situations, the RP has no reliable evidence of the authenticator's origin or capabilities. + +### User verification + +When a website calls {{domxref("CredentialsContainer.create()")}} to create a new passkey, or calls {{domxref("CredentialsContainer.get()")}} to create an assertion, the authenticator will always ask the user to consent to the operation. + +The RP can also ask the authenticator to perform _user verification_, which means the user will be asked to authorize the use of their credential, for example by entering a PIN or a biometric such as a fingerprint. + +When this happens, it's considered to be a form of {{glossary("multi-factor authentication")}}: the authenticator itself is "something the user has" while the PIN or biometric are respectively "something they know" or "something they are". + +Note that not all authenticators support user verification. + +### Passkey scope + +The scope of a passkey determines which sites will be allowed to use the passkey. + +By default: + +- When a page creates a passkey by calling {{domxref("CredentialsContainer.create()")}}, the browser sets the passkey's _RP ID_ to the domain component of the caller's {{glossary("origin")}}, and the authenticator stores this value alongside the passkey. + +- When a page uses a passkey by calling {{domxref("CredentialsContainer.get()")}}, the browser passes the domain component of the caller's {{glossary("origin")}} to the authenticator, and the authenticator will only allow the passkey to be used if this value matches the stored RP ID. + +This means that by default, a passkey can only be used by a page from the same origin (excluding the port) as the page that originally created it. + +Websites are allowed to relax these rules, within some constraints: + +- When a website creates a passkey, it can pass an ID into {{domxref("CredentialsContainer.create()")}}, and the authenticator will use this as the RP ID. + +- Similarly, when a website tries to use a passkey, it can pass an ID into {{domxref("CredentialsContainer.get()")}}, and the authenticator will compare this ID with the stored RP ID. + +For both `create()` and `get()`, the passed value must be a {{glossary("registrable domain")}} that is a _domain suffix_ of the domain of the caller's origin. + +This relaxation means that, for example, a page at `https://register.example.com` may create a passkey with an RP ID of `example.com`, and a page at `https://login.example.com` will then be allowed to use that passkey. + +Passkey scope helps to defend against [phishing](/en-US/docs/Web/Security/Attacks/Phishing) attacks. In a phishing attack, the user is presented with a malicious page that looks like the target site, and that asks the user to enter their credentials for the target site. Typically, the URL of the malicious site appears similar to that of the target site, helping to confuse the user. For example, if the target site is `https://example.com`, then the phishing site might be served from `https://examp1e.com`. + +With the scope rules for passkeys, though, a site served from `https://examp1e.com` is not able to use passkeys that were created for `https://example.com`. + +### Origin verification + +The signed [assertion](#assertions) returned by an authenticator includes information about the context of the caller: + +- The {{glossary("origin")}} of the document that called {{domxref("CredentialsContainer.get()")}}. +- If the caller was embedded as an {{htmlelement("iframe")}}, whether the caller had the same origin as the top-level document. +- The origin of the top-level document, if the caller was embedded as an {{htmlelement("iframe")}} and was not same-origin with the caller. + +When the RP server verifies the assertion, it must check that these values are what it expects to see. + +This provides a layer of protection against [phishing](/en-US/docs/Web/Security/Attacks/Phishing) attacks, in addition to that provided by [passkey scope](#passkey_scope). + +## Security properties of passkeys + +Passkeys are more secure than passwords, and we can see how their design addresses the most serious [weaknesses of passwords](/en-US/docs/Web/Security/Authentication/Passwords#weaknesses_of_password-based_authentication): + +- Unlike a password, the user never invents a passkey value or needs to remember it. This means users can't choose weak passkey values, so they are not vulnerable to [guessing](/en-US/docs/Web/Security/Authentication/Passwords#guessing) attacks. Passkey generation is transferred from the user to the authenticator. + +- Passkeys are never reused across sites, so they are not vulnerable to [credential stuffing](/en-US/docs/Web/Security/Authentication/Passwords#credential_stuffing) attacks. If an attacker does get access to a passkey, they can only use it for the site that originally created it. + +- With passkeys, the server never has to store any secrets: it just stores the public key. So if an attacker [breaks into the server's database](/en-US/docs/Web/Security/Authentication/Passwords#database_compromise), they can't compromise the private key, which is stored in the authenticator. Note, however, that they can compromise user accounts if they can _write_ fake credentials into the server's database. + +- When the user tries to sign in, the browser will only look for passkeys whose scope matches the requesting site, and the RP's server can verify that the origin of the requester was what they expected. This makes passkeys resistant to [phishing](/en-US/docs/Web/Security/Attacks/Phishing) attacks, because front-end code served from a phishing site like `https://examp1e.com` is not able to use the passkey associated with `https://example.com`. + +Although passkeys provide protection against these common web authentication attacks, they don't eliminate all threats. Since widespread deployment of passkeys is relatively new, there isn't yet a mature understanding of the attacks that passkeys may face, but it's likely that some attacks would focus on the user's devices: for example, convincing them to install a malicious authenticator. Attacks may also target parts of the authentication system that are not secured by passkeys, such as account recovery mechanisms. + +## Handling lost passkeys + +If a user loses an authenticator, whether it's a separate module or integrated into their phone, they lose all the passkeys it contains. + +In this section we'll discuss two strategies for dealing with authenticator loss: + +- [Creating multiple passkeys for a single account](#creating_multiple_passkeys) +- [Backing up passkeys](#passkey_backup) + +### Creating multiple passkeys + +In contrast to the advice regarding passwords, RPs are encouraged to create multiple passkeys for a single account. A common pattern would be to have: + +- One passkey in a [platform authenticator](#platform_authenticators), that is their everyday passkey for the site +- One passkey in a [roaming authenticator](#roaming_authenticators), that the user keeps somewhere safe, as a backup in case the user loses their device. + +The [`excludeCredentials`](/en-US/docs/Web/API/PublicKeyCredentialCreationOptions#excludecredentials) option, passed to {{domxref("CredentialsContainer.create()")}}, lists credential IDs, and tells the browser that the authenticators containing the listed keys must not be used for the new key. That is, it is a way for the RP to ensure that the new passkey is created in a new authenticator. + +### Passkey backup + +Some authenticators support backup by various methods, such as cloud sync or manual export. The signed assertion returned from a call to `get()` includes a set of [flags](/en-US/docs/Web/API/Web_Authentication_API/Authenticator_data#flags), which, among other things, indicates whether the passkey: + +- Is _backup eligible_: that is, whether it is stored in an authenticator that supports backup +- Has in fact been backed up. + +An RP can use this information to help a user manage their credentials. For example: + +- If the passkey is not backup eligible, then the RP might respond by inviting the user to create another passkey in another authenticator that could be used as a backup. + +- If the RP is migrating users away from passwords, and the user has an old password as well as a passkey, and the assertion indicates that the passkey has been backed up, then the RP might invite the user to delete their old password, since they don't need it as a backup any more. + +## Managing passkeys + +We've seen that a user may have multiple passkeys for a single account, distributed across multiple authenticators and multiple devices. Each passkey corresponds to a WebAuthn credential, with private key material protected by the authenticator and a corresponding public key stored by the RP as part of the user's account information. + +Sometimes the user might need to delete a passkey for their RP account: this essentially means deleting the public key stored in the RP's server, so that the corresponding private key can't be used to sign the user in anymore. This is generally needed when the user doesn't have control of the authenticator anymore, for example, because they have lost the device containing it. + +This means that an RP should implement a means for an authenticated user to view the registered passkeys for their account and delete specific public keys. For each key, the RP should display information to help a user understand which key it is and which authenticator it is associated with. This can include: + +- **Passkey provider name**: The name of the passkey provider, such as "Windows Hello" or "Bitwarden". + + > [!NOTE] + > To determine this value: + > + > - Find the _AAGUID_ value in the [`attestedCredentialData`](/en-US/docs/Web/API/Web_Authentication_API/Authenticator_data#attestedcredentialdata) returned by the browser from a successful call to {{domxref("CredentialsContainer.create()")}}. + > - Use this to look up the corresponding name in the [Passkey Provider AAGUIDs](https://github.com/passkeydeveloper/passkey-authenticator-aaguids) list. + > + > See also [Determine the passkey provider with AAGUID](https://web.dev/articles/webauthn-aaguid). + +- **Timestamp**: The time the passkey was last used to sign in. + +- **Backup status**: An indicator of whether the passkey has been backed up (see [Passkey backup](#passkey_backup)). + +Additionally, the user should be able to edit the passkey name and delete the passkey. + +If the user tries to delete the last passkey, the RP should inform them of the implications of this: the RP might allow the user to sign in with a different method such as a [one-time code](/en-US/docs/Web/Security/Authentication/OTP), or they might not be able to access their account any more. + +See also [Help users manage passkeys effectively](https://web.dev/articles/passkey-management). + +### Synchronizing server and authenticators + +Note that if the user deletes a passkey on the RP's server, this introduces an asymmetry between the server and the authenticator that contains the corresponding private key. The authenticator still thinks the passkey is valid, so the browser may offer it to the user as a sign-in option, but the RP will no longer accept its assertions. + +To reduce the chance of problems like this, the WebAuthn API defines a set of static methods of {{domxref("PublicKeyCredential")}} that enable an RP to tell authenticators about server-side changes: + +- {{domxref("PublicKeyCredential.signalUnknownCredential_static", "PublicKeyCredential.signalUnknownCredential()")}} tells the browser that a specific passkey was not recognized by the RP, and is typically called by the RP immediately after the user tried to sign in with this passkey. The most common scenario here is that the user deleted this passkey on the server, and then mistakenly tried to sign in with it. + +- {{domxref("PublicKeyCredential.signalAllAcceptedCredentials_static", "PublicKeyCredential.signalAllAcceptedCredentials()")}} gives the browser the identifiers of all the passkeys that the RP currently accepts as valid, to enable all attached authenticators to update their stored keys. It could be called every time the user successfully authenticates. This API must only be called for authenticated users, because it exposes the user's credential IDs. + +- {{domxref("PublicKeyCredential.signalCurrentUserDetails_static", "PublicKeyCredential.signalCurrentUserDetails()")}} tells the browser the user's current username and display name, and should be called when an authenticated user changes these values. This API must only be called for authenticated users, because it exposes user data. + +## Migrating from passwords + +Most websites that add passkey support will already support password-based authentication, and will have an existing base of users with passwords. These users are not safe from the [weaknesses of passwords](/en-US/docs/Web/Security/Authentication/Passwords#weaknesses_of_password-based_authentication) until they not only have and use passkeys on your site, but no longer even possess passwords associated with their accounts. + +You can implement a three-step process to migrating users from passwords: + +- [Enabling users to create passkeys alongside their passwords](#creating_passkeys_alongside_passwords) +- [Enabling users to use their passkeys, instead of their passwords](#using_passkeys_alongside_passwords) +- [Enabling users to delete their passwords](#retiring_passwords) + +### Creating passkeys alongside passwords + +The first step here is to offer users the opportunity to create a passkey when they successfully sign into your site with a password. + +#### Conditional create + +An additional step towards increasing passkey adoption is a feature called _conditional create_. This allows an RP to create a new passkey for a user's account without requiring any user interaction, when certain conditions are met. + +To enable conditional create, the RP calls {{domxref("CredentialsContainer.create()")}}, passing the [`mediation`](/en-US/docs/Web/API/CredentialsContainer/create#mediation) option set to `"conditional"`: + +```js +try { + const publicKeyCredential = await navigator.credentials.create({ + publicKey: options, + mediation: "conditional", + }); + // handle new passkey creation + // let the user know that they have a passkey now +} catch (e) { + // passkey was not created +} +``` + +With this option: + +- If the user has just signed in with a password, using a password manager that also supports passkeys (that is, a _credentials manager_ that can also function as an authenticator), then the browser will ask that credentials manager to create a new passkey for the user, without asking the user. + +- Otherwise, the `create()` call will fail. + +From the user's point of view, if the create failed, they don't know it was made, and if it succeeds, the RP can inform them that they have a passkey that they can use to sign in next time. + +The theory here is that if the user is relying on a credentials manager for sign-in already, then they implicitly trust it to look after their sign-in credentials _in general_, so they can trust it to create a new form of credential for them. + +### Using passkeys alongside passwords + +If a user has both a password and one or more passkeys, they can choose to use either to sign in, and the RP might want to encourage them to use the passkey. + +In the transitional period, a user could have either passwords or passkeys for their account, or both. In this situation, a UI that asks them which method they want to sign in with can be confusing: they might not remember which method they have for which account. + +#### Autofill UI + +One technique to help users in this situation is the _autofill UI_, also sometimes called _conditional mediation_. + +In this technique, the RP's sign-in page offers the user a form, which allows them to sign in with a username and password. In the field for the username, the RP adds an autocomplete value of `"webauthn"`: + +```html + +``` + +In the background, the RP starts the normal process to request an assertion signed with a passkey: it fetches a [challenge](#challenges) from the server and prepares the other options to {{domxref("CredentialsContainer.get()")}}. + +However, when the RP calls `get()`, it passes the `mediation: "conditional"` option (just like with [conditional create](#conditional_create)): + +```js +const assertion = await navigator.credentials.get({ + publicKey: options, + mediation: "conditional", +}); +``` + +The effect of this is that the call waits until the user interacts with the username field. When the user interacts with the field, the browser looks for passkeys that can be used to sign into the RP, and displays them to the user as autofill values. If the user selects one, then the selected passkey is used, and the RP can use the resulting assertion to sign the user in. + +If the user doesn't have a passkey for the site, or they don't select one of the offered passkeys, then they can enter their username and password, or have it autofilled by their password manager. + +This means that you can support users who may have passwords or passkeys, or both, without any special UI, and without the user having to remember whether they actually do have a passkey for your site. + +### Retiring passwords + +Even if a user has a passkey for your site, and uses it in preference to their password, they are still vulnerable to attacks such as [credential stuffing](/en-US/docs/Web/Security/Authentication/Passwords#credential_stuffing), [guessing](/en-US/docs/Web/Security/Authentication/Passwords#guessing), and [phishing](/en-US/docs/Web/Security/Attacks/Phishing) for as long as you retain a password for their account. + +So as a final step, you might want a user to delete their password entirely. You can offer this as an option in their account settings, and potentially nudge them to delete their password if they haven't used it in a long time (but have used their passkeys regularly). + +However, you should also consider that having a password helps protect a user against being locked out of their account if they lose access to their passkey. Before encouraging users to delete their password, you can check that they have alternative protection, such as [multiple passkeys on different authenticators](#creating_multiple_passkeys), and/or passkeys that have been [backed up](#passkey_backup). + +## See also + +- [The Web Authentication API](/en-US/docs/Web/API/Web_Authentication_API) +- [Passkey Central](https://www.passkeycentral.org/home) +- [passkeys.dev](https://passkeys.dev/) +- [Passkeys](https://developers.google.com/identity/passkeys/) (developers.google.com) diff --git a/files/en-us/web/security/authentication/passkeys/passkeys-register.svg b/files/en-us/web/security/authentication/passkeys/passkeys-register.svg new file mode 100644 index 000000000000000..1f3aac3421d2d11 --- /dev/null +++ b/files/en-us/web/security/authentication/passkeys/passkeys-register.svg @@ -0,0 +1,3 @@ + + +
Website front-end
User
Web server
Authenticator
Device
Register
create(options)
Get challenge
Generate
challenge
User verification
Generate
passkey
Store
private key
Return public key
Create account (public key, user info
Verify
challenge
Store
public key
Create
account
Browser
create(options)
Sign
public key
Return public key
\ No newline at end of file diff --git a/files/en-us/web/security/authentication/passkeys/passkeys-sign-in.svg b/files/en-us/web/security/authentication/passkeys/passkeys-sign-in.svg new file mode 100644 index 000000000000000..8855a3f0d6e7dfd --- /dev/null +++ b/files/en-us/web/security/authentication/passkeys/passkeys-sign-in.svg @@ -0,0 +1,3 @@ + + +
Website front-end
User
Web server
Authenticator
Device
Sign in
get(options)
Get challenge
Generate
challenge
User verification
Sign
assertion
Return assertion
Sign in user (assertion)
Verify
assertion
Sign in
user
Browser
get(options)
Return assertion
\ No newline at end of file diff --git a/files/en-us/web/security/authentication/passwords/index.md b/files/en-us/web/security/authentication/passwords/index.md index a3b925a7bdd7d03..cbd7b7a395e0dba 100644 --- a/files/en-us/web/security/authentication/passwords/index.md +++ b/files/en-us/web/security/authentication/passwords/index.md @@ -257,8 +257,8 @@ For more information, see: The practices described above help reduce the risks of a password-based authentication system, but passwords remain an inherently vulnerable authentication method: -- Although password managers and good password policies can help users choose strong passwords and not reuse passwords, they can't guarantee either outcome, leaving users vulnerable to credential stuffing and guessing attacks. +- Although password managers and good password policies can help users choose strong passwords and not reuse passwords, they can't guarantee either outcome, leaving users vulnerable to [credential stuffing](#credential_stuffing) and [guessing](#guessing) attacks. -- Even if users have strong passwords and do not reuse them, users remain vulnerable to phishing attacks. +- Even if users have strong passwords and do not reuse them, users remain vulnerable to [phishing](#phishing) attacks. To address these weaknesses, consider using alternative methods, either instead of passwords or as {{glossary("multi-factor authentication", "additional authentication factors")}}. For example, websites sometimes use passwords with a [one-time password](/en-US/docs/Web/Security/Authentication/OTP) as a second factor, and some websites support [passkeys](/en-US/docs/Web/Security/Authentication/Passkeys), which are resistant to phishing attacks. From bdb21cdfa9a7dc7c65222d2219aa2d96543d8a2e Mon Sep 17 00:00:00 2001 From: rebloor Date: Wed, 28 Jan 2026 02:05:13 +1300 Subject: [PATCH 4/6] Bug-1773681 Showing Paste button for navigator.clipboard.readText() (#42263) * Bug-1773681 Showing Paste button for navigator.clipboard.readText() * Additional note on Chrome behavior * Various updates based on feedback, mainly to restore execCommand guidance and add additional compatibility details. * Apply suggestions from review Co-authored-by: Rob Wu * Apply suggestions from linter Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Updates for feedback * Update files/en-us/mozilla/add-ons/webextensions/interact_with_the_clipboard/index.md --------- Co-authored-by: Rob Wu Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../interact_with_the_clipboard/index.md | 109 +++++++++--------- .../manifest.json/permissions/index.md | 6 +- .../mozilla/firefox/releases/147/index.md | 1 + files/en-us/web/api/clipboard_api/index.md | 8 +- 4 files changed, 60 insertions(+), 64 deletions(-) diff --git a/files/en-us/mozilla/add-ons/webextensions/interact_with_the_clipboard/index.md b/files/en-us/mozilla/add-ons/webextensions/interact_with_the_clipboard/index.md index 59781622fea3a9d..a7c6854d810b1db 100644 --- a/files/en-us/mozilla/add-ons/webextensions/interact_with_the_clipboard/index.md +++ b/files/en-us/mozilla/add-ons/webextensions/interact_with_the_clipboard/index.md @@ -8,43 +8,25 @@ browser-compat: sidebar: addonsidebar --- -Working with the clipboard in extensions is transitioning from the Web API {{domxref("Document.execCommand()","document.execCommand")}} method (which is deprecated) to the {{domxref("Clipboard", "navigator.clipboard")}} method. +You work with the clipboard in extensions using the Web API {{domxref("Clipboard", "navigator.clipboard")}} method and `"clipboardRead"` or `"clipboardWrite"` extension permissions. {{domxref("Clipboard", "navigator.clipboard")}} enables your extension to read and write arbitrary data from and to the clipboard. > [!NOTE] -> The {{domxref("Clipboard", "navigator.clipboard")}} API is a recent addition to the specification and may not be fully implemented in all browsers. This article describes some limitations, but be sure to review the compatibility tables for each method before using them to ensure that the API supports your needs. +> The Web API {{domxref("Document.execCommand()","document.execCommand")}} method was used to provide clipboard functionality. However, {{domxref("Document.execCommand()","document.execCommand("copy")")}}, {{domxref("Document.execCommand()","document.execCommand("cut")")}}, and {{domxref("Document.execCommand()","document.execCommand("paste")")}} are deprecated and no longer guaranteed to work or be available on any browser. These features are documented in this article for historical reference. -The difference between the two APIs is that {{domxref("Document.execCommand()","document.execCommand")}} this is analogous to the keyboard copy, cut, and paste actions – exchanging data between a webpage and clipboard – whereas {{domxref("Clipboard", "navigator.clipboard")}} writes and reads arbitrary data to and from the clipboard. +The {{domxref("Clipboard", "navigator.clipboard")}} API provides methods for: -{{domxref("Clipboard", "navigator.clipboard")}} provide separate methods to read or write: +- Text content, using {{domxref("Clipboard.readText", "navigator.clipboard.readText()")}} and {{domxref("Clipboard.writeText", "navigator.clipboard.writeText()")}}. +- Images, rich text, HTML, and other rich content, using {{domxref("Clipboard.read", "navigator.clipboard.read()")}} and {{domxref("Clipboard.write", "navigator.clipboard.write()")}}. -- text content, using {{domxref("Clipboard.readText", "navigator.clipboard.readText()")}} and {{domxref("Clipboard.writeText", "navigator.clipboard.writeText()")}}. -- images, rich text, HTML, and other rich content, using {{domxref("Clipboard.read", "navigator.clipboard.read()")}} and {{domxref("Clipboard.write", "navigator.clipboard.write()")}}. - -However, while {{domxref("Clipboard.readText", "navigator.clipboard.readText()")}} and {{domxref("Clipboard.writeText", "navigator.clipboard.writeText()")}} work on all browsers, {{domxref("Clipboard.read", "navigator.clipboard.read()")}} and {{domxref("Clipboard.write", "navigator.clipboard.write()")}} do not. For example, on Firefox at the time of writing, {{domxref("Clipboard.read", "navigator.clipboard.read()")}} and {{domxref("Clipboard.write", "navigator.clipboard.write()")}} are not fully implemented, such that to: - -- work with images use {{WebExtAPIRef("clipboard.setImageData","browser.clipboard.setImageData()")}} to write images to the clipboard and {{domxref("Document.execCommand()","document.execCommand("paste")")}} to paste images to a webpage. -- write rich content (such as, HTML, rich text including images, etc.) to the clipboard, use {{domxref("Document.execCommand()","document.execCommand("copy")")}} or {{domxref("Document.execCommand()","document.execCommand("cut")")}}. Then, either {{domxref("Clipboard.read","navigator.clipboard.read()")}} (recommended) or {{domxref("Document.execCommand()","document.execCommand("paste")")}} to read the content from the clipboard. +> [!NOTE] +> The Clipboard API write and read methods are only available in [secure contexts](/en-US/docs/Web/Security/Secure_Contexts). Your extension can't use them from a content script running on `http:` pages; they can only use them from `https:` pages. ## Writing to the clipboard -This section describes the options for writing data to the clipboard. - -### Using the Clipboard API - -The Clipboard API writes arbitrary data to the clipboard from your extension. Using the API requires the permission `"clipboardRead"` or `"clipboardWrite"` in your `manifest.json` file. As the API is only available to [Secure Contexts](/en-US/docs/Web/Security/Defenses/Secure_Contexts), it cannot be used from a content script running on `http:`-pages, only `https:`-pages. - -For page scripts, the `"clipboard-write"` permission needs to be requested using the Web API {{domxref("Permissions", "navigator.permissions")}}. You can check for that permission using {{domxref("Permissions.query", "navigator.permissions.query()")}}: - -```js -navigator.permissions.query({ name: "clipboard-write" }).then((result) => { - if (result.state === "granted" || result.state === "prompt") { - /* write to the clipboard now */ - } -}); -``` +The Clipboard API methods {{domxref("Clipboard.write", "navigator.clipboard.write()")}} and {{domxref("Clipboard.writeText", "navigator.clipboard.writeText()")}} write arbitrary content to the clipboard. The methods are available from a secure context but only function after the extension's user performs {{Glossary("Transient Activation","transient activation")}}. However, with the [`"clipboardWrite"` permission](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions#clipboardwrite), transient activation isn't required. > [!NOTE] -> The `clipboard-write` permission name is not supported in Firefox, only Chromium browsers. +> In Firefox and Chrome, the `"clipboardWrite"` permission enables writing to the clipboard from all extension contexts and content scripts. In Safari, the `"clipboardWrite"` permission is only supported in extension contexts (not content scripts). This function takes a string and writes it to the clipboard: @@ -63,9 +45,12 @@ function updateClipboard(newClip) { ### Using execCommand() -The `"cut"` and `"copy"` commands of the {{domxref("Document.execCommand", "document.execCommand()")}} method are used to replace the clipboard's content with the selected material. These commands can be used without any special permission in short-lived event handlers for a user action (for example, a click handler). +> [!NOTE] +> {{domxref("Document.execCommand()","document.execCommand("copy")")}} and {{domxref("Document.execCommand()","document.execCommand("cut")")}} are deprecated and no longer guaranteed to work or be available on any browser. + +The `"cut"` and `"copy"` commands of the {{domxref("Document.execCommand", "document.execCommand()")}} method are used to replace the clipboard's content with the selected material. Extensions can use these commands without special permission in short-lived event handlers triggered by user actions (for example, a click handler). -For example, suppose you've got a popup that includes the following HTML: +For example, suppose you've got a popup that includes this HTML: ```html @@ -83,9 +68,9 @@ function copy() { document.querySelector("#copy").addEventListener("click", copy); ``` -Because the `execCommand()` call is inside a click event handler, you don't need any special permissions. +Because the `execCommand()` call is inside a click event handler, your extension doesn't need special permissions. -However, let's say that instead you trigger the copy from an alarm: +However, take the example of your extension triggering the copy from an alarm: ```js function copy() { @@ -101,38 +86,25 @@ browser.alarms.create({ browser.alarms.onAlarm.addListener(copy); ``` -Depending on the browser, this may not work. On Firefox, it will not work, and you'll see a message like this in your console: +Depending on the browser, this may not work. On Firefox, it doesn't work, and you see a message like this in the console: `document.execCommand('cut'/'copy') was denied because it was not called from inside a short running user-generated event handler.` -To enable this use case, you need to ask for the `"clipboardWrite"` [permission](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions). So: `"clipboardWrite"` enables you to write to the clipboard outside a short-lived event handler for a user action. +To enable this use case, your extension must request the `"clipboardWrite"` [permission](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions): `"clipboardWrite"` enables your extension to write to the clipboard outside a short-lived event handler for a user action. > [!NOTE] -> {{domxref("Document.execCommand", "document.execCommand()")}} does not work on input fields of `type="hidden"`, with the HTML5 attribute `"hidden"`, or any matching CSS rule using `"display: none;"`. So, to add a "copy to clipboard" button to a `span`, `div`, or `p` tag, you need to use a workaround, such as setting the input's position to absolute and moving it out of the viewport. - -### Browser-specific considerations - -The clipboard and other APIs involved here are evolving rapidly, so there are variations among browsers in how they work. - -In Chrome: - -- You don't need `"clipboardWrite"`, even to write to the clipboard outside a user-generated event handler. - -In Firefox: - -- {{domxref("Clipboard.write", "navigator.clipboard.write()")}} is not supported. - -See the [browser compatibility tables](#browser_compatibility) for more information. +> {{domxref("Document.execCommand", "document.execCommand()")}} doesn't work on input fields of `type="hidden"`, with the HTML5 attribute `"hidden"`, or any matching CSS rule using `"display: none;"`. To add a "copy to clipboard" button to a `span`, `div`, or `p` tag, you need to use a workaround, such as setting the input's position to absolute and moving it out of the viewport. ## Reading from the clipboard -This section describes the options for reading or pasting data from the clipboard. +The Clipboard API methods {{domxref("Clipboard.read", "navigator.clipboard.read()")}} and {{domxref("Clipboard.readText", "navigator.clipboard.readText()")}} read arbitrary text or binary data from the clipboard. These methods allow extensions to access data in the clipboard without pasting it into an editable element. -### Using the Clipboard API +The methods are available from a secure context but only function after the extension's user performs {{Glossary("Transient Activation","transient activation")}} and clicks a paste prompt in an ephemeral context menu. However, with the [`"clipboardRead"` permission](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions#clipboardRead), your extension can read from the clipboard without user confirmation or transient activation. -The Clipboard API's {{domxref("Clipboard.readText", "navigator.clipboard.readText()")}} and {{domxref("Clipboard.read", "navigator.clipboard.read()")}} methods let you read arbitrary text or binary data from the clipboard in [secure contexts](/en-US/docs/Web/Security/Defenses/Secure_Contexts). This lets you access the data in the clipboard without pasting it into an editable element. +> [!NOTE] +> In Firefox and Chrome, the `"clipboardRead"` permission enables writing to the clipboard from all extension contexts and content scripts. Safari doesn't support the `"clipboardRead"` permission. -Once you have the `"clipboard-read"` permission from the [Permissions API](/en-US/docs/Web/API/Permissions_API), you can read from the clipboard easily. For example, this snippet of code fetches the text from the clipboard and replaces the contents of the element with the ID `"outbox"` with that text. +This snippet of code fetches the text from the clipboard and replaces the contents of the element with the ID `"outbox"` with that text. ```js navigator.clipboard @@ -142,9 +114,12 @@ navigator.clipboard ### Using execCommand() -To use {{domxref("Document.execCommand()","document.execCommand("paste")")}} your extension needs the `"clipboardRead"` [permission](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions). This is the case even if you're using the `"paste"` command from within a user-generated event handler, such as {{domxref("Element/click_event", "click")}} or {{domxref("Element/keypress_event", "keypress")}}. +> [!NOTE] +> {{domxref("Document.execCommand()","document.execCommand("paste")")}} is deprecated and no longer guaranteed to work or be available on any browser. + +To use {{domxref("Document.execCommand()","document.execCommand("paste")")}}, your extension needs the `"clipboardRead"` [permission](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions). This requirement exists even if you're using the `"paste"` command from within a user-generated event handler, such as {{domxref("Element/click_event", "click")}} or {{domxref("Element/keypress_event", "keypress")}}. -Consider HTML that includes something like this: +Consider HTML that includes this: ```html @@ -159,13 +134,35 @@ function paste() { document.execCommand("paste"); console.log(pasteText.textContent); } - document.querySelector("#paste").addEventListener("click", paste); ``` ### Browser-specific considerations -Firefox supports the `"clipboardRead"` [permission](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions) from version 54 but only supports pasting into elements in [content editable mode](/en-US/docs/Web/HTML/Reference/Global_attributes/contenteditable), which for content scripts only works with a {{HTMLElement("textarea")}}. For background scripts, any element can be set to content editable mode. +In Chrome: + +- Chrome doesn't expose `navigator.clipboard` to extension service workers, and offscreen documents can't access `navigator.clipboard` due to the API's document focus requirements. As a result, Chrome extensions have to use the deprecated `document.execCommand()` APIs in an offscreen document or use `navigator.clipboard` in a different context, such as a content script or extension page. + For page scripts to write to the clipboard without user interaction, the `"clipboard-write"` permission needs to be requested using the Web API {{domxref("Permissions", "navigator.permissions")}}. Your extension can check for that permission using {{domxref("Permissions.query", "navigator.permissions.query()")}}: + + ```js + navigator.permissions.query({ name: "clipboard-write" }).then((result) => { + if (result.state === "granted" || result.state === "prompt") { + /* write to the clipboard now */ + } + }); + ``` + + > [!NOTE] + > The `clipboard-write` permission is not supported in Firefox or Safari. + +In Firefox: + +- The availability of the Clipboard API read methods on the user's response to a paste prompt was introduced for web pages in Firefox 127 and extensions in Firefox 147. Before that, the methods were only available when the `"clipboardRead"` permission was set. + +In Safari: + +- The `"clipboardWrite"` permission is only supported in extension contexts (not content scripts). +- The `"clipboardRead"` permission isn't support. ## Browser compatibility diff --git a/files/en-us/mozilla/add-ons/webextensions/manifest.json/permissions/index.md b/files/en-us/mozilla/add-ons/webextensions/manifest.json/permissions/index.md index 023eab229abb534..fb3e61e7db6fb6b 100644 --- a/files/en-us/mozilla/add-ons/webextensions/manifest.json/permissions/index.md +++ b/files/en-us/mozilla/add-ons/webextensions/manifest.json/permissions/index.md @@ -182,12 +182,12 @@ Usually, the tab that's granted `activeTab` is the active tab, with one exceptio ## Clipboard access -There are two permissions which enables the extension to interact with the clipboard: +Two permissions enable an extension to interact with the clipboard: - `clipboardWrite` - - : Write to the clipboard using {{DOMxRef("Clipboard.write()")}}, {{DOMxRef("Clipboard.writeText()")}}, `document.execCommand("copy")` or `document.execCommand("cut")` + - : Write to the clipboard using {{DOMxRef("Clipboard.write()")}}, {{DOMxRef("Clipboard.writeText()")}}, `document.execCommand("copy")` or `document.execCommand("cut")`. - `clipboardRead` - - : Read from the clipboard using {{DOMxRef("Clipboard.read()")}}, {{DOMxRef("Clipboard.readText()")}} or `document.execCommand("paste")` + - : Read from the clipboard using {{DOMxRef("Clipboard.read()")}}, {{DOMxRef("Clipboard.readText()")}} or `document.execCommand("paste")`. See [Interact with the clipboard](/en-US/docs/Mozilla/Add-ons/WebExtensions/Interact_with_the_clipboard) for more details. diff --git a/files/en-us/mozilla/firefox/releases/147/index.md b/files/en-us/mozilla/firefox/releases/147/index.md index fef9e1d994d0859..fa67d6899cada39 100644 --- a/files/en-us/mozilla/firefox/releases/147/index.md +++ b/files/en-us/mozilla/firefox/releases/147/index.md @@ -90,6 +90,7 @@ No notable changes. ## Changes for add-on developers +- When using [navigator.clipboard.readText()](/en-US/docs/Web/API/Clipboard/readText) or [navigator.clipboard.read()](/en-US/docs/Web/API/Clipboard/read) without the `clipboardRead` permission, a clipboard paste button is displayed to obtain user confirmation. If the extension has `clipboardRead` permission, it continues to read the clipboard data without user confirmation, as before. For more information on working with the clipboard in extensions, see [Interact with the clipboard](/en-US/docs/Mozilla/Add-ons/WebExtensions/Interact_with_the_clipboard). ([Firefox bug 1773681](https://bugzil.la/1773681)) - Temporarily loaded Manifest Version 3 extensions can now load scripts from localhost, as explained in [Scripts from localhost](/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_Security_Policy#scripts_from_localhost) in the Content Security Policy article. ([Firefox bug 1864284](https://bugzil.la/1864284)) ## Experimental web features diff --git a/files/en-us/web/api/clipboard_api/index.md b/files/en-us/web/api/clipboard_api/index.md index 1ddffb9f46744d8..a0be1b5a4db14ad 100644 --- a/files/en-us/web/api/clipboard_api/index.md +++ b/files/en-us/web/api/clipboard_api/index.md @@ -80,12 +80,10 @@ Firefox & Safari: - The paste-prompt is suppressed if reading same-origin clipboard content, but not cross-origin content. - The `clipboard-read` and `clipboard-write` permissions are not supported (and not planned to be supported) by Firefox or Safari. -Firefox [Web Extensions](/en-US/docs/Mozilla/Add-ons/WebExtensions/Interact_with_the_clipboard): +Firefox [web extensions](/en-US/docs/Mozilla/Add-ons/WebExtensions/Interact_with_the_clipboard): -- Reading text is only available for extensions with the Web Extension [`clipboardRead`](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions#clipboardread) permission. - With this permission the extension does not require transient activation or a paste prompt. -- Writing text is available in secure context and with transient activation. - With the Web Extension [`clipboardWrite`](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions#clipboardwrite) permission transient activation is not required. +- Reading is available to extensions with the web extension [`clipboardRead`](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions#clipboardread) permission. With this permission, the extension doesn't require transient activation or use the paste prompt. From Firefox 147, reading is also available without the permission in a secure context, with transient activation, and after the user clicks the paste prompt in an ephemeral context menu. +- Writing is available in a secure context and with transient activation. However, with the web extension [`clipboardWrite`](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions#clipboardwrite) permission transient activation is not required. ## Examples From dd5b298285d8dc33f91c8c77f68773b513ae51f0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Jan 2026 14:06:09 +0000 Subject: [PATCH 5/6] chore(deps-dev): bump lefthook from 2.0.15 to 2.0.16 (#42938) --- package-lock.json | 88 +++++++++++++++++++++++------------------------ package.json | 2 +- 2 files changed, 45 insertions(+), 45 deletions(-) diff --git a/package-lock.json b/package-lock.json index 717237021504ae3..36cf1f8548c76c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,7 +34,7 @@ "imagemin-pngquant": "^10.0.0", "imagemin-svgo": "^11.0.1", "is-svg": "^6.1.0", - "lefthook": "^2.0.15", + "lefthook": "^2.0.16", "markdownlint-cli2": "0.20.0", "markdownlint-rule-search-replace": "1.2.0", "node-html-parser": "^7.0.2", @@ -6126,9 +6126,9 @@ } }, "node_modules/lefthook": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/lefthook/-/lefthook-2.0.15.tgz", - "integrity": "sha512-sl5rePO6UUOLKp6Ci+MMKOc86zicBaPUCvSw2Cq4gCAgTmxpxhIjhz7LOu2ObYerVRPpTq3gvzPTjI71UotjnA==", + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/lefthook/-/lefthook-2.0.16.tgz", + "integrity": "sha512-ABs3M5V9c4nqxFnZO509HXuQTu8GM8hmqc9ruV0acQ81yKlxEq70MRoYP5Z1dr5le326X8vA5qj3arJA36yE3A==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -6136,22 +6136,22 @@ "lefthook": "bin/index.js" }, "optionalDependencies": { - "lefthook-darwin-arm64": "2.0.15", - "lefthook-darwin-x64": "2.0.15", - "lefthook-freebsd-arm64": "2.0.15", - "lefthook-freebsd-x64": "2.0.15", - "lefthook-linux-arm64": "2.0.15", - "lefthook-linux-x64": "2.0.15", - "lefthook-openbsd-arm64": "2.0.15", - "lefthook-openbsd-x64": "2.0.15", - "lefthook-windows-arm64": "2.0.15", - "lefthook-windows-x64": "2.0.15" + "lefthook-darwin-arm64": "2.0.16", + "lefthook-darwin-x64": "2.0.16", + "lefthook-freebsd-arm64": "2.0.16", + "lefthook-freebsd-x64": "2.0.16", + "lefthook-linux-arm64": "2.0.16", + "lefthook-linux-x64": "2.0.16", + "lefthook-openbsd-arm64": "2.0.16", + "lefthook-openbsd-x64": "2.0.16", + "lefthook-windows-arm64": "2.0.16", + "lefthook-windows-x64": "2.0.16" } }, "node_modules/lefthook-darwin-arm64": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/lefthook-darwin-arm64/-/lefthook-darwin-arm64-2.0.15.tgz", - "integrity": "sha512-ygAqG/NzOgY9bEiqeQtiOmCRTtp9AmOd3eyrpEaSrRB9V9f3RHRgWDrWbde9BiHSsCzcbeY9/X2NuKZ69eUsNA==", + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/lefthook-darwin-arm64/-/lefthook-darwin-arm64-2.0.16.tgz", + "integrity": "sha512-kjVHkD7rfPa7M0aKJSx/yatdV9uC6o3cJyzM9zk7cg5HD7alSwchFalgF/P0w6nt7C02rAUx8C05qiWCDWaKeA==", "cpu": [ "arm64" ], @@ -6163,9 +6163,9 @@ ] }, "node_modules/lefthook-darwin-x64": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/lefthook-darwin-x64/-/lefthook-darwin-x64-2.0.15.tgz", - "integrity": "sha512-3wA30CzdSL5MFKD6dk7v8BMq7ScWQivpLbmIn3Pv67AaBavN57N/hcdGqOFnDDFI5WazVwDY7UqDfMIk5HZjEA==", + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/lefthook-darwin-x64/-/lefthook-darwin-x64-2.0.16.tgz", + "integrity": "sha512-tbJ0mdT49DNRLqknro0BvWrYNC23TTcqBJFQnQ32pq1/H9B87bTNKvKKAtogp/saxfHUzkIq1i3twZlBZ3G3Xw==", "cpu": [ "x64" ], @@ -6177,9 +6177,9 @@ ] }, "node_modules/lefthook-freebsd-arm64": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/lefthook-freebsd-arm64/-/lefthook-freebsd-arm64-2.0.15.tgz", - "integrity": "sha512-FbYBBLVbX8BjdO+icN1t/pC3TOW3FAvTKv/zggBKNihv6jHNn/3s/0j2xIS0k0Pw9oOE7MVmEni3qp2j5vqHrQ==", + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/lefthook-freebsd-arm64/-/lefthook-freebsd-arm64-2.0.16.tgz", + "integrity": "sha512-wa1KFD5tSUhw3tuetVef/BCSxbbmS7auTDNdoLx3WFeuN5RS15woSN9+E8GPGOOY1g2HCsgdLrhrexEomulfjQ==", "cpu": [ "arm64" ], @@ -6191,9 +6191,9 @@ ] }, "node_modules/lefthook-freebsd-x64": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/lefthook-freebsd-x64/-/lefthook-freebsd-x64-2.0.15.tgz", - "integrity": "sha512-udHMjh1E8TfC0Z7Y249XZMATJOyj1Jxlj9JoEinkoBvAsePFKDEQg5teuXuTGhjsHYpqVekfSvLNNfHKUUbbjw==", + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/lefthook-freebsd-x64/-/lefthook-freebsd-x64-2.0.16.tgz", + "integrity": "sha512-UXowfn2e94AwNk9UuoePRK+qiF15jZsssiyA15VK5GTtxpn6Sn+Z2QFciofxJczXXxM4abaf7qgx2OoJBt32VA==", "cpu": [ "x64" ], @@ -6205,9 +6205,9 @@ ] }, "node_modules/lefthook-linux-arm64": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/lefthook-linux-arm64/-/lefthook-linux-arm64-2.0.15.tgz", - "integrity": "sha512-1HAPmdYhfcOlubv63sTnWtW2rFuC+kT1MvC3JvdrS5V6zrOImbBSnYZMJX/Dd3w4pm0x2ZJb9T+uef8a0jUQkg==", + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/lefthook-linux-arm64/-/lefthook-linux-arm64-2.0.16.tgz", + "integrity": "sha512-U355elz4Z0AHSVqxfcglN09TGR86ov/GtYlliDknci2mmz6EWLiD3dTYnqJiwa4dYxqmuCDc/DvAL9rgb3YJiQ==", "cpu": [ "arm64" ], @@ -6219,9 +6219,9 @@ ] }, "node_modules/lefthook-linux-x64": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/lefthook-linux-x64/-/lefthook-linux-x64-2.0.15.tgz", - "integrity": "sha512-Pho87mlNFH47zc4fPKzQSp8q9sWfIFW/KMMZfx/HZNmX25aUUTOqMyRwaXxtdAo/hNJ9FX4JeuZWq9Y3iyM5VA==", + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/lefthook-linux-x64/-/lefthook-linux-x64-2.0.16.tgz", + "integrity": "sha512-7eAvBeWGAgjOKZ23OQbjJINLPImDIuDeX8dXOfk+aI6IFt2X6KCzRkp+ASUvGQtrPuNZQZT43EhW0/1jZU14ZQ==", "cpu": [ "x64" ], @@ -6233,9 +6233,9 @@ ] }, "node_modules/lefthook-openbsd-arm64": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/lefthook-openbsd-arm64/-/lefthook-openbsd-arm64-2.0.15.tgz", - "integrity": "sha512-pet03Edlj1QeFUgxcIK1xu8CeZA+ejYplvPgdfe//69+vQFGSDaEx3H2mVx8RqzWfmMbijM2/WfkZXR2EVw3bw==", + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/lefthook-openbsd-arm64/-/lefthook-openbsd-arm64-2.0.16.tgz", + "integrity": "sha512-Fcd+E17ZkWGnRSQINb5gf+rNy2So5PYn5mBljiC31dl+TgWM8Wy46mSEGveHo7lKCO3q+DkmHIa50Qm58G03AQ==", "cpu": [ "arm64" ], @@ -6247,9 +6247,9 @@ ] }, "node_modules/lefthook-openbsd-x64": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/lefthook-openbsd-x64/-/lefthook-openbsd-x64-2.0.15.tgz", - "integrity": "sha512-i+a364CcSAeIO5wQzLMHsthHt/v6n3XwhKmRq/VBzPOUv9KutNeF55yCE/6lvuvzwxpdEfBjh6cXPERC0yp98w==", + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/lefthook-openbsd-x64/-/lefthook-openbsd-x64-2.0.16.tgz", + "integrity": "sha512-uL5nOkz8eBtQHped0/tB5X8clZ5kfnyjQrv1fpKbGAjeFHI2J+GmRqcn6Awq2IeuBbQvkyV6jDjpATyHBp5mCA==", "cpu": [ "x64" ], @@ -6261,9 +6261,9 @@ ] }, "node_modules/lefthook-windows-arm64": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/lefthook-windows-arm64/-/lefthook-windows-arm64-2.0.15.tgz", - "integrity": "sha512-69u5GdVOT4QIxc2TK5ce0cTXLzwB55Pk9ZnnJNFf1XsyZTGcg9bUWYYTyD12CIIXbVTa0RVXIIrbU9UgP8O1AQ==", + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/lefthook-windows-arm64/-/lefthook-windows-arm64-2.0.16.tgz", + "integrity": "sha512-U61bNWzD6Vd2kjuJ7b4voPfTQ4mlBFOyTpCU3k/h0YjpKDQEFT1T5fDKkDothdnw/JVDSgrclIcfAY7Jyr/UIg==", "cpu": [ "arm64" ], @@ -6275,9 +6275,9 @@ ] }, "node_modules/lefthook-windows-x64": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/lefthook-windows-x64/-/lefthook-windows-x64-2.0.15.tgz", - "integrity": "sha512-/zYEndCUgj8XK+4wvLYLRk3AcfKU6zWf2GHx+tcZ4K2bLaQdej4m+OqmQsVpUlF8N2tN9hfwlj1D50uz75LUuQ==", + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/lefthook-windows-x64/-/lefthook-windows-x64-2.0.16.tgz", + "integrity": "sha512-dCHi2+hebhPI0LQUGRNjPMsGRyXhrTN3Y/b8M4HO8KVyGamKB3Yemf67ya81tZopDkxNVy5XUBXLYWKGhnAfLQ==", "cpu": [ "x64" ], diff --git a/package.json b/package.json index 206e9d5d5809e39..32d0a581883a899 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "imagemin-pngquant": "^10.0.0", "imagemin-svgo": "^11.0.1", "is-svg": "^6.1.0", - "lefthook": "^2.0.15", + "lefthook": "^2.0.16", "markdownlint-cli2": "0.20.0", "markdownlint-rule-search-replace": "1.2.0", "node-html-parser": "^7.0.2", From c85c8052aea07baf133c0b491c12708a9a965a68 Mon Sep 17 00:00:00 2001 From: Estelle Weyl Date: Tue, 27 Jan 2026 15:33:40 +0100 Subject: [PATCH 6/6] CSS guides sidebar (#42857) --- files/sidebars/cssref.yaml | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/files/sidebars/cssref.yaml b/files/sidebars/cssref.yaml index a114d828c4dbc0e..a4c34856aa7e2de 100644 --- a/files/sidebars/cssref.yaml +++ b/files/sidebars/cssref.yaml @@ -88,6 +88,10 @@ sidebar: - /Web/CSS/Guides/Containment/Container_queries - /Web/CSS/Guides/Containment/Using - /Web/CSS/Guides/Containment/Container_size_and_style_queries + - title: Counters + details: closed + children: + - /Web/CSS/Guides/Counter_styles/Using_counters - title: CSSOM_view details: closed children: @@ -156,10 +160,9 @@ sidebar: - /Web/CSS/Guides/Images/Using_object-view-box - /Web/CSS/Guides/Images/Replaced_element_properties - /Web/CSS/Guides/Images/Implementing_image_sprites - - title: Lists_and_counters + - title: Lists details: closed children: - - /Web/CSS/Guides/Counter_styles/Using_counters - /Web/CSS/Guides/Lists/Indenting - title: Logical_properties details: closed @@ -196,13 +199,14 @@ sidebar: details: closed children: - /Web/CSS/Guides/Positioned_layout/Stacking_context - - /Web/CSS/Guides/Positioned_layout/Stacking_context/Example_1 - - /Web/CSS/Guides/Positioned_layout/Stacking_context/Example_2 - - /Web/CSS/Guides/Positioned_layout/Stacking_context/Example_3 - /Web/CSS/Guides/Positioned_layout/Stacking_floating_elements - /Web/CSS/Guides/Positioned_layout/Understanding_z-index - /Web/CSS/Guides/Positioned_layout/Using_z-index - /Web/CSS/Guides/Positioned_layout/Stacking_without_z-index + - title: Properties and Values API + details: closed + children: + - /Web/CSS/Guides/Properties_and_values_API/Houdini - type: listSubPages path: /Web/CSS/Guides/Scroll_anchoring title: Scroll_anchoring @@ -229,6 +233,7 @@ sidebar: - /Web/CSS/Guides/Shapes/From_box_values - /Web/CSS/Guides/Shapes/From_images - /Web/CSS/Guides/Shapes/Using_shape-outside + - /Web/CSS/Guides/Shapes/Shape_generator - title: Syntax details: closed children: @@ -370,6 +375,7 @@ l10n: Combinators: Kombinatoren Conditional_rules: Bedingte Regeln Containment: Containment + Counters: Zähler CSSOM_view: CSSOM-Ansicht Display: Display Flexbox: Flexbox @@ -377,7 +383,7 @@ l10n: Functions: Funktionen Grid: Raster Images: Bilder - Lists_and_counters: Listen und Zähler + Lists: Listen Logical_properties: Logische Eigenschaften Media_queries: Media-Abfragen Modules: Module @@ -408,6 +414,7 @@ l10n: Conditional_rules: Conditional rules Containment: Containment Coordinate_systems: Coordinate systems (API) + Counters: Counters CSSOM_view: CSSOM view Custom_functions_and_mixins: Custom functions and mixins Display: Display @@ -417,10 +424,10 @@ l10n: Fonts: Fonts Grid: Grid Images: Images - Lists_and_counters: Lists and counters + Lists: Lists Logical_properties: Logical properties Media_queries: Media queries - Nesting: Nesting style rules + Nesting: Nesting Overflow: Overflow Positioning: Positioning Selectors: Selectors @@ -440,13 +447,14 @@ l10n: Box_model: Modèle de boîte Columns: Colonnes Conditional_rules: Règles conditionnelles + Counters: Compteurs CSSOM_view: Vue CSSOM Flexbox: Boîtes flexibles Display: Mis en page Fonts: Polices Grid: Grille Images: Images - Lists_and_counters: Listes et compteurs + Lists: Listes Logical_properties: Propriétés logiques Media_queries: Requêtes média Positioning: Positionnement @@ -474,6 +482,7 @@ l10n: Combinators: 結合子 Conditional_rules: 条件付きルール Containment: コンテナー + Counters: カウンター Coordinate_systems: 座標系 (API) CSSOM_view: CSSOM view Custom_functions_and_mixins: カスタム関数とミックスイン @@ -486,7 +495,7 @@ l10n: Grid: グリッド Images: 画像 Layout cookbook: レイアウト料理帳 - Lists_and_counters: リストとカウンター + Lists: リスト Logical_properties: 論理的プロパティ Masking: マスク Media_queries: メディアクエリー @@ -519,13 +528,14 @@ l10n: Box_model: 박스 모델 Columns: 열 Conditional_rules: CSS에서 규칙 정의하기 + Counters: 카운터 CSSOM_view: CSSOM view Display: 플로 레이아웃 Flexbox: 플렉스박스 Fonts: 글꼴 Grid: 그리드 Images: 이미지 - Lists_and_counters: 리스트와 카운터 + Lists: 리스트 Logical_properties: Logical 속성 Media_queries: 미디어 쿼리 Positioning: 위치 잡기 @@ -545,13 +555,14 @@ l10n: Box_model: Блочная модель Columns: Колонки Conditional_rules: Условные конструкции + Counters: Счетчики CSSOM_view: CSSOM View Display: Потоковая раскладка Flexbox: Флексбокс Fonts: Шрифты Grid: Грид Images: Изображения - Lists_and_counters: Списки и счетчики + Lists: Списки Logical_properties: Логические свойства Media_queries: Медиа-запросы Positioning: Позиционирование @@ -576,6 +587,7 @@ l10n: Color_contrast: Web 无障碍:色彩对比度 Columns: 多列 Conditional_rules: 条件规则 + Counters: 计数器 CSSOM_view: CSS 对象模型视图 Display: 流式布局 Filter_effects: 滤镜效果 @@ -584,7 +596,7 @@ l10n: Functions: 函数 Grid: 网格布局 Images: 图片 - Lists_and_counters: 列表和计数器 + Lists: 列表 Logical_properties: 逻辑属性 Media_queries: 媒体查询 Nesting: 嵌套样式规则