Web Platform Design Principles

Editor’s Draft, 7 July

This version:
https://w3ctag.github.io/design-principles/
Editor:
Sangwhan Moon ( Invited Expert )
Former Editors:
Domenic Denicola ( Google )
( Microsoft )
By:
Members of the TAG , past and present
Participate:
GitHub w3ctag/design-principles ( file an issue ; open issues )
Not Ready For Implementation

This spec is not yet ready for implementation. It exists in this repository to record the ideas and promote discussion.

Before attempting to implement this spec, please contact the editors.


Abstract

This document contains a set of design principles to be used when designing Web Platform technologies. These principles have been collected during the Technical Architecture Group’s discussions in reviewing developing specifications. We encourage specification designers to read this document and use it as a resource when making design decisions.

Status of this document

This section describes the status of this document at the time of its publication. Other documents may supersede this document. A list of current W3C publications and the latest revision of this technical report can be found in the W3C technical reports index .

This document was published by the W3C Technical Architecture Group (TAG) as an Editor’s Draft. Publication as an Editor’s Draft does not imply endorsement by the W3C Membership. This is a draft document and may be updated, replaced or obsoleted by other documents at any time. It is inappropriate to cite this document as other than work in progress.

Feedback and comments on this document are welcome. Please file an issue in this document’s GitHub repository .

This document is governed by the 1 March 2019 W3C Process Document .

1. Principles behind design of Web APIs

1.1. Put user needs first (Priority of Constituencies)

If a trade-off needs to be made, always put user needs above all.

The internet is for end users : any change made to the web platform has the potential to affect vast numbers of people, and may have a profound impact on any person’s life.

User needs come before the needs of web page authors, which come before than the needs of user agent implementors, which come before than the needs of specification writers, which come before theoretical purity.

Similarly, when beginning to design an API, be sure to understand and document the user need that the API aims to address.

Like all principles, this is not absolute. Ease of authoring affects how content reaches users. User agents have to prioritize finite engineering resources, which affects how features reach authors. Specification writers also have finite resources, and theoretical concerns reflect underlying needs of all of these groups.

See also:

1.2. It should be safe to visit a web page

Hyperlinks, links from one page to another, are one of the foundations of the Web.

Following a link, or visiting a web page, should be safe: users doing this should not have to fear for the security of their computer or for essential aspects of their privacy. (But it’s not completely safe, in the sense that users may face consequences if their use of the Web is harming others.) Furthermore, users should understand that it is safe (and how it isn’t) so they can make informed decisions between use of the Web versus other technologies.

Saying “essential aspects” here skips over quite a bit of detail. The Web today is far from being perfectly private. One aspect of privacy problems is when reality doesn’t meet expectations. For example, a person walking down the street generally expects to be recognized by their friends, but (depending on the country) may not expect that they walked down that street at that time to be recorded in a permanent government database. Online, people might have less understanding of what is or isn’t possible, and their expectations might differ more widely.

We can probably make privacy on the Web better than it is today:

When we add new features to the Web that might weaken the security or privacy characteristics that the Web currently has, we should consider the tradeoffs involved in that particular feature. A new feature with security risks might still make users safer, for example, because they would download and run native binary software less often, and downloading and running binaries is much riskier than visiting a Web page. However, we should also consider the tradeoff against the idea that it is safe to visit Web pages: adding a risky feature to the Web teaches the Web’s users that the Web is less safe, and this erodes one of the core values of the Web: that it is safe to visit websites. We should seek to give users a true picture of the safety of the Web so that they can act based on that understanding of safety (in comparison to the safety of alternatives), and at the same time we should seek to keep the Web safe for its users and work to make it safer.

1.3. Trusted user interface should be trustworthy

Since users of the Web can follow links to sites they might not know already, software that lets them access the Web generally provides user interface to show the user who they’re interacting with and how, such as by displaying part or all of the URL or other information about the site, or whether the connection is secure. Since users rely on this information to learn what site they’re on and to make judgments about whether it is trustworthy, this software is designed so that sites are not able to override this user interface or spoof it in a way that would confuse users. Therefore, when we add new features to the web, we should consider whether they require new complexity in this user interface, and whether that complexity would reduce users' ability to make correct judgments about who they’re interacting with or whether the user interface is trustworthy or spoofed.

In some cases, we might add features to the Web that are not appropriate to allow for any website a user visits, but that are reasonable to have if the user agrees to use the feature based on a reasonable understanding of what will happen as a result. One example of such a feature might be support for location: many users seem likely to understand what it means to share their current location with a website and be able to consent to doing so (even though they might not fully understand the privacy implications of doing so). Another example of such a feature would be camera access, where users understand that they’re sharing the image in the rectangle visible to the camera (even if they might not understand everything someone might do with the data).

We should not depend on asking the user for consent (via a permission prompt or other mechanism) if we don’t have a way to express that request in a way that users will understand what is being requested and the main implications of giving their consent. Understanding whether this is the case may require studying users understanding of potential user interfaces, although in many cases experienced user experience designers may be able to extrapolate from previous experience.

We should also not ask users to consent to something that can also happen if the user does not consent. The existence of prompts that request a user’s consent for something (from trusted user interface in their browser or other user agent) imply that the thing for which consent is being requested will not happen if the user doesn’t consent. Therefore we should not have user agents request consent for something that they can’t effectively prevent. Doing so would send the wrong message about the safety characteristics of the Web and would lead users to be disappointed when the expectations of privacy, which they learned from trusted user interface, did not match reality.

Asking users for consent via permission prompts can reinforce the idea that the web is safe by showing the user that certain things won’t happen without their permission. But frequently asking users for consent can also show how scary a place the web is by showing how many sites are willing to ask for intrusive and unnecessary permissions.

1.5. Support the full range of devices and platforms

Features on the web should work across different input and output devices, different platforms, and different media, as much as this is reasonably possible. For example, they should not be specific to a particular screen size, a particular operating system or browser engine, or assume the user is using a mouse.

It is not always possible for a feature to work in all contexts: for example, hyperlinks don’t work when printed on paper, and :hover doesn’t work on touch input devices where there isn’t a mouse pointer position. However, these features are still valuable across a range of devices, and can be adapted to devices that don’t support their original intent (for example, hyperlink targets may be converted to footnotes when printing).

Features should also be designed so that the easiest way to use them works across devices as well. For example, the display:block , display: flex , and display: grid layout models in CSS all default to placing content within the available space and without overlap, so that it works across devices of various sizes, across browsers and operating systems that have different fonts available, and across different user settings for default text size.

2. API Design Across Languages

2.1. Prefer simple solutions

Simple solutions are generally better than complex solutions, although they may be harder to find. Simpler features are easier for user agents to implement and test, more likely to be interoperable, and easier for authors to understand. See related [LEAST-POWER]

2.2. Naming things

Names take meaning from:

Use common words

API naming must be done in easily readable US English. Keep in mind that most web developers are not native English speakers. Whenever possible, names should be chosen that use common vocabulary non-native English speakers are likely to understand when first encountering the name.

For example horizontalAlignment is a more English-readable name than alignmentHorizontal .

Value readability over brevity, though always keep in mind that sometimes the shorter name is the clearer one. For instance, it may be appropriate to use technical language or well-known terms of art in the specification where the API is defined.

For example, the Fetch API’s Body mixin’s json() method is named for the kind of object it returns. JSON is a well-known term of art among web developers likely to use the Fetch API. It would harm comprehension to name this API less directly connected to its return type. [FETCH]

Use ASCII names

Names must adhere to the local language restrictions, for example CSS ident rules etc. and should be in the ASCII range .

Consultation

Please consult widely on names in your APIs. You may find good names or inspiration in surprising places. What are similar APIs named on other platforms, or in popular libraries in various programming languages? Ask end users and developers what they call things that your API works with or manipulates. Look at other web platform specifications, and seek advice from others working in related areas of the platform.

Pay particular attention to advice you receive with clearly-stated rationale based on underlying principles.

Tantek Çelik extensively researched how to name the various pieces of a URL. The editors of the URL spec have relied on this research when editing that document. [URL]

Future-proofing

Naming should be generic and future-proof whenever possible.

The name should not be directly associated with a brand or specific revision of the underlying technology whenever possible; technology becomes obsolete, and removing APIs from the web is difficult.

The Remote Playback API was not named after one of the pre-existing, proprietary systems it was inspired by (such as Chromecast or AirPlay). Instead, general terms that describe what the API does were chosen. [REMOTE-PLAYBACK]

The keydown and keyup KeyboardEvent s were not named for the specific hardware bus that keyboards used at the time. Instead, generic names were chosen that are as applicable to today’s Bluetooth and USB keyboards as they were to PS/2 and ADB keyboards back then. [UIEVENTS]

Consistency

Naming schemes should aim for consistency, to avoid confusion.

Sets of related names should agree with each other in:

Boolean properties vs. boolean-returning methods

Boolean properties, options, or API parameters which are asking a question about their argument should not be prefixed with is , while methods that serve the same purpose, given that it has no side effects, should be prefixed with is to be consistent with the rest of the platform.

Use casing rules consistent with existing APIs

Although they haven’t always been uniformly followed, through the history of web platform API design, the following rules have emerged:

