• Stars
    star
    515
  • Rank 82,973 (Top 2 %)
  • Language Bikeshed
  • License
    Other
  • Created about 1 year ago
  • Updated 14 days ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Observable API proposal

Observable

This is the explainer for the Observable API proposal for more ergonomic and composable event handling.

Introduction

EventTarget integration

This proposal adds an .on() method to EventTarget that becomes a better addEventListener(); specifically it returns a new Observable that adds a new event listener to the target when its subscribe() method is called. The Observable calls the subscriber's next() handler with each event.

Observables turn event handling, filtering, and termination, into an explicit, declarative flow that's easier to understand and compose than today's imperative version, which often requires nested calls to addEventListener() and hard-to-follow callback chains.

Example 1

// Filtering and mapping:
element.on('click')
  .filter(e => e.target.matches('.foo'))
  .map(e => ({x: e.clientX, y: e.clientY }))
  .subscribe({next: handleClickAtPoint});

Example 2

// Automatic, declarative unsubscription via the takeUntil method:
element.on('mousemove')
  .takeUntil(document.on('mouseup'))
  .subscribe({next: e => โ€ฆ });

// Since reduce and some other terminators return promises, they also play
// well with async functions:
await element.on('mousemove')
  .takeUntil(element.on('mouseup'))
  .reduce((soFar, e) => โ€ฆ);
Imperative version
// Imperative
const controller = new AbortController();
element.addEventListener('mousemove', e => {
  element.addEventListener('mouseup', e => controller.abort());
  console.log(e);
}, {signal});

Example 3

Tracking all link clicks within a container (example):

container.on('click').filter(e => e.target.closest('a')).subscribe({next: e => {
  // โ€ฆ
}});

Example 4

Find the maximum Y coordinate while the mouse is held down (example):

const maxY = await element.on('mousemove')
                          .takeUntil(element.on('mouseup'))
                          .map(e => e.clientY)
                          .reduce((soFar, y) => Math.max(soFar, y), 0);

Example 5

Multiplexing a WebSocket, such that a subscription message is send on connection, and an unsubscription message is send to the server when the user unsubscribes.

const socket = new WebSocket('wss://example.com');

function multiplex({ startMsg, stopMsg, match }) {
  if (socket.readyState !== WebSocket.OPEN) {
    return socket
      .on('open')
      .flatMap(() => multiplex({ startMsg, stopMsg, match }));
  } else {
    socket.send(JSON.stringify(startMsg));
    return socket
      .on('message')
      .filter(match)
      .takeUntil(socket.on('close'))
      .takeUntil(socket.on('error'))
      .map((e) => JSON.parse(e.data))
      .finally(() => {
        socket.send(JSON.stringify(stopMsg));
      });
  }
}

function streamStock(ticker) {
  return multiplex({
    startMsg: { ticker, type: 'sub' },
    stopMsg: { ticker, type: 'unsub' },
    match: (data) => data.ticker === ticker,
  });
}

const googTrades = streamStock('GOOG');
const nflxTrades = streamStock('NFLX');

const googController = new AbortController();
const googSubscription = googTrades.subscribe({next: updateView, signal: googController.signal});
const nflxSubscription = nflxTrades.subscribe({next: updateView, ...});

// And the stream can disconnect later, which
// automatically sends the unsubscription message
// to the server.
googController.abort();
Imperative version
// Imperative
function multiplex({ startMsg, stopMsg, match }) {
  const start = (callback) => {
    const teardowns = [];

    if (socket.readyState !== WebSocket.OPEN) {
      const openHandler = () => start({ startMsg, stopMsg, match })(callback);
      socket.addEventListener('open', openHandler);
      teardowns.push(() => {
        socket.removeEventListener('open', openHandler);
      });
    } else {
      socket.send(JSON.stringify(startMsg));
      const messageHandler = (e) => {
        const data = JSON.parse(e.data);
        if (match(data)) {
          callback(data);
        }
      };
      socket.addEventListener('message', messageHandler);
      teardowns.push(() => {
        socket.send(JSON.stringify(stopMsg));
        socket.removeEventListener('message', messageHandler);
      });
    }

    const finalize = () => {
      teardowns.forEach((t) => t());
    };

    socket.addEventListener('close', finalize);
    teardowns.push(() => socket.removeEventListener('close', finalize));
    socket.addEventListener('error', finalize);
    teardowns.push(() => socket.removeEventListener('error', finalize));

    return finalize;
  };

  return start;
}