Casing rule Examples
Methods and properties
(Web IDL attributes, operations, and dictionary keys)
Camel case document . createAttribute ()
document . compatMode
Classes and mixins
(Web IDL interfaces)
Pascal case NamedNodeMap
NonElementParentNode
Initialisms in APIs All caps, except when the first word in a method or property HTMLCollection
element . innerHTML
document . bgColor
Repeated initialisms in APIs Follow the same rule HTMLHRElement
RTCDTMFSender
The abbreviation of "identity" Id , except when the first word in a method or property node . getElementById ()
event . pointerId
credential . id
Enumeration values Lowercase, dash-delimited "no-referrer-when-downgrade"
Events Lowercase, concatenated autocompleteerror
languagechange
HTML elements and attributes Lowercase, concatenated < figcaption >
< textarea maxlength >
JSON keys Lowercase, underscore-delimited manifest . short_name
Note that in particular, when a HTML attribute is reflected as a property, the attribute and property’s casings will not necessarily match. For example, the HTML attribute ismap on img elements is reflected as the isMap property on HTMLImageElement .

The rules for JSON keys are meant to apply to specific JSON file formats sent over HTTP or stored on disk, and don’t apply to the general notion of JavaScript object keys.

Repeated initialisms are particularly non-uniform throughout the platform. Infamous historical examples that violate the above rules are XMLHttpRequest and HTMLHtmlElement . Do not follow their example; instead always capitalize your initialisms, even if they are repeated.

Warning about dangerous features

Where possible, mark features that weaken the guarantees provided to developers by making their names start with "unsafe" so that this is more noticeable.

For example, Content Security Policy (CSP) provides protection against certain types of content injection vulnerabilities. CSP also provides features that weaken this guarantee, such as the unsafe-inline keyword, which reduces CSP’s own protections by allowing inline scripts.

2.3. New features should be detectable

The existence of new features should generally be detectable, so that web content can act appropriately whether the feature is present or not. This applies both to features that are not present because they are not implemented, and to features that may not be present in a particular configuration (ranging from features that are present only on particular platforms to features that are only available in secure contexts).

It should generally be indistinguishable why a feature is unavailable, so that feature detection code written for one case of unavailability (e.g., the feature not being implemented yet in some browsers) also works in other cases (e.g., a library being used in a non-secure context when the feature is limited to secure contexts).

However, some features may be for interaction with devices that may or may not be available. Detection of device availability should generally be separate from feature detection. This helps developers who may want to handle device unavailability differently from lack of support for the feature. It also protects users; for more detail see § 8.2 Use care when exposing APIs for selecting or enumerating devices .

Detection should always be possible from script, but in some cases the feature should also be detectable in the language where it is used (such as @supports in CSS).

Note that some features should not be detectable. In general, features which add capabilities that sites can use should be detectable, whereas features which are for end users of the web rather than for developers should not be detectable. For instance, sites should not be able to detect if private browsing mode is engaged, or if assistive technologies are being used. See § 2.6 Do not reveal that private browsing mode is engaged , § 2.7 Do not reveal that assistive technologies are being used , and the Observations on Private Browsing Modes TAG Finding.

2.4. Consider limiting new features to secure contexts

It may save you significant time and effort to pre-emptively restrict your feature to Secure Contexts.

The TAG is on the record in supporting an industry-wide move to Secure the Web and applaud efforts to shift web traffic to secure connections. A great deal of effort has gone into debating which features should be restricted to Secure Contexts . Opinions vary amongst engine vendors, leading to difficult choices for feature designers. Some vendors require all new features be restricted this way, whereas others take a more selective approach.

This backdrop makes it difficult to provide advice about the extent to which your feature should be restricted. What we can highlight is that Secure Context-restricted features face the least friction in gaining wide adoption amongst these varying regimes.

Specification authors can limit most features defined in Web IDL , to secure contexts by using the [ SecureContext ] extended attribute on interfaces, namespaces, or their members (such as methods and attributes). Similar ways of marking features as limited to secure contexts should be added to other major points where the Web platform is extended over time (for example, the definition of a new CSS property). However, for some types of extension points (e.g., dispatching an event), limitation to secure contexts should just be defined in normative prose in the specification.

As described in § 2.3 New features should be detectable , the existence of features should generally be detectable, so that web content can act appropriately if the feature is present or not. Since the detection should be the same no matter why the feature is unavailable, a feature that is limited to secure contexts should, in non-secure contexts, be indistinguishable from a feature that is not implemented. However, if, for some reason (a reason that itself requires serious justification), it is not possible for developers to detect whether a feature is present, limiting the feature to secure contexts might cause problems for libraries that may be used in either secure or non-secure contexts.

If a feature would pose a risk to user privacy or security without the authentication, integrity, or confidentiality that is present only in secure contexts, then the feature must be limited to secure contexts. One example of a feature that should be limited to secure contexts is geolocation, since the authentication and confidentiality provided by secure contexts reduce the risks to user privacy.

2.5. Consider how your API should behave in private browsing mode

In general, APIs should behave the same in and out of private browsing mode . This is because the use of private browsing mode should not be revealed to sites. See § 2.6 Do not reveal that private browsing mode is engaged .

However, there are some situations in which APIs should consider behaving differently when private browsing mode is engaged, as discussed in our privacy & security questionnaire .

For instance, if your API would reveal information that would allow for the correlation of a single user’s activity both in and out of private browsing mode, consider possible mitigations such as introducing noise or augmenting permission prompts with additional information which would help users meaningfully consent to such correlation (see § 1.4 Ask users for meaningful consent when appropriate ).

The Payment Request API 's show() method , when called, allows User Agents to act as if the user had immediately aborted the payment request .

This enables User Agents to automatically abort payment requests in private browsing mode (thus protecting sensitive information such as the user’s shipping or billing address) without revealing that private browsing mode is engaged.

Private browsing modes enable users to browse the web without leaving any trace of their private browsing on their device. Therefore, APIs which provide client-side storage should not persist data stored while private browsing mode is engaged after it is disengaged. This can and should be done without revealing any detectable API differences to the site.

User Agents which support localStorage should not persist storage area changes made while private browsing mode is engaged.

If the User Agent has two simultaneous sessions with a site, one in private browsing mode and one not, storage area changes made in the private browsing mode session should not be revealed to the other browsing session, and vice versa. (The storage event should not be fired at the other session’s window object .)

See also

2.6. Do not reveal that private browsing mode is engaged

Some people use private browsing mode for their own safety ; just the fact that they have or are using private browsing mode may be sensitive information about them. Consider the harm that could be visited upon people—especially vulnerable people—were their use of private browsing mode revealed to others who wield power over them (such as employers, parents, partners, or state actors ).

Given such dangers, websites should not be able to detect that private browsing mode is engaged.

User Agents which support IndexedDB should not disable it in private browsing mode, because that would reveal that private browsing mode is engaged

See also

2.7. Do not reveal that assistive technologies are being used

The web platform must be accessible to people with disabilities. APIs which reveal the use of assistive technologies to sites enable sites to deny access to services to those who make use of assistive technologies.

People who make use of assistive technologies are often vulnerable members of society ; their use of assistive technologies is sensitive information about them. It should be safe for them to browse the web . Consider the risks of revealing their use of assistive technologies to others (including state actors ) who may wish them harm.

Given these two concerns, websites should not be able to detect that assitive technologies are being used.

The Accessibility Object Model (AOM) used to define a set of events which, when fired, revealed the use of assistive technology .

AOM has since removed these events and replaced them with synthetic DOM events which do not reveal the use of assistive technology.

See also

3. Cascading Style Sheets (CSS)

3.1. Separate CSS properties based on what should cascade separately

Decisions about what should be in the same CSS property or in different CSS properties should be based on what makes sense to set independently. CSS cascading allows declarations (which are property-value pairs) from different rules or different style sheets to override one another in a process called the cascade. Thus things that should always be set together belong in the same properties, but things that make sense to set in different places should be in separate properties.

For example, the "size" and "sink" aspects of the initial-letter property belong in a single property because they are part of a single initial letter effect (e.g., a drop cap, sunken cap, or raised cap) and should nearly always be specified together. However, the initial-letter-align property should be separate because it sets an alignment policy for all of these effects across the document which is a general stylistic choice and a function of the script (e.g., Latin, Cyrillic, Arabic) used in the document.

3.2. Make appropriate choices for whether CSS properties are inherited

An important choice in the design of every CSS property is whether the property is inherited . This affects what happens when no value is specified for the property on an element. If the property is not inherited, then that element gets the property’s initial value. If the property is inherited, then that element gets the parent element’s computed value.

The decision about whether a property should be inherited should be based largely on what should happen when the property is specified on multiple elements. If setting the property on a descendant element needs to override (rather than add to) the effect of setting it on an ancestor, then the property should probably be inherited. If setting the property on a descendant element is a separate effect that adds to setting it on an ancestor, then the property should probably not be inherited.

One specific example that is common is that if a property has an effect on text, then it is almost always in the class where a descendant element needs to override (rather than add to) the effect of setting it on an ancestor, and the property should be inherited. This is also needed to maintain the design principle that inserting an unstyled inline element around a piece of text does not change the appearance of that text.

A specification of an non-inherited property requiring that the handling of an element look at the value of that property on its ancestors (which may also be slow) is a "code smell" that suggests that the property likely should have been inherited.

Likewise, a specification of an inherited property requiring that the handling of an element ignore the value of a property if it’s the same as the value on the parent element is a "code smell" that suggests that the property likely should not have been inherited.

For example, the background-image property is not inherited. It causes an image to be drawn over part or all of the element (though this image might not be fully opaque). The CSS painting model allows other things to draw on top of that background, including the backgrounds of descendant elements. But specifying background-image: none on a descendant element does not cause a hole to be poked in the ancestor element’s background, since the entire effect comes from the property being used on a single element. Setting background-image: none is useful to override a declaration lower in the cascade on the same element, but this is a different feature from replacing the backgrounds that are drawn by ancestors.

If the background-image property had been inherited, then the specification would have had to create a good bit of complexity to avoid a partially-transparent image being visibly repeated for each descendant element. This complexity probably would have involved behaving differently if the property had the same value on the parent element, which is the "code smell" mentioned above that suggests that a property likely should not have been inherited.

Another example is the font-size property, which is inherited. It sets the size of the font used for the text in the element, and continues to apply to any descendants that don’t have a declaration setting font-size to a different value.

If the font-size property were not inherited, then it would probably have to have an initial value that requires walking up the ancestor chain to find the nearest ancestor that doesn’t have that value. This is the "code smell" mentioned above that suggests that a property likely should have been inherited.

A more subtle example is the text-decoration property. It could reasonably be described as either an inherited or non-inherited property, but the resulting behavior is different in a number of observable ways.

As an inherited property, text-decoration would draw underlines (or other decorations) in the style and position of each piece of text. As a non-inherited property, it would draw underlines in the style and position of the element that specifies text-decoration . These behaviors lead to different results when the element that has text-decoration declared has descendants that are at a different vertical-alignment (e.g., superscripts), in a different color, or in a different font size.

The property was designed as not inherited because the behavior of having underlines have a consistent color, thickness, and position was considered the better behavior.

However, in the end, some of the characteristics of the other behavior (such as its interaction with visibility or text selection) were also seen as desirable, and it ended up being specified in a somewhat hybrid way that does involve the "code smell" of walking up the ancestor chain looking for text decorations on ancestors.

3.3. Choose the computed value type based on how the property should inherit

Another important choice in the design of every CSS property is what its computed value is. This is an abstract description of the value that is inherited when the property inherits. It is also important for starting and animation of CSS Transitions . This is most important for inherited properties, but it does matter for all properties.

Note: Due to a somewhat unfortunate history, this is not the value that is returned by getComputedStyle() , which is actually the resolved value , which can be the result of further computation starting from the computed value.

The definition of a CSS property often has interactions with other CSS properties. It is often possible for these interactions to happen at the computed value stage or at a later stage such as the used value . Since inheritance operates on the computed value, whether an interaction should affect the value that descendants inherit is a major factor in the choice of where that interaction should be handled.

The dependencies that do affect computed values are required to not be circular.

Making decisions about whether dependencies should affect computed values often involves tradeoffs of complexity. Sometimes it is less complex for the behavior to happen as part of the computation of the computed value; sometimes it is less complex for the behavior to happen in whatever handles the computed value. (This complexity might be in the specification, in implementations, or both. Complexity in implementations might affect how fast things run for end users.)

For more information about computed values, see Computed Values Patterns .

For example, the line-height property accepts values that are numbers, such as line-height: 1.4 . This value is a multiple of the font-size , so if the font-size is 20px then this would mean the line height is 28px .

However, the computed value in this case is the number 1.4 , not the length 28px . This is important because the value can inherit to elements that have a different font size, and preserving the "multiple of the current element’s font" behavior would be broken if it turned into a length at computed-value time:

<body style="line-height: 1.4">
  <p>For example, this body text has a line height
  that’s 1.4 times its font size.</p>
  <h2 style="font-size: 200%">
    And this heading also has a line-height that’s
    1.4 times the h2’s larger font size, and *not*
    1.4 times the body’s font size, which would lead
    to text overlap
  </h2>
</body>

These number values are generally the preferred values to use for line-height because they inherit better than length values.

3.4. Naming of CSS properties and values

The names of CSS properties are usually nouns, and the names of their values are usually adjectives (although sometimes nouns).

Words in properties and values are separated by hyphens. Abbreviations are generally avoided.

Use the root form of words when possible rather than a form with a grammatical prefix or suffix (for example, "size" rather than "sizing").

The list of values of a property should generally be chosen so that new values can be added. Avoid values like yes , no , true , false , or things with more complex names that are basically equivalent to them.

Avoid words like "mode" or "state" in the names of properties, since properties are generally setting a mode or state.

See § 2.2 Naming things for general (cross-language) advice on naming.

4. JavaScript Language

4.1. Web APIs are for JavaScript

The language that web APIs are meant to be used in, and specified for, is JavaScript (also known as [ECMASCRIPT] ). They are not language-agnostic, and are not meant to be.

This is sometimes a confusing point because [WEBIDL] descended from the language-agnostic OMG IDL (and at one point, included "Java Bindings"). Even today, the structure of the document contains a confusing and redundant division between the "Interface definition language" and the "ECMAScript binding". Rest assured that this division is simply a historical artifact of document structure, and does not imply anything about the intent of Web IDL in general. The only reason it remains is that nobody has taken the time to eradicate it.

As such, when designing your APIs, your primary concern should be with the interface you present to JavaScript developers. You can freely rely upon language-specific semantics and conventions, with no need to keep things generalized.

4.2. Preserve run-to-completion semantics

Web APIs are essentially vehicles for extruding C++- (or Rust-) authored capabilities into the JavaScript code that developers write. As such, it’s important to respect the invariants that are in play in normal JavaScript code. One of the most important of these is run-to-completion semantics : wherein each turn of the JavaScript event loop is processed completely before returning control to the user agent.

In particular, this means that JavaScript functions cannot be preempted mid-execution, and thus that any data observed within the function will stay constant as long as that function is active. This is not the case in other languages, which allow data races via multithreading or other techniques—a C function can be preempted at any time, with the bindings it has access to changing values from one line to the next.

This no-data-races invariant is extensively relied upon in JavaScript programs. As such, the invariant must never be violated—even by web APIs, which are often implemented in languages that do allow data races. Although the user agent may be using threads or other techniques to modify state in parallel , web APIs must never expose such changing state directly to developers. Instead, they should queue a task to modify author-observable state (such as an object property).

4.3. Do not expose garbage collection

Web APIs should not expose a way for author code to deduce when/if garbage collection of JavaScript objects has run.

Although some APIs may expose garbage collection, such as some implementations of getElementsByTagName , and the JavaScript WeakRefs proposal (which has multiple implementer support), API designers are strongly encouraged to avoid points in their own APIs that depend on this timing.

The reason for this is somewhat subtle. The more that Web API semantics are affected by garbage collection timing (or whether objects are collected at all), the more programs will be affected by changes in this timing. But user agents differ significantly in both the timing of garbage collection and whether certain objects are collected at all, which means the resulting code will be non-interoperable. Worse, according to the usual rules of game theory as applied to browsers, this kind of scenario could force other user agents to copy the garbage collection timing of the original in order to create interoperability. This would cause current garbage collection strategies to ossify, preventing improvement in one of the most dynamic areas of JavaScript virtual machine technology.

In particular, this means that you shouldn’t expose any API that acts as a weak reference, e.g. with a property that becomes null once garbage collection runs. Such freeing of memory should be entirely deterministic.

For example, currently, getElementsByTagName permits reusing the HTMLCollection which it creates, when it’s called with the same receiver and tag name. In practice, engines reuse the output if it has not been garbage collected. This creates behavior which differs based on the details of garbage collection, which is strongly discouraged. If getElementsByTagName were designed today, the advice to the specification authors would be to either reliably reuse the output, or to produce a new HTMLCollection each time it’s invoked.

The case of getElementsByTagName is particularly insidious because nothing about the API visibly relates to garbage collection details. By contrast, special APIs for this particular purpose such as WeakRef and FinalizationGroup have their GC interaction as a specific part of their contract with the developer.

5. JavaScript API Surface Concerns

5.1. Attributes should behave like data properties

[WEBIDL] attributes are reified in JavaScript as accessor properties, i.e. properties with separate getter and setter functions which can react independently. This is in contrast to the "default" style of JavaScript properties, data properties, which do not have configurable behavior but instead can simply be set and retrieved, or optionally marked read-only so that they cannot be set.

Data property semantics are what are generally expected by JavaScript developers when interfacing with objects. As such, although getters and setters allow infinite customizability when defining your Web IDL attributes, you should endeavor to make the resulting accessor properties behave as much like a data property as possible. Specific guidance in this regard includes:

If you were thinking about using an attribute, but it doesn’t behave this way, you should probably use a method instead.

5.2. Consider whether objects should be live or static

Objects returned from functions, attribute getters, etc., can either be live or static. A live object is one that continues to reflect changes made after it was returned to the caller. A static object is one that reflects the state at the time it was returned.

Objects that are the way state is mutated are generally live. For example, DOM nodes are returned as live objects, since they are the API through which attributes are set and other changes to the DOM are made. They also reflect changes to the DOM made in other ways (such as through user interaction with forms).

Objects that represent a collection that might change over time (and that are not the way state is mutated) should generally be returned as static objects. This is because it is confusing to users of the API when a collection changes while being iterated. Because of this, it is generally considered a mistake that methods like getElementsByTagName() return live objects; querySelectorAll() was made to return static objects as a result of this experience. On the other hand, even though URLSearchParams represents a collection, it should be live because the collection is mutated through that object.

Note: It’s possible that some of this advice should be reconsidered for maplike and setlike types, where iterators have reasonable behavior for mutation that happens during iteration. This point likely needs further discussion, and perhaps further experience of use of these types.