function streamStock(ticker) {
  return multiplex({
    startMsg: { ticker, type: 'sub' },
    stopMsg: { ticker, type: 'unsub' },
    match: (data) => data.ticker === ticker,
  });
}

const googTrades = streamStock('GOOG');
const nflxTrades = streamStock('NFLX');

const unsubGoogTrades = googTrades(updateView);
const unsubNflxTrades = nflxTrades(updateView);

// And the stream can disconnect later, which
// automatically sends the unsubscription message
// to the server.
unsubGoogTrades();

Example 6

Here we're leveraging observables to match a secret code, which is a pattern of keys the user might hit while using an app:

const pattern = [
  'ArrowUp',
  'ArrowUp',
  'ArrowDown',
  'ArrowDown',
  'ArrowLeft',
  'ArrowRight',
  'ArrowLeft',
  'ArrowRight',
  'b',
  'a',
  'b',
  'a',
  'Enter',
];

const keys = document.on('keydown').map((e) => e.key);
keys
  .flatMap((firstKey) => {
    if (firstKey === pattern[0]) {
      return keys
        .take(pattern.length - 1)
        .every((k, i) => k === pattern[i + 1]);
    }
  })
  .filter(matched => matched)
  .subscribe({next: _ => {
    console.log('Secret code matched!');
  }});
Imperative version
const pattern = [...];

// Imperative
document.addEventListener('keydown', e => {
  const key = e.key;
  if (key === pattern[0]) {
    let i = 1;
    const handler = (e) => {
      const nextKey = e.key;
      if (nextKey !== pattern[i++]) {
        document.removeEventListener('keydown', handler)
      } else if (pattern.length === i) {
        console.log('Secret code matched!');
        document.removeEventListener('keydown', handler)
      }
    }
    document.addEventListener('keydown', handler)
  }
})

The Observable API

Observables are first-class objects representing composable, repeated events. They're like Promises but for multiple events, and specifically with EventTarget integration, they are to events what Promises are to callbacks. They can be:

  • Created by script or by platform APIs, and passed to anyone interested in consuming events via subscribe()
  • Fed to operators like Observable.map(), to be composed & transformed without a web of nested callbacks

Better yet, the transition from event handlers โžก๏ธ Observables is simpler than that of callbacks โžก๏ธ Promises, since Observables integrate nicely on top of EventTarget, the de facto way of subscribing to events from the platform and custom script. As a result, developers can use Observables without migrating tons of code on the platform, since it's an easy drop-in wherever you're handling events today.

The proposed API shape is as follows:

partial interface EventTarget {
  Observable on(DOMString type, optional AddEventListenerOptions options);
};

// `SubscribeCallback` is where the Observable "creator's" code lives. It's
// called when `subscribe()` is called, to set up a new subscription.
callback SubscribeCallback = undefined (Subscriber subscriber);
callback ObserverCallback = undefined (any value);

dictionary Observer {
  ObserverCallback next;
  VoidFunction complete;
  ObserverCallback error;

  AbortSignal signal;
};

dictionary PromiseOptions {
  AbortSignal signal;
};

[Exposed=*]
interface Subscriber {
  undefined next(any result);
  undefined complete();
  undefined error(any error);

  readonly attribute AbortSignal signal;
};

callback Predicate = boolean (any value);
callback Reducer = any (any accumulator, any currentValue)
callback Mapper = any (any element, unsigned long long index)
// Differs from `Mapper` only in return type, since this callback is exclusively
// used to visit each element in a sequence, not transform it.
callback Visitor = undefined (any element, unsigned long long index)

[Exposed=*]
interface Observable {
  constructor(SubscribeCallback callback);
  undefined subscribe(Observer observer);

  undefined finally(VoidFunction callback);