It’s also worth considering the implications of having live versus static objects for the speed of implementations of the API. When the data needed by an object are expensive to compute up-front, there is an advantage for that object to be live so that the results can be computed lazily, such as for getComputedStyle() . On the other hand, if the data needed by an object are expensive to keep up-to-date, such as for the NodeList returned from querySelectorAll() , then providing a static object avoids having to keep the object updated until it is garbage collected (which may be substantially after its last use).

Likewise, the choice of live versus static objects can influence the memory use of an API. If each call of a method returns a new static object, and the objects are large, then substantial amounts of memory can be wasted until the next garbage collection.

The choice of whether an object is live or static may also influence whether it should be returned from an attribute getter or from a method. See § 5.1 Attributes should behave like data properties . In particular, if a result that changes frequently is returned as a static object, it should probably be returned from a method rather than an attribute getter.

5.3. Prefer dictionary parameters over boolean parameters or other unreadable parameters

APIs should generally prefer dictionary parameters (with named booleans in the dictionary) over boolean parameters. This makes the code that calls the API much more readable . It also makes the API more extensible in the future, particularly if multiple booleans are needed.

For example, new Event ( "exampleevent" , { bubbles : true , cancelable : false }) is much more readable than new Event ( "exampleevent" , true , false ) .

Furthermore, the booleans in dictionaries need to be designed so that they all default to false. If booleans default to true, then users of the API will find unexpected JavaScript behavior since { passive : false } and { passive : undefined } will produce different results. But at the same time, it’s important to avoid naming booleans in negative ways, because then code will have confusing double-negatives. These pieces of advice may sometimes conflict, but the conflict can be avoided by using opposite words without negatives, such as “repeat” versus “once”, “isolate” versus “connect”, or “private” versus “public”.

Likewise, APIs should use dictionary parameters to avoid other cases of difficult to understand sequences of parameters. For example, window . scrollBy ({ left : 50 , top : 0 }) is more readable than window . scrollBy ( 50 , 0 ) .

5.4. Make non-critical function parameters optional

Parameters that are not strictly necessary should be made optional. Making arguments optional with reasonable defaults reduces overhead when the function is used, and increases user-friendliness.

Optional parameters should be consolidated into a dictionary, and empty parameters should be treated identically to when the parameter is omitted.

For example, element . scrollIntoView ( false , {}); is equivalent to element . scrollIntoView ( false ); , and element . addEventListener ( "click" , onClick , undefined ); is equivalent to element . addEventListener ( "click" , onClick ) .

Exceptions have been made for legacy interoperability reasons (such as XMLHttpRequest), but this should be considered a design mistake rather than recommended practice.

5.5. Classes should have constructors when possible

By default, [WEBIDL] interfaces are reified in JavaScript as "non-constructible" classes: trying to create instances of them using the normal pattern in JavaScript, new X() , will throw a TypeError . This is a poor experience for JavaScript developers, and doesn’t match the design of most of the JavaScript standard library or of the classes JavaScript developers themselves create.

From a naive perspective, it’s not even coherent: how can instances of this thing ever exist, if they can’t be constructed? The answer is that the platform has magic powers: it can create instances of Web IDL-derived classes without ever actually calling their constructor.

You should strive to reduce such magic in designing your own APIs, and give them constructors which allow JavaScript developers to create them, just like the platform does. This means adding an appropriate constructor operation to your interface, and defining the algorithm for creating new instances of your class.

Apart from reducing the magic in the platform, adding constructors allows JavaScript developers to create instances of the class for their own purposes, such as testing, mocking, or interfacing with third-party libraries which accept instances of that class. Additionally, because of how JavaScript’s class design works, it is only possible to create a subclass of the class if it is constructible.

Sometimes this isn’t the right answer: for example, some objects essentially wrap handles to privileged resources, and it isn’t reasonable to create some representation of that privileged resource in JavaScript so that you can pass it to the constructor. Or the object’s lifecycle needs to be very carefully controlled, so that creating new ones is only possible through privileged operations. Or the interface represents an abstract base class for which it doesn’t make sense to construct an instance, or create an author-defined subclass. But cases like these should be the exception, not the norm.

The Event class, and all its derived interfaces, are constructible. This allows JavaScript developers to synthesize instances of them, which can be useful in testing or shimming scenarios. For example, a library designed for MouseEvent instances can still be used by another library that accepts as input TouchEvent instances, by writing a small bit of adapter code that creates synthetic MouseEvent s.

The DOMTokenList class is, very unfortunately, not constructible . This prevents the creation of custom elements that expose their token list attributes as DOMTokenList s.

No HTML elements are constructible. This is largely due to some unfortunate design decisions that would take some effort to work around . In the meantime, for the purposes of the customized built-in elements feature, they have been made subclassable through a specifically-designed one-off solution: the [ HTMLConstructor ] extended attribute.

The Window class is not constructible, because creating a new window is a privileged operation with significant side effects. Instead, the window.open() method is used to create new windows.

The ImageBitmap class is not constructible, as it represents an immutable, ready-to-paint bitmap image, and the process of getting it ready to paint must be done asynchronously. Instead, the createImageBitmap() method is used to create it.

Several non-constructible classes, like Navigator , History , or Crypto , are non-constructible because they are singletons representing access to per-window information. In these cases, the Web IDL namespace feature or some evolution of it might have been a better fit, but they were designed before namespaces existed, and somewhat exceed the current capabilities of namespaces. If you’re designing a new singleton, strongly consider using a namespace instead of a non-constructible class; if namespaces don’t yet have enough capabilities, file an issue on Web IDL to discuss.

For any JavaScript developers still incredulous that it is possible to create an instance of a non-constructible class, we provide the following code example:

const secret = {};
class NonConstructible {
    constructor(theSecret = undefined) {
        if (theSecret !== secret) {
            throw new TypeError("Illegal constructor.");
        }
        // Set up the object.
    }
}

If the author of this code never exposes the secret variable to the outside world, they can reserve the ability to create instances of NonConstructible for themself: they can always do const instance = new NonConstructible ( secret ) . But the outside world can never successfully "guess" the secret, and thus will always receive a TypeError when trying to construct an instance of NonConstructible .

From this perspective, what browsers are doing when they create non-constructible classes isn’t magic: it’s just very tricky. The above section is aimed at urging browsers to avoid such tricks (or their equivalents in C++/Rust/etc.) whenever reasonable.

5.6. Design asynchronous APIs using Promises

Asynchronous APIs should generally be designed using promises rather than callback functions. This is the pattern that we’ve settled on for the Web platform, and having APIs consistently use promises means that the APIs are easier to use together (such as by chaining promises). This pattern also tends to produce cleaner code than the use of APIs with callback functions.

Furthermore, you should carefully consider whether an API might need to be asynchronous before making it a synchronous API. An API might need to be asynchronous if:

For more information on how to design APIs using promises, and on when to use promises and when not to use promises, see Writing Promise-Using Specifications .

5.7. Cancel asynchronous APIs/operations using AbortSignal

Async functions that need cancellation should take an AbortSignal as part of an options dictionary.

Example:

const controller = new AbortController();
const signal = controller.signal;
geolocation.read({ signal });

Reusing the same primitive everywhere has multiplicative effects throughout the platform. In particular, there’s a common pattern of using a single AbortSignal for a bunch of ongoing operations, and then aborting them (with the corresponding AbortController ) when e.g. the user presses cancel, or a single-page-app navigation occurs, or similar. So the minor extra complexity for an individual API leads to a large reduction in complexity when used with multiple APIs together.

There might be cases where cancellation cannot be guaranteed. In these cases, the AbortController can still be used because a call to abort() on AbortController is a request to abort. How you react to it depends on your spec. Note, requestAbort() was considered in the AbortController design instead of abort(), but the latter was chosen for brevity.

5.8. Constants, enums, and bitmasks

In many other platforms and programming languages, constants and enums are commonly expressed using a integer constant, sometimes in conjunction with a bitmask mechanism.

However on the Web platform, it is more common to use a string constant for the cases where a constant is needed. This is much more inspection friendly for both development and expressing the constant codes through a user facing interface, and in JavaScript engines, using integers offers no significant performance benefit over strings.

Strings do not directly address the use case for bitmasks. For these cases, you should use an object dictionary which contains the state that the bitmask is attempting to express, as object dictionaries can then be passed around from method to method as needed as easily as the state in a single bitmask value.

6. Event Design

6.1. Use promises for one time events

Follow the advice in the Writing Promise-Using Specifications guideline.

6.2. Events should fire before Promises resolve

In the case that an asynchronous algorithm (Promise based) intends to dispatch events, then such events should be dispatched before the Promise resolves, rather than after.

This ensures consistency, for instance if you have event handlers changing state, you want all that state to be applied before acting on the resolved promise, as you can subscribe to a promise at any time (before/after it has resolved or rejected).

6.3. Don’t invent your own event listener-like infrastructure

For recurring events, it could be convenient to create a custom pair of APIs to "register" / "unregister" , "subscribe" / "unsubscribe" , etc., that take a callback and get invoked multiple times until paired cancel API is called.

Instead, use the existing event registration pattern and separate API controls to start/stop the underlying process (since event listeners should not have side-effects ).

If the callback would have been provided specific data, then this data should be added to an Event object (but see State and Event subclasses as this is not always necessary).

In some cases, you can transfer the state that would be surfaced in callback parameters into a more persistent object which, in turn, can inherit from EventTarget .

In some cases you might not have an object to inherit from EventTarget but it is usually possible to create such an object.