  // Observable-returning operators. See "Operators" section below.
  Observable takeUntil(Observable notifier);
  Observable map(Mapper mapper);
  Observable filter(Predicate predicate);
  Observable take(unsigned long long);
  Observable drop(unsigned long long);
  Observable flatMap(Mapper mapper);
  Promise<sequence<any>> toArray(optional PromiseOptions options);
  Promise<undefined> forEach(Visitor callback, optional PromiseOptions options);

  // Promise-returning. See "Concerns" section below.
  Promise<any> every(Predicate predicate, optional PromiseOptions options);
  // Maybe? Promise<any> first(optional PromiseOptions options);
  Promise<any> find(Predicate predicate, optional PromiseOptions options);
  Promise<any> some(Predicate predicate, optional PromiseOptions options);
  Promise<any> reduce(Reducer reducer, optional any initialValue, optional PromiseOptions options);
};

The creator of an Observable passes in a callback that gets invoked synchronously whenever subscribe() is called. The subscribe() method can be called any number of times, and the callback it invokes sets up a new "subscription" by registering the caller of subscribe() as a Observer. With this in place, the Observable can signal any number of events to the Observer via the next() callback, optionally followed by a single call to either complete() or error(), signaling that the stream of data is finished.

const observable = new Observable(subscriber => {
  let i = 0;
  setInterval(() => {
    if (i >= 10)
      subscriber.complete();
    else
      subscriber.next(i++);
  }, 2000);
});

observable.subscribe({
  // Print each value the Observable produces.
  next: console.log
});

Issue: See #3 about having the Observable constructor being able to register teardown upon unsubscription.

While custom Observables can be useful on their own, the primary use case they unlock is with event handling. Observables returned by the new EventTarget#on() method are created natively with an internal callback that uses the same underlying mechanism as addEventListener(). Therefore calling subscribe() essentially registers a new event listener whose events are exposed through the Observer handler functions and are composable with the various combinators available to all Observables.

Lazy, synchronous delivery

Crucially, Observables are "lazy" in that they do not start emitting data until they are subscribed to, nor do they queue any data before subscription. They can also start emitting data synchronously during subscription, unlike Promises which always queue microtasks when invoking .then() handlers. Consider this example:

el.on('click').subscribe({next: () => console.log('One')});
el.on('click').find(() => {โ€ฆ}).then(() => console.log('Three'));
el.click();
console.log('Two');
// Logs "One" "Two" "Three"

Firehose of synchronous data

By using AbortController, you can unsubscribe from an Observable even as it synchronously emits data during subscription:

// An observable that synchronously emits unlimited data during subscription.
let observable = new Observable(subscriber => {
  let i = 0;
  while (true) {
    subscriber.next(i++);
  }
});

let controller = new AbortController();
observable.subscribe({next: data => {
  if (data > 100)
    controller.abort();
}, signal: controller.signal});

Operators

We propose the following operators in addition to the Observable interface:

  • takeUntil(Observable)
    • Returns an observable that mirrors the one that this method is called on, until the input observable emits its first value
  • finally()
    • Like Promise.finally(), it takes a callback which gets fired after the observable completes in any way (complete()/error())

Versions of the above are often present in userland implementations of observables as they are useful for observable-specific reasons, but in addition to these we offer a set of common operators that follow existing platform precedent and can greatly increase utility and adoption. These exist on other iterables, and are derived from TC39's iterator helpers proposal which adds the following methods to Iterator.prototype:

  • map()
  • filter()
  • take()
  • drop()
  • flatMap()
  • reduce()
  • toArray()
  • forEach()
  • some()
  • every()
  • find()
  • maybe: from()

We expect userland libraries to provide more niche operators that integrate with the Observable API central to this proposal, potentially shipping natively if they get enough momentum to graduate to the platform. But for this initial proposal, we'd like to restrict the set of operators to those that follow the precedent stated above, similar to how web platform APIs that are declared Setlike and Maplike have native properties inspired by TC39's Map and Set objects. Therefore we'd consider most discussion of expanding this set as out-of-scope for the initial proposal, suitable for discussion in an appendix. Any long tail of operators could conceivably follow along if there is support for the native Observable API presented in this explainer.

Note that the operators every(), find(), some(), and reduce() return Promises whose scheduling differs from that of Observables, which sometimes means event handlers that call e.preventDefault() will run too late. See the Concerns section which goes into more detail.

Background & landscape