For instance with Web Bluetooth you can add event listeners on a Characteristic object, which is obtained via getCharacteristic() . If you need to filter events, it might be possible to create a filter like

const filter = navigator.nfc.createReadFilter({
  recordType: "json"
});
const onMessage = message => {};
filter.addEventListener('exchange', onMessage);

6.4. Always add event handler attributes

For an object that inherits from EventTarget , there are two techniques available for registering an event handler (e.g., an event named "somethingchanged"):

  1. addEventListener() which allows authors to register for the event using the event’s name (i.e., someobject . addEventListener ( "somethingchanged" , myhandler ) ) and

  2. onsomethingchanged IDL attributes which allow one event handler to be directly assigned to the object (i.e., someobject . onsomethingchanged ).

Because there are two techniques for registering events on objects inheriting from EventTarget , authors may be tempted to omit the corresponding event handler IDL attributes . They may assume that event handler IDL attributes are a legacy registration technique or are simply not needed given that addEventListener() is available as an alternative. However, it is important to continue to define event handler IDL attributes because:

So, if the object inherits from EventTarget , add a corresponding on yourevent event handler IDL attribute to the interface.

Note that for HTML and SVG elements, it is traditional to add the event handler IDL attributes on the GlobalEventHandlers interface mixin, instead of directly on the relevant element interface(s). Similarly, event handler IDL attributes are traditionally added to WindowEventHandlers rather than Window .

6.5. Events are for notification

Try to design DOM events to deliver after-the-fact notifications of changes. It may be tempting to try to trigger side-effects from the action of dispatchEvent() , but in general this is strongly discouraged as it requires changes to the DOM specification when added. Your design will proceed more quickly if you avoid this pattern.

6.6. Guard against potential recursion

When designing a long-running or complicated algorithm that is initiated by an API call, events are appropriate for notifying user code of the ongoing process. However, they also introduce the possibility of unexpected re-execution of the current algorithm before it has finished! Because user code gets to run in the middle of the algorithm, recursion can happen if that user code triggers the algorithm again (directly or indirectly).

To address this, if the algorithmic complexity and call graph is reasonably understood, add appropriate state to track that the algorithm is in progress and terminate immediately if the algorithm is already concurrently running. This technique is "guarding" the algorithm:

The following is a technique (e.g., as used in the AbortSignal’s add algorithm or as a stack-based variation in the focus update steps ) for guarding against unplanned recursion.

In this example, an object of type MyObject has a method doComplexOpWithEvents() and an internal state attribute [[ started ]] referenced below as this .[[ started ]] whose initial value is false .

The doComplexOpWithEvents() method must act as follows:

  1. If this .[[ started ]] is not false then throw an InvalidStateError DOMException and terminate this algorithm.

    Note: doComplexOpWithEvents() is not allowed to be run again (e.g., from within a "currentlyinprogress" event handler) before it finishes its original execution.

  2. Set this .[[ started ]] to true .

  3. ...Do complex stuff...

  4. Fire an event named "currentlyinprogress" at this .

  5. ...Finish up complex stuff...

  6. Set this .[[ started ]] to false .

Note: A caution about early termination: if the algorithm being terminated would go on to ensure some critical state consistency, be sure to also make the relevant adjustments in state before early termination of the algorithm. Not doing so can lead to inconsistent state and end-user-visible bugs when implemented as-specified.

Note: A caution about throwing exceptions in early termination: keep in mind the scenario in which developers will be invoking the algorithm, and whether they would reasonably expect to handle an exception in this [perhaps rare] case. For example, will this be the only exception in the algorithm?

Sometimes "guarding" as illustrated above cannot be done because of the complexity of the algorithm, the number of potential algorithm entry-points, or the side-effects beyond the algorithm’s control. In these cases another form of protecting the integrity of the algorithm’s state is to "defer" the event to a subsequent task or microtask. Deferral ensures that any stack-based recursion is avoided (but does not eliminate potentially problematic loops, as they could now occur as unending follow-up tasks).

Deferring an event is often specified as " queue a task to fire an event ...".

By way of illustration, the deprecated Mutation Events fired in the middle of node insertion and removal algorithms, and could be triggered by a variety of common API calls like appendChild() . The side-effects from recursively running node mutation algorithms caused many years of security issues. A more robust approach embodied in MutationObserver applies the principle of deferral of notification — in this case to the microtask queue following the algorithm.

Also note that deferral of events is always necessary if the algorithm that triggers the event could be running on a different thread or process. In this case deferral ensures the events can be processed on the correct task in the task queue .

Both approaches to protecting against unwanted recursion have trade-offs. Some things to consider when choosing the guarding approach:

When deferring, event handlers will run after the algorithm ends (or starts to run in parallel ) and any number of other tasks or microtasks may run in between that invalidate the object’s state. Since the object’s state will be unknown when the deferred event is dispatched, consider the following:

Finally, dispatching a deferred event that does not seem to require packaging extra state or defining a state machine for the algorithm as mentioned above, could mean that all of the state transitions have been completed and that the event is meant to signal completion of the algorithm. In this case, it’s likely that instead of using an event to signal completion, the API should be designed to return and complete a Promise instead. See § 6.1 Use promises for one time events .

Note: events that expose the possibility of recursion as described in this section were sometimes called "synchronous events". This terminology is discouraged as it implies that it is possible to dispatch an event asynchronously. All events are dispatched synchronously. What is more often implied by "asynchronous event" is to defer firing an event.

6.7. State and Event subclasses

It’s tempting to create subclasses of Event for all event types. This is frequently unnecessary. Consider subclassing Event when adding unique methods and large amounts of state. In all other cases, using a "vanilla" event with state captured in the target object.

6.8. How to decide between Events and Observers

Several recent additions to the platform employ an Observer pattern. MutationObserver , IntersectionObserver , ResizeObserver , and IndexedDB Observers provide precedents for new Observer types.

Many designs can be described as either Observers or EventTarget s. How to decide?

In general, start your design process using an EventTarget and only move to Observers if and when events can’t be made to work well. Using an EventTarget ensures your feature benefits from improvements to the shared base class, such as the recent addition of the once .

Observers have the following properties:

MutationObserver takes a callback which receives MutationRecord s. It cannot be customized at construction time, but each observation can be customized using the MutationObserverInit set of options. It observes Node s as targets.

IntersectionObserver takes a callback which receives IntersectionObserverEntry s. It can be customized at construction time using the IntersectionObserverInit set of options, but each observation is not further customizable. It observers Element s as targets.

Observers involve defining a new class, dictionaries for options, and a new type for the delivered records. For the cost, you gain a few advantages:

Observer s and EventTarget s overlap in the following ways:

Here is an example of using a hypothetical version of IntersectionObserver that is an EventTarget subclass:
const io = new ETIntersectionObserver(element, { root, rootMargin, threshold });
function listener(e) {
    for (const change of e.changes) {
        // ...
    }
}
io.addEventListener("intersect", listener);
io.removeEventListener("intersect", listener);

As you can see, we’ve lost some functionality compared to the Observer version: the ability to easily observe multiple elements with the same options, or the takeRecords() and disconnect() methods. We’re also forced to add the rather-redundant "intersect" event type to our subscription calls.

However, we haven’t lost the batching, timing, or creation-time customization, and the ETIntersectionObserver doesn’t participate in a hierarchy. These aspects can be achieved with either design.

7. Types and Units

7.1. Use numeric types appropriately

[WEBIDL] contains many numeric types. However, it is very rare that its more specific ones are actually appropriate.

JavaScript has only one numeric type, Number: IEEE 754 double-precision floating point, including ±0, ±Infinity, and NaN (although thankfully only one). The Web IDL "types" are coercion rules that apply when accepting an argument or triggering a setter. For example, a Web IDL unsigned short roughly says: "when someone passes this as an argument, take it modulo 65536 before doing any further processing". That is very rarely a useful thing to do.

Instead, you will want to stick with one of:

unrestricted double

When truly any JavaScript number will do, including infinities and NaN

double

Any JavaScript number excluding infinities and NaN

[EnforceRange] long long

Any JavaScript number in the integer-representable range, throwing a TypeError outside the range and rounding inside of it

[EnforceRange] unsigned long long

Any nonnegative JavaScript number in the integer-representable range, throwing a TypeError outside the range and rounding inside of it

bigint

When the possible integer values range more broadly than JavaScript numbers can accurately represent

Additionally, you can combine any of the above with an extra line in your algorithm to validate that the number is within the expected domain-specific range, and throwing or performing other actions in response. (While it is very rarely appropriate to modify author input by taking it modulo 65536, it might be appropriate to take it modulo 360, for example.)

A special case of domain-specific validation, which Web IDL already has you covered for, is the 0–255 range. This can be written as [EnforceRange] octet : any JavaScript number in the range 0–255, throwing a TypeError outside the range and rounding inside of it. (And indeed, if it turns out that the other power-of-two ranges are semantically meaningful for your domain, such that you want the modulo or range-checking behavior, feel free to use them.)

Those coming from other languages should carefully note that despite their names, long long and unsigned long long only have 53 bits of precision, and not 64.

bigint is recommended to be used only in circumstances where a [EnforceRange] long long or [EnforceRange] unsigned long long would not work in terms of expressivity: that is, only when values greater than 2 53 or less than -2 53 are expected. In particular, APIs should not use the bigint type simply because the input is an integer— bigint is for big integers (hence the name!).