To illustrate how Observables fit into the current landscape of other reactive primitives, see the below table which is an attempt at combining two other tables that classify reactive primitives by their interaction with producers & consumers:

Singular Plural
Spatial Temporal Spatial Temporal
Push Value Promise Observable
Pull Function Async iterator Iterable Async iterator

History

Observables were first proposed to the platform in TC39 in May of 2015. The proposal failed to gain traction, in part due to some opposition that the API was suitable to be a language-level primitive. In an attempt to renew the proposal at a higher level of abstraction, a WHATWG DOM issue was filed in December of 2017. Despite ample developer demand, lots of discussion, and no strong objectors, the DOM Observables proposal sat mostly still for several years (with some flux in the API design) due to a lack of implementer prioritization.

Later in 2019, an attempt at reviving the proposal was made back at the original TC39 repository, which involved some API simplifications and added support for the synchronous "firehose" problem.

This repository is an attempt to again breathe life into the Observable proposal with the hope of shipping a version of it to the Web Platform.

Userland libraries

In prior discussion, Ben Lesh has listed several custom userland implementations of observable primitives, of which RxJS is the most popular with "47,000,000+ downloads per week."

  • RxJS: Started as a reference implementation of the TC39 proposal, is nearly identical to this proposal's observable.
  • Relay: A mostly identical contract with the addition of start and unsubscribe events for observation and acquiring the Subscription prior to the return.
  • tRPC: A nearly identical implemention of observable to this proposal.
  • XState: uses an observable interface in several places in their library, in particular for their Actor type, to allow subscriptions to changes in state, as shown in their useActor hook. Using an identical observable is also a documented part of access state machine changes when using XState with SolidJS.
  • SolidJS: An identical interface to this proposal is exposed for users to use.
  • Apollo GraphQL: Actually re-exporting from zen-observable as their own thing, giving some freedom to reimplement on their own or pivot to something like RxJS observable at some point.
  • zen-observable: A reference implementation of the TC39 observable proposal. Nearly identical to this proposal.
  • React Router: Uses a { subscribe(callback: (value: T) => void): () => void } pattern in their Router and DeferredData code. This was pointed out by maintainers as being inspired by Observable.
  • Preact Uses a { subscribe(callback: (value: T) => void): () => void } interface for their signals.
  • TanStack: Uses a subscribable interface that matches { subscribe(callback: (value: T) => void): () => void } in several places
  • Redux: Implements an observable that is nearly identical to this proposal's observable as a means of subscribing to changes to a store.
  • Svelte: Supports subscribing to observables that fit this exact contract, and also exports and uses a subscribable contract for stores like { subscribe(callback: (value: T) => void): () => void }.
  • Dexie.js: Has an observable implementation that is used for creating live queries to IndexedDB.
  • MobX: Uses similar interface to Observable internally for observation: { observe_(callback: (value: T)): () => void }.

UI Frameworks Supporting Observables

  • Svelte: Directly supports implicit subscription and unsubscription to observables simply by binding to them in templates.
  • Angular: Directly supports implicit subscription and unsubscription to observables using their | async "async pipe" functionality in templates.
  • Vue: maintains a dedicated library specifically for using Vue with RxJS observables.
  • Cycle.js: A UI framework built entirely around observables

Given the extensive prior art in this area, there exists a public "Observable Contract".

Additionally many JavaScript APIs been trying to adhere to the contract defined by the TC39 proposal from 2015. To that end, there is a library, symbol-observable, that ponyfills (polyfills) Symbol.observable to help with interoperability between observable types that adheres to exactly the interface defined here. symbol-observable has 479 dependent packages on npm, and is downloaded more than 13,000,000 times per week. This means that there are a minimum of 479 packages on npm that are using the observable contract in some way.

This is similar to how Promises/A+ specification that was developed before Promises were adopted into ES2015 as a first-class language primitive.

Concerns

One of the main concerns expressed in the original WHATWG DOM thread has to do with Promise-ifying APIs on Observable, such as the proposed first(). The potential footgun here with microtask scheduling and event integration. Specifically, the following innocent-looking code would not always work:

element.on('click').first().then(e => {
  e.preventDefault();
  // Do something custom...
});