Transparent support for inputs being either JavaScript Number of BigInt types is discouraged, unless there is a domain-specific requirement to support it. As far as ergonomics, JavaScript developers are expected to keep track of whether their values are Numbers or BigInts. Sloppiness or implicit conversions here could lead to a loss of precision, defeating the purpose of using BigInt.

Further, try to resist doubling up the surface area of an API in order to provide both BigInt and Number variants of an API. As an API designer, consider it your job to chose the correct numeric type for the feature being exposed.

7.2. Represent strings appropriately

When designing a web platform feature which operates on strings , DOMString is almost always the right Web IDL string type to use. This is because most string operations don’t need to interpret the code units inside of the string. There are exceptions, however, for which Web IDL provides USVString and ByteString . [INFRA] [WEBIDL]

USVString is the Web IDL type that represents scalar value strings . For strings whose most common algorithms operate on scalar values (such as percent-encoding ), or for operations which cannot handle surrogates in input (such as APIs that pass strings through to native platform APIs), USVString should be used.

Reflecting IDL attributes whose content attribute is defined to contain a URL (such as href ) should use USVString . [HTML]

ByteString should only be used for representing data from protocols like HTTP which don’t distinguish between bytes and strings. It is not a general-purpose string type. If you need to represent a sequence of bytes , use Uint8Array .

7.3. Use milliseconds for time measurement

Any web API that accepts a time measurement should do so in milliseconds. This is a tradition stemming from setTimeout and the Date API, and carried through since then.

Even if seconds (or some other unit) are more natural in the domain of an API, sticking with milliseconds ensures interoperability with the rest of the platform, allowing easy arithmetic with other time quantities.

Note that high-resolution time is usually represented as fractional milliseconds, not e.g. as nanoseconds.

7.4. Use the appropriate type to represent times and dates

When representing date-times on the platform, use the DOMTimeStamp type, with values being the number of milliseconds relative to 1970-01-01T00:00:00Z.

The JavaScript Date class must not be used for this purpose. Date objects are mutable (and there is no way to make them immutable), which comes with a host of attendant problems.

For more background on why Date must not be used, see the following:

While in theory time is monotonically increasing, values like DOMTimeStamp that represent time since 1970 are derived from the system’s clock, which may sometimes move backwards (for example, from NTP or manual adjustment), causing the timestamp values to decrease over time. They may also remain the same due to the limitation of millisecond resolution. Thus, for time stamps that do not need to correspond to an absolute time, consider using DOMHighResTimeStamp , which provides monotonically non-decreasing sub-millisecond timestamps that are comparable within a single browsing context or web worker. See [HIGHRES-TIME] for more details.

7.5. Use Error or DOMException for errors

Errors in web APIs should be represented as ECMAScript error objects (e.g., Error ) or as DOMException . There was at one point a trend to use DOMError when objects had a property representing an error. However, we no longer believe there was value in this split, and therefore suggest that ECMAScript error objects (e.g., TypeError ) or DOMException should be used for errors, whether they are exceptions, promise rejection values, or properties.

8. OS and Device Wrapper APIs

It is increasingly common to see new APIs developed in the web platform for interacting with devices. For example, authors wish to be able to use the web to connect with their microphones and cameras , generic sensors (such as gyroscope and accelerometer), Bluetooth and USB -connected peripherals, automobiles , toothbrush, etc.

These can be functionality provided by the underlying operating system, or provided by a native third-party library to interact with a device. These are an abstraction which "wrap" the native functionality without introducing significant complexity while securing the API surface to the browser, hence are called wrapper APIs.

This section contains principles for consideration when designing APIs for devices.

8.1. Use care when exposing identifying information about devices

Merely exposing the presence of a device may harm user privacy. Exposing additional information about a device or its capabilities increases this risk further. Exposing device identifiers increases this risk even more. Any such information increases the set of fingerprinting data available to sites. But there are possible user privacy harms here that go beyond fingerprinting. Please read [FINGERPRINTING-GUIDANCE] and [UNSANCTIONED-TRACKING] for additional details.

Think carefully about whether it is really necessary to expose identifying information about the device at all. Please consider if your use cases could be satisfied by a less powerful API. [LEAST-POWER]

Despite these concerns, you may find that it is necessary to expose identifying information about a device to the web platform.

The following guidelines will help ensure that this is done in a consistent and privacy-friendly way:

Limit identifiable information in the id

As much as possible, device ids exposed to the web platform should not contain identifiable information such as branding, make and model numbers, etc. In many cases using a randomized number or unique id is preferable to a generic string identifier such as "device1".

Device ids expressed as numbers should contain sufficient entropy so as to avoid re-use or potential sharing among other devices, and should not be easily guessable.

Keep the user in control

Any existing device ids mapped to or stored with the current session by the user agent should be cleared when users elect to "clear their cookies" (and other related settings). Above all, the user should be in control of this potential tracking state and be able to reset it on demand.

Hide sensitive ids behind a user permission

Where device identification does not make sense to be expressed in an anonymous way, access to the identifier should be limited by default. One way to limit exposure is to only surface the identifier to author code after obtaining permission from the user.

Tie ids to the same-origin model

Identifiers should be unique to the origin of the web content that is attempting to access them. Web content from one origin should receive an identifier that is distinct from the identifier given to web content from any other origin despite the physical device being the same.

Within an origin, ids may have a stable representation to web content. This ensures a consistent developer experience if requesting the same device twice.

Persistable when necessary

Some device identifiers can only be obtained via a complex or time-consuming device selection process. In such cases, it may make sense for author code to be able to persist such identifiers for use in a later session in order to avoid the selection process a second time. When this is the case, the API should not only provide a stable id during the session for the given origin, but it should also be capable of deterministically producing the same id in subsequent sessions.

8.2. Use care when exposing APIs for selecting or enumerating devices

APIs which expose the the existence, capabilities, and/or identifiers of many devices risk multiplying the harm described in § 8.1 Use care when exposing identifying information about devices (since they expose fingerprinting entropy per exposed device) and thus require additional consideration.

As in the previous section, please consider if your use cases could be satisfied by a less powerful API. [LEAST-POWER]

If the purpose of the API is to enable the user to select a device from the set of available devices of a particular kind, you may not need to expose a list to script at all. An API which invokes a User-Agent-provided device picker could suffice. Such an API keeps the user in control, hides the device behind a user permission, does not expose any fingerprinting data about the user’s environment by default, and, when used, only exposes information about one device.

File selection form controls ( <input type=file> ) invoke a User Agent-provided file picker. This elegant design ensures that the website only gets access to file(s) the user explicitly selected, and does not receive any other information about the user’s file system. [HTML]

When designing such an API, it may be necessary to also expose the fact that there are devices are available to be picked. Such a feature exposes a small amount (one bit) of fingerprinting data about the user’s environment to websites, so it isn’t quite as safe as an API which does not have such a feature.

The RemotePlayback interface does not expose a list of available remote playback devices . Like <input type=file> , it invokes a User Agent-defined device picker, which has the same desirable properties. [REMOTE-PLAYBACK]

Unlike <input type=file> , it also enables websites to detect whether or not any remote playback device is available, so the website can show or hide a control the user can use to invoke the picker.

If you must expose a list of devices, try to expose the smallest subset that satisfies your use cases. Consider an API which allows the website to request a filtered or constrained list of devices, but keep in mind that constraint-matching systems may reveal more information than intended when repeatedly invoked with different constraints.

If you must expose the full list of devices of a particular kind, please rigorously define the order in which devices will be listed. This can reduce interoperability issues, and helps to mitigate fingerprinting (because sort order could reveal other information; see Fingerprinting Guidance §5.2 Standardization for more.)

While APIs should not expose a full list of devices in an implementation-defined order, they may need to for web compatibility reasons.

8.3. Native APIs don’t typically translate well to the web

Many modern operating systems come with convenience APIs, which abstract away normally complex technology in the form of higher level user-friendly APIs.

Exposing these APIs to the web platform is getting more and more common, but you need to be careful when exposing these to the web platform.

Generalize interface when underlying API is overly specific

Don’t blindly map native convenience APIs 1:1 to the web platform. Native APIs do not translate well to the web platform, and doing so may result in a API shape which is difficult to implement across a variety of underlying platforms and native libraries.

Especially be careful about exposing the exact lifecycle and data structures of the underlying native APIs. Think about making an API that fits well in with existing web platform APIs and which can be expressed in commonly available low-level APIs.

Prefer asynchronous function by returning promises

Even if the underlying API is synchronous, this does not necessarily mean it translates well when ported to the web platform. Exposing a synchronous native API to the web platform is generally discouraged, and should be provided as a promise based API whenever possible.

Doing this also allows putting the API behind a permission if it is necessary, especially if the API depends on I/O or in the future may need to be isolated to a separate process.

Don’t overconstrain or overfit the API to the underlying (wrapped) OS

When designing a wrapper API, you should consider how different implementations (operating systems and platforms) provide this functionality. Ideally the entire specification is implementable across platforms, but in some cases it may be desirable to expose distinctions made on only some platforms.

These distinctions should be clearly noted if they are known in advance.

Do not propose black-box proprietary library or dedicated hardware processor dependencies

Black-box dependencies are strongly discouraged, as this not only prevents wide implementor adoption, but is unhealthy to the open nature of the web.

Underlying protocols should be open

APIs which require exchange with external hardware or services should not depend on closed protocols.

Be offline friendly

Web platform APIs should ideally not have a hard dependency on online services. The availability of these services across different regions may vary, users may be offline, and the online services have no guarantee of being always available.