If Observable#first() returns a Promise that resolves when the first event is fired on an EventTarget, then the user-supplied Promise .then() handler will run:

  • โœ… Synchronously after event firing, for events triggered by the user
  • โŒ Asynchronously after event firing, for all events triggered by script (i.e., element.click())
    • This means e.preventDefault() will have happened too late and effectively been ignored
To understand why this is the case, you must understand how and when the microtask queue is flushed (and thus how microtasks, including Promise resolution handlers, are invoked).

In WebIDL after a callback is invoked, the HTML algorithm clean up after running script is called, and this algorithm calls perform a microtask checkpoint if and only if the JavaScript stack is empty.

Concretely, that means for element.click() in the above example, the following steps occur:

  1. To run element.click(), a JavaScript execution context is first pushed onto the stack
  2. To run the internal click event listener callback (the one created natively by the Observable#from() implementation), another JavaScript execution context is pushed onto the stack, as WebIDL prepares to run the internal callback
  3. The internal callback runs, which immediately resolves the Promise returned by Observable#first(); now the microtask queue contains the Promise's user-supplied then() handler which will cancel the event once it runs
  4. The top-most execution context is removed from the stack, and the microtask queue cannot be flushed, because there is still JavaScript on the stack.
  5. After the internal click event callback is executed, the rest of the event path continues since event was not canceled during or immediately after the callback. The event does whatever it would normally do (submit the form, alert() the user, etc.)
  6. Finally, the JavaScript containing element.click() is finished, and the final execution context is popped from the stack and the microtask queue is flushed. The user-supplied .then() handler is run, which attempts to cancel the event too late

Two things mitigate this concern. First, there is a very simple workaround to always avoid the case where your e.preventDefault() might run too late:

element.on('click').map(e => (e.preventDefault(), e)).first()

...or if Observable had a .do() method (see whatwg/dom#544 (comment)):

element.on('click').do(e => e.preventDefault()).first()

...or by modifying the semantics of first() to take a callback that produces a value that the returned Promise resolves to:

el.on("submit").first(e => e.preventDefault()).then(doMoreStuff)

Second, this "quirk" already exists in today's thriving Observable ecosystem, and there are no serious concerns or reports from that community that developers are consistently running into this. This gives some confidence that baking this behavior into the web platform will not be dangerous.

Standards venue

There's been much discussion about which standards venue should ultimately host an Observables proposal. The venue is not inconsequential, as it effectively decides whether Observables becomes a language-level primitive like Promises, that ship in all JavaScript browser engines, or a web platform primitive with likely (but technically optional) consideration in other environments like Node.js (see AbortController for example).

Observables purposefully integrate frictionlessly with the main event-emitting interface (EventTarget) and cancellation primitive (AbortController) that live in the Web platform. As proposed here, observables join this existing strongly-connected component from the DOM Standard: Observables depend on AbortController/AbortSignal, which depend on EventTarget, and EventTarget depends on both Observables and AbortController/AbortSignal. Because we feel that Observables fits in best where its supporting primitives live, the WHATWG standards venue is probably the best place to advance this proposal. Additionally, non-Web ECMAScript embedders like Node.js and Deno would still be able to adopt Observables, and are even likely to, given their commitment to Web platform aborting and events.

This does not preclude future standardization of event-emitting and cancellation primitives in TC39 in the future, something Observables could theoretically be layered on top of later. But for now, we are motivated to make progress in WHATWG.

In attempt to avoid relitigating this discussion, we'd urge the reader to see the following discussion comments:

User needs

Observables are designed to make event handling more ergonomic and composable. As such, their impact on end users is indirect, largely coming in the form of users having to download less JavaScript to implement patterns that developers currently use third-party libraries for. As stated above in the explainer, there is a thriving userland Observables ecosystem which results in loads of excessive bytes being downloaded every day.

In an attempt to codify the strong userland precedent of the Observable API, this proposal would save dozens of custom implementations from being downloaded every day.

Additionally, as an API like EventTarget, AbortController, and one related to Promises, it enables developers to build less-complicated event handling flows by constructing them declaratively, which may enable them to build more sound user experiences on the Web.

Authors:

More Repositories

1

webcomponents

Web Components specifications
HTML
4,306
star
2

import-maps

How to control the behavior of JavaScript imports
JavaScript
2,636
star
3

virtual-scroller

1,997
star
4

focus-visible

Polyfill for `:focus-visible`
JavaScript
1,606
star
5

webusb

Connecting hardware to the web.
Bikeshed
1,287
star
6

webpackage

Web packaging format
Go
1,216
star
7

EventListenerOptions

An extension to the DOM event pattern to allow authors to disable support for preventDefault
JavaScript
1,166
star
8

portals

A proposal for enabling seamless navigations between sites or pages
HTML
945
star
9

floc

This proposal has been replaced by the Topics API.
Makefile
933
star
10

inert

Polyfill for the inert attribute and property.
JavaScript
914
star
11

scheduling-apis

APIs for scheduling and controlling prioritized tasks.
HTML
896
star
12

view-transitions

789
star
13

file-system-access

Expose the file system on the userโ€™s device, so Web apps can interoperate with the userโ€™s native applications.
Bikeshed
641
star
14

background-sync

A design and spec for ServiceWorker-based background synchronization
HTML
638
star
15

scroll-to-text-fragment

Proposal to allow specifying a text snippet in a URL fragment
HTML
577
star
16

ua-client-hints

Wouldn't it be nice if `User-Agent` was a (set of) client hints?
Bikeshed
575
star
17

aom

Accessibility Object Model
HTML
553
star
18

kv-storage

[On hold] A proposal for an async key/value storage API for the web
550
star
19

turtledove

TURTLEDOVE
Bikeshed
505
star
20

navigation-api

The new navigation API provides a new interface for navigations and session history, with a focus on single-page application navigations.
Makefile
474
star
21

webmonetization

Proposed Web Monetization standard
HTML
439
star
22

trust-token-api

Trust Token API
Bikeshed
412
star
23

attribution-reporting-api

Attribution Reporting API
Bikeshed
338
star
24

direct-sockets

Direct Sockets API for the web platform
HTML
304
star
25

shape-detection-api

Detection of shapes (faces, QR codes) in images
Bikeshed
299
star
26

display-locking

A repository for the Display Locking spec
HTML
294
star
27

background-fetch

API proposal for background downloading/uploading
Shell
279
star
28

resize-observer

This repository is no longer active. ResizeObserver has moved out of WICG into
HTML
256
star
29

first-party-sets

Bikeshed
255
star
30

serial

Serial ports API for the platform.
HTML
254
star
31

priority-hints

A browser API to enable developers signal the priorities of the resources they need to download.
Bikeshed
228
star
32

dbsc

HTML
227
star
33

is-input-pending

HTML
222
star
34

sanitizer-api

Bikeshed
213
star
35

proposals

A home for well-formed proposed incubations for the web platform. All proposals welcome.
209
star
36

spatial-navigation

Directional focus navigation with arrow keys
JavaScript
199
star
37

js-self-profiling

Proposal for a programmable JS profiling API for collecting JS profiles from real end-user environments
HTML
196
star
38

cq-usecases

Use cases and requirements for standardizing element queries.
HTML
185
star
39

interventions

A place for browsers and web developers to collaborate on user agent interventions.
178
star
40

visual-viewport

A proposal to add explicit APIs to the Web for querying and setting the visual viewport
HTML
174
star
41

frame-timing

Frame Timing API
HTML
170
star
42

layout-instability

A proposal for a Layout Instability specification
Makefile
157
star
43

page-lifecycle

Lifecycle API to support system initiated discarding and freezing
HTML
153
star
44

isolated-web-apps

Repository for explainers and other documents related to the Isolated Web Apps proposal.
Bikeshed
146
star
45

speech-api

Web Speech API
Bikeshed
144
star
46

cookie-store

Asynchronous access to cookies from JavaScript
Bikeshed
141
star
47

nav-speculation

Proposal to enable privacy-enhanced preloading
HTML
141
star
48

construct-stylesheets

API for constructing CSS stylesheet objects
Bikeshed
137
star
49

webhid

Web API for accessing Human Interface Devices (HID)
HTML
135
star
50

color-api

A proposal and draft spec for a Color object for the Web Platform, loosely influenced by the Color.js work. Heavily WIP, if you landed here randomly, please move along.
HTML
124
star
51

devtools-protocol

DevTools Protocol
JavaScript
120
star
52

fenced-frame

Proposal for a strong boundary between a page and its embedded content
Bikeshed
118
star
53

sms-one-time-codes

A way to format SMS messages for use with browser autofill features such as HTMLโ€™s autocomplete=one-time-code.
Makefile
109
star
54

bundle-preloading

Bundles of multiple resources, to improve loading JS and the Web.
HTML
103
star
55

netinfo

HTML
95
star
56

intrinsicsize-attribute

Proposal to add an intrinsicsize attribute to media elements
94
star
57

window-controls-overlay

HTML
94
star
58

container-queries

HTML
92
star
59

animation-worklet

๐Ÿšซ Old repository for AnimationWorklet specification โžก๏ธ New repository: https://github.com/w3c/css-houdini-drafts
Makefile
92
star
60

manifest-incubations

Before install prompt API for installing web applications
HTML
90
star
61

async-append

A way to create DOM and add it to the document without blocking the main thread.
HTML
87
star
62

privacy-preserving-ads

Privacy-Preserving Ads
86
star
63

indexed-db-observers

Prototyping and discussion around indexeddb observers.
WebIDL
83
star
64

shared-storage

Explainer for proposed web platform Shared Storage API
Bikeshed
82
star
65

compression

Standard text for CompressionStream and DecompressionStream API
HTML
81
star
66

file-handling

API for web applications to handle files
81
star
67

compression-dictionary-transport

80
star
68

canvas-color-space

Proposed web platform feature to add color management, wide gamut and high bit-depth support to the <canvas> element.
78
star
69

canvas-formatted-text

HTML
77
star
70

local-font-access

Web API for enumerating fonts on the local system
Bikeshed
75
star
71

performance-measure-memory

performance.measureMemory API
HTML
73
star
72

starter-kit

A simple starter kit for incubations
JavaScript
72
star
73

handwriting-recognition

Handwriting Recognition Web API Proposal
Makefile
72
star
74

css-parser-api

This is the repo where the CSS Houdini parser API will be worked on
HTML
72
star
75

ContentPerformancePolicy

A set of policies that a site guarantees to adhere to, browsers enforce, and embedders can count on.
HTML
72
star
76

web-app-launch

Web App Launch Handler
HTML
72
star
77

pwa-url-handler

71
star
78

eyedropper-api

HTML
70
star
79

idle-detection

A proposal for an idle detection and notification API for the web
Bikeshed
67
star
80

close-watcher

A web API proposal for watching for close requests (e.g. Esc, Android back button, ...)
Makefile
67
star
81

storage-foundation-api-explainer

Explainer showcasing a new web storage API, NativeIO
65
star
82

video-editing

64
star
83

uuid

UUID V4
63
star
84

client-hints-infrastructure

Specification for the Client Hints infrastructure - privacy preserving proactive content negotiation
Bikeshed
61
star
85

sparrow

59
star
86

element-timing

A proposal for an Element Timing specification.
Bikeshed
59
star
87

local-peer-to-peer

โ†”๏ธ Proposal for local communication between browsers without the aid of a server.
Bikeshed
53
star
88

digital-credentials

Digital Credentials, like driver's licenses
HTML
53
star
89

video-rvfc

video.requestVideoFrameCallback() incubation
HTML
53
star
90

time-to-interactive

Repository for hosting TTI specification and discussions around it.
52
star
91

digital-goods

Makefile
49
star
92

private-network-access

HTML
49
star
93

raw-clipboard-access

An explainer for the Raw Clipboard Access feature
45
star
94

document-picture-in-picture

Bikeshed
45
star
95

admin

๐Ÿ‘‹ Ask your questions here! ๐Ÿ‘‹
HTML
42
star
96

soft-navigations

Heuristics to detect Single Page Apps soft navigations
Bikeshed
42
star
97

pending-beacon

A better beaconing API
Bikeshed
40
star
98

webcrypto-secure-curves

Proposal for the addition of Curve25519 and Curve448 to the Web Cryptography API
HTML
40
star
99

entries-api

Spec defining browser support for file/directory upload by drag-and-drop
Bikeshed
40
star
100

transfer-size

38
star