Avoid additional fingerprinting surfaces

Wrapper APIs can expose unintentionally expose the user to a wider fingerprinting surface. Please read the TAG’s finding on unsanctioned tracking for additional details.

9. Other API Design Considerations

9.1. Privacy, Security, Accessibility, and Internationalization

It is important not to neglect other aspects of API design such as privacy, security, accessibility, and internationalization. Please take advantage of these other excellent resources in your design process:

9.2. Polyfills

Polyfills can be hugely beneficial in helping to roll out new features to the web platform. The Technical Architecture Group finding on Polyfills and the Evolution of the Web offers guidance that should be considered in the development of new features, notably:

9.3. Extend existing manifest files rather than creating new ones

New web features should be self-contained and self-describing and ideally should not require an additional manifest file. Manifest files can add complexity to the development and deployment process for web technologies. Sometimes manifest files are required, but the use of different manifests on the web should be kept to a minimum.

You may need to make a new manifest file if the domain of the manifest file is different from the existing manifest files. For example, if the fetch timing is different, or if the complexity of the manifest warrants it. Application metadata should be added to the Web App Manifest or be an extension of it. Manifests designated to be used for specific applications or which require interoperability with non-browsers may need to take a different approach. Payment Method Manifest, Publication Manifest, and Origin Policy are examples of these cases.

For example, if you have a single piece of metadata, even if the fetch timing is different than an existing manifest, it is probably best to use an existing manifest (or ideally design the feature in such a way that a manifest is not required). However, if your feature requires a complex set of metadata specific to a functional domain, the creation of a new manifest may be justified.

Note that in all cases, the naming conventions should be harmonized (see section 2.2)

Note: By principle, existing manifests use lowercase, underscore-delimited names. There have been times where it has been beneficial to re-use dictionaries from a manifest in DOM APIs as well, which are camel-cased. One such example is the image resource . For this reason, if a key can clearly be expressed as a single word, that is recommended.

When designing new keys and values for a manifest, make sure they are needed (that is, they enable well-thought-out use-cases). Also, please check if a similar key exists. If an existing key/value pair does more or less what is needed, work with the existing spec to extend it to your use-case if possible.

Some of the existing manifest files include

We encourage people to think of manifest files as extensible. Always try to get the changes into the original spec, or at least discuss the extension with the spec editors. Having this discussion is more likely to result in a better design and lead to something that better integrates with the platform.

There are certain times the spec authors might not want to integrate immediately due to process (like going to CR) or due to it having a different scope, like extensions to Web App Manifest only affecting store or payment use-cases. In that case, it is acceptable to monkey-patch as long as that is agreed with the original spec editors.

when we write up a principle on monkey patching, be sure to take this nuance into account. <https://github.com/w3ctag/design-principles/issues/184>

An example of something that was done as a monkey patch that is scheduled to be integrated into the web app manifest in a future level (post-CR):

10. Writing good specifications

This document mostly covers API design for the Web, but those who design APIs are hopefully also writing specifications for the APIs that they design.

10.1. Identify the audience of each requirement in your specification

Document both how authors should write good code using your API, and how implementers of your API should handle poorly-written code.

The web platform is, unusually, web, especially in comparison to other platforms, is designed to be robust in accepting poorly-formed markup. When writing specifications for the web, This means that web pages which use older versions of web standards can still be sure to note authoring requirements for well-formed markup (the "conforming language"), viewed in newer user agents, and also implementation requirements for handling that authors have a shallower learning curve.

To support this, web specification writers need to describe how to interpret poorly-formed markup (the "supported language"). markup, as well as well-formed markup.

This allows us Implementers need to have a relatively clean and understandable language be able to understand the "supported language", which is more complex than the "conforming language" which authors should be aiming to use.

For example, the Processing model for authors, while at the same time supporting existing documents that make use <table> element explains how to process the contents of older or nonstandard constructs, and enabling better interoperability in error handling. a <table> element, including cases where the contents do not conform to the Content model .

10.2. Specify completely and avoid ambiguity

If the specification leaves room for implementations to When specifying how a feature should work, make different choices which mean sure that there is enough information so that authors need don’t have to write different markup for code to work with different implementations, this adds a maintenance burden for authors. implementations.

The If a specification should be complete enough for implementations isn’t specific enough, implementers might make different choices which force authors to be interoperable, without depending on or referring write extra code to handle the differences.

Implementers shouldn’t need to check details of other implementations. When there isn’t a good reason implementations to allow variation between implementations, it is important that things avoid this situation. Instead, the specification should be specified unambiguously, so that implementations interoperate complete and web content doesn’t end up depending clear enough on the implementation-defined behavior in one implementation and failing in another (which can cause higher development costs to fix the content for all browsers, or costs to users of broken content if it wasn’t detected). its own.

Note: This way, it is easier to author content doesn’t mean that works in a variety of user agents. However, implementations can’t render things differently, or show different user interfaces for things like permission prompts.

Note: Implementers should still be free file bugs against specifications which don’t give them clear enough information to make improvements in areas such as user interface and quality of rendering. write the implementation.

10.2.1. Defining algorithms in specifications

Specification text Write algorithms in a way that is often more precise when it clear and concise.

The most common way to write algorithms is written using to write an explicit sequence of steps. Using an ordered sequence of steps makes it clear how the required behaviors interact. For example, if there are multiple error conditions present, a specification that describes the behavior using This often looks like pseudo-code.

The showModal() method is described as a numbered sequence of steps will make it clear which error will be reported, since the different errors would be checked clearly explains when to throw exceptions and reported when to run algorithms defined in different steps. However, sequences other parts of steps are not the only way to make a specification precise enough. HTML spec.

When there are particular characteristics writing a sequence of steps, imagine that specification editors want to ensure it is a piece of functional code.

Summarize the purpose of specifying behavior formally is more important when the algorithms would be more repetitive. These formal specification techniques algorithm before going into detail, so that readers can include dependencies on languages such as [WEBIDL] , on grammar productions, decide whether to read the steps or skip over them. For example take the development of new languages for defining pieces following steps, which ensure that there is at most one pending X callback per top-level browsing context.

A plain sequence of the specification. Merely having a formal syntax steps is not sufficient, though; the requirements that always the formal definitions place on implementations must still be clearly defined, which generally requires specifying some algorithms (or, sometimes, another layer of formal syntax) that best way to write an algorithm. For example, it might make sense to define what the formal syntax means, and then using the more-readable or re-use a formal syntax or grammar to avoid excessive repetition of the less-readable algorithms. There are also possibilities in-between, for example, if it is desirable repetition, or define specific states to ensure that an algorithm can be implemented with used in a state machine, it may be preferable to specify it in an algorithmic way that incorporates explicit states so that machine. When using extra constructs like these, the specification authors avoid accidentally creating more states than intended. earlier advice still applies.

When specifying in an algorithmic way, it is generally good to match the As much as possible, describe algorithms that implementations would use, even if this is somewhat less convenient for specification authors, as long closely as doing so possible to how they would not significantly increase be implmented. This may make the specification’s complexity. For example, it is better to specify CSS selector matching by moving right spec harder to left across combinators , since write, but it means that is what implementations do. This avoids don’t need to figure out how to translate what’s written in the risk of future problems caused by new features that would specification to how it should actually be slightly implemented. In particular, that may mean that different (in unimportant ways) between implementations based on one algorithm and implementations based on a make different one. Thus, it avoids small future divergence decisions that may lead to later features being feasible in behavior between specification one implementation but not another.

CSS selectors are read and implementations. It also helps understood from left to keep the costs of specifying features aligned with implementing them, since the complexity of adding a feature right, but in practice are matched from right to left in implementations. This allows the specification is more likely most specific term to be related to the cost matched or not matched quickly, avoiding unnecessary work. The CSS selector matching algorithm is written this way, instead of adding it to implementations. a hypothetical algorithm which would more closely match how CSS selectors are often read by CSS authors.

Algorithms in specifications should also reflect best practices in programming. For example, they should explicitly describe the inputs and outputs of the algorithm, rather than relying on "stack introspection" or handwaving. They should also avoid side-effects when possible. See also:

10.2.2. Use explicit flags for state

Describing Instead of describing state using with words, use explicit flags makes behavior clearer. for state when writing algorithms.

Using explicit flags makes it clear whether or not the state changes in different error conditions, and makes it clear when the state described by the flags is reset.

10.3. Other resources

Some useful advice on how to write specifications is available elsewhere:

Acknowledgments

This document consists of principles which have been collected by TAG members past and present during TAG design reviews . We are indebted to everyone who has requested a design review from us.

The TAG would like to thank Adrian Hope-Bailie, Alan Stearns, Aleksandar Totic, Alex Russell, Andreas Stöckel, Andrew Betts, Anne van Kesteren, Benjamin C. Wiley Sittler, Boris Zbarsky, Brian Kardell, Charles McCathieNevile, Chris Wilson, Dan Connolly, Daniel Ehrenberg, Daniel Murphy, Domenic Denicola, Eiji Kitamura, Eric Shepherd, Ethan Resnick, fantasai, François Daoust, Henri Sivonen, HE Shi-Jun, Ian Hickson, Irene Knapp, Jake Archibald, Jeffrey Yasskin, Jeremy Roman, Jirka Kosek, Kevin Marks, Lachlan Hunt, Léonie Watson, L. Le Meur, Lukasz Olejnik, Maciej Stachowiak, Marcos Cáceres, Mark Nottingham, Martin Thomson, Matt Giuca, Matt Wolenetz, Michael[tm] Smith, Mike West, Nick Doty, Nigel Megitt, Nik Thierry, Ojan Vafai, Olli Pettay, Pete Snyder, Philip Jägenstedt, Philip Taylor, Reilly Grant, Richard Ishida, Rick Byers, Ryan Sleevi, Sergey Konstantinov, Stefan Zager, Stephen Stewart, Steven Faulkner, Surma, Tab Atkins-Bittner, Tantek Çelik, Tobie Langel, Travis Leithead, and Yoav Weiss for their contributions to this & the HTML Design Principles document which preceded it.

Special thanks to Anne van Kesteren and Maciej Stachowiak, who edited the HTML Design Principles document.

If you contributed to this document but your name is not listed above, please let the editors know so they can correct this omission.

Conformance

Document conventions

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example" , like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note" , like this:

Note, this is an informative note.

(function() { "use strict"; var collapseSidebarText = ' ← ' + ' Collapse Sidebar '; var expandSidebarText = ' → ' + ' Pop Out Sidebar '; var tocJumpText = ' ↑

Conformant Algorithms ' + ' Jump to Table

Requirements phrased in the imperative as part of Contents '; var sidebarMedia = window.matchMedia('screen algorithms (such as "strip any leading space characters" or "return false and (min-width: 78em)'); var autoToggle = function(e){ toggleSidebar(e.matches) }; if(sidebarMedia.addListener) { sidebarMedia.addListener(autoToggle); } function toggleSidebar(on) { if (on == undefined) { on = !document.body.classList.contains('toc-sidebar'); } /* Don’t scroll abort these steps") are to compensate for be interpreted with the ToC if we’re above it already. */ var headY = 0; var head = document.querySelector('.head'); if (head) { // terrible approx of "top meaning of ToC" headY += head.offsetTop + head.offsetHeight; } var skipScroll = window.scrollY table.data, :not(.overlarge) > table.index'); var numTables = tables.length; for (var i = 0; i the key word ("must", "should", "may", etc) used in introducing the algorithm.

Conformance requirements phrased as algorithms or specific steps can be implemented in any manner, so long as the end result is equivalent. In particular, the algorithms defined in this specification are intended to be easy to understand and are not intended to be performant. Implementers are encouraged to optimize.

Index

Terms defined by this specification Terms defined by this specification

Terms defined by reference Terms defined by reference

References

Normative References Normative References [CSS-CASCADE-4] Elika Etemad; Tab Atkins Jr.. CSS Cascading and Inheritance Level 4 https://drafts.csswg.org/css-cascade/ [CSS-CONDITIONAL-3] CSS Conditional Rules Module Level 3 URL:

[CSS-CASCADE-5]
CSS Cascading and Inheritance Level 5 URL: https://drafts.csswg.org/css-cascade-5/
[CSS-CONDITIONAL-3]
CSS Conditional Rules Module Level 3 URL: https://drafts.csswg.org/css-conditional-3/ [CSSOM-1] Simon Pieters; Glenn Adams. CSS Object Model (CSSOM)
[CSSOM-1]
Simon Pieters; Glenn Adams. CSS Object Model (CSSOM) . URL: https://drafts.csswg.org/cssom/ [DOM] Anne van Kesteren. DOM Standard
[DOM]
Anne van Kesteren. DOM Standard . Living Standard. URL: https://dom.spec.whatwg.org/ [ECMASCRIPT]
[ECMASCRIPT]
ECMAScript Language Specification ECMAScript Language Specification . URL: https://tc39.es/ecma262/ [HIGHRES-TIME] Ilya Grigorik. High Resolution Time Level 2
[HIGHRES-TIME]
Ilya Grigorik. High Resolution Time Level 2 . URL: https://w3c.github.io/hr-time/ [HTML] Anne van Kesteren; et al. HTML Standard
[HTML]
Anne van Kesteren; et al. HTML Standard . Living Standard. URL: https://html.spec.whatwg.org/multipage/ [IndexedDB-2] Ali Alabbas; Joshua Bell. Indexed Database API 2.0
[IndexedDB-2]
Ali Alabbas; Joshua Bell. Indexed Database API 2.0 . URL: https://w3c.github.io/IndexedDB/ [INFRA] Anne van Kesteren; Domenic Denicola. Infra Standard
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard . Living Standard. URL: https://infra.spec.whatwg.org/ [MEDIAQUERIES-5] Dean Jackson; Florian Rivoal; Tab Atkins Jr.. Media Queries Level 5
[MEDIAQUERIES-5]
Dean Jackson; Florian Rivoal; Tab Atkins Jr.. Media Queries Level 5 . URL: https://drafts.csswg.org/mediaqueries-5/ [PAYMENT-REQUEST] Marcos Caceres; et al. Payment Request API
[PAYMENT-REQUEST]
Marcos Caceres; et al. Payment Request API . URL: https://w3c.github.io/payment-request/ [RFC2119] S. Bradner. Key words for use in RFCs to Indicate Requirement Levels
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels . March 1997. Best Current Practice. URL: https://tools.ietf.org/html/rfc2119 [SELECTORS-4] Elika Etemad; Tab Atkins Jr.. Selectors Level 4
[SELECTORS-4]
Elika Etemad; Tab Atkins Jr.. Selectors Level 4 . URL: https://drafts.csswg.org/selectors/ [URL] Anne van Kesteren. URL Standard
[URL]
Anne van Kesteren. URL Standard . Living Standard. URL: https://url.spec.whatwg.org/ [WEBIDL] Boris Zbarsky. Web IDL
[WEBIDL]
Boris Zbarsky. Web IDL . URL: https://heycam.github.io/webidl/ [XHR] Anne van Kesteren. XMLHttpRequest Standard
[XHR]
Anne van Kesteren. XMLHttpRequest Standard . Living Standard. URL: https://xhr.spec.whatwg.org/

Informative References Informative References [CSS-BACKGROUNDS-3] Bert Bos; Elika Etemad; Brad Kemper. CSS Backgrounds and Borders Module Level 3

[CSS-BACKGROUNDS-3]
Bert Bos; Elika Etemad; Brad Kemper. CSS Backgrounds and Borders Module Level 3 . URL: https://drafts.csswg.org/css-backgrounds/ [CSS-FONTS-3] John Daggett; Myles Maxfield; Chris Lilley. CSS Fonts Module Level 3 https://drafts.csswg.org/css-fonts/ [CSS-INLINE-3] Dave Cramer; Elika Etemad; Steve Zilles. CSS Inline Layout Module Level 3
[CSS-FONTS-3]
John Daggett; Myles Maxfield; Chris Lilley. CSS Fonts Module Level 3 . URL: https://drafts.csswg.org/css-fonts-3/
[CSS-INLINE-3]
Dave Cramer; Elika Etemad; Steve Zilles. CSS Inline Layout Module Level 3 . URL: https://drafts.csswg.org/css-inline-3/ [CSS-TEXT-DECOR-4] Elika Etemad; Koji Ishii. CSS Text Decoration Module Level 4
[CSS-TEXT-DECOR-4]
Elika Etemad; Koji Ishii. CSS Text Decoration Module Level 4 . URL: https://drafts.csswg.org/css-text-decor-4/ [CSS2] Bert Bos; et al. Cascading Style Sheets Level 2 Revision 1 (CSS 2.1) Specification
[CSS2]
Bert Bos; et al. Cascading Style Sheets Level 2 Revision 1 (CSS 2.1) Specification . URL: https://drafts.csswg.org/css2/ [FETCH] Anne van Kesteren. Fetch Standard
[FETCH]
Anne van Kesteren. Fetch Standard . Living Standard. URL: https://fetch.spec.whatwg.org/ [FINGERPRINTING-GUIDANCE] Nick Doty. Mitigating Browser Fingerprinting in Web Specifications
[FINGERPRINTING-GUIDANCE]
Nick Doty. Mitigating Browser Fingerprinting in Web Specifications . 28 March 2019. NOTE. URL: https://www.w3.org/TR/fingerprinting-guidance/ [LEAST-POWER] Tim Berners-Lee; Noah Mendelsohn. The Rule of Least Power
[LEAST-POWER]
Tim Berners-Lee; Noah Mendelsohn. The Rule of Least Power . 23 February 2006. TAG Finding. URL: https://www.w3.org/2001/tag/doc/leastPower [REMOTE-PLAYBACK] Mounir Lamouri; Anton Vayvod. Remote Playback API
[REMOTE-PLAYBACK]
Mounir Lamouri; Anton Vayvod. Remote Playback API . URL: https://w3c.github.io/remote-playback/ [SVG2] Amelia Bellamy-Royds; et al. Scalable Vector Graphics (SVG) 2 https://svgwg.org/svg2-draft/ [UIEVENTS] Gary Kacmarcik; Travis Leithead; Doug Schepers. UI Events
[UIEVENTS]
Gary Kacmarcik; Travis Leithead; Doug Schepers. UI Events . URL: https://w3c.github.io/uievents/ [UNSANCTIONED-TRACKING] Mark Nottingham. Unsanctioned Web Tracking
[UNSANCTIONED-TRACKING]
Mark Nottingham. Unsanctioned Web Tracking . 17 July 2015. TAG Finding. URL: http://www.w3.org/2001/tag/doc/unsanctioned-tracking/

Issues Index Issues Index

when we write up a principle on monkey patching, be sure to take this nuance into account. <https://github.com/w3ctag/design-principles/issues/184>