├── .github └── workflows │ └── deploy.yml ├── .gitignore ├── .pr-preview.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── asynclocalstorage.md ├── index.bs └── w3c.json /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | on: [push] 3 | 4 | jobs: 5 | deploy: 6 | name: Deploy 7 | runs-on: ubuntu-latest 8 | permissions: 9 | id-token: write 10 | contents: read 11 | 12 | steps: 13 | - name: Clone repository 14 | uses: actions/checkout@v2 15 | 16 | - name: Build spec 17 | run: | 18 | mkdir -p out 19 | curl --retry 2 --fail https://api.csswg.org/bikeshed/ --output out/index.html --header "Accept: text/plain, text/html" -F die-on=fatal -F file=@"index.bs" 20 | - name: Upload to Deno Deploy 21 | uses: denoland/deployctl@v1 22 | with: 23 | project: "proposal-min-common-api" 24 | entrypoint: "https://deno.land/std@0.134.0/http/file_server.ts" 25 | root: out/ 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | index.html 2 | -------------------------------------------------------------------------------- /.pr-preview.json: -------------------------------------------------------------------------------- 1 | { 2 | "src_file": "index.bs", 3 | "type": "bikeshed" 4 | } 5 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | All documentation, code, and communication under this repository are covered by 4 | the 5 | [W3C Code of Ethics and Professional Conduct](https://www.w3.org/Consortium/cepc/). 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Minimum Common API proposal 2 | 3 | ## Building the spec 4 | 5 | To build the spec locally first install bikeshed: 6 | 7 | ```sh 8 | pip3 install bikeshed && bikeshed update 9 | ``` 10 | 11 | Then to build the spec (index.bs) into HTML (index.html), run one of the below 12 | commands: 13 | 14 | ```sh 15 | bikeshed spec # build once 16 | 17 | # or 18 | 19 | bikeshed watch # rebuild on changes 20 | ``` 21 | 22 | ## IPR policy 23 | 24 | This repository is being used for work in the W3C Web-Interoperable Runtimes 25 | Community Group, governed by the 26 | [W3C Community License Agreement (CLA)](http://www.w3.org/community/about/agreements/cla/). 27 | To make substantive contributions, you must join the CG. 28 | 29 | If you are not the sole contributor to a contribution (pull request), please 30 | identify all contributors in the pull request comment. 31 | 32 | To add a contributor (other than yourself, that's automatic), mark them one per 33 | line as follows: 34 | 35 | ``` 36 | +@github_username 37 | ``` 38 | 39 | If you added a contributor by mistake, you can remove them in a comment with: 40 | 41 | ``` 42 | -@github_username 43 | ``` 44 | 45 | If you are making a pull request on behalf of someone else but you had no part 46 | in designing the feature, you can remove yourself with the above syntax. 47 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | All Reports in this Repository are licensed by Contributors under the 2 | [W3C Software and Document License](http://www.w3.org/Consortium/Legal/2015/copyright-software-and-document). 3 | 4 | Contributions to Specifications are made under the 5 | [W3C CLA](https://www.w3.org/community/about/agreements/cla/). 6 | 7 | Contributions to Test Suites are made under the 8 | [W3C 3-clause BSD License](https://www.w3.org/Consortium/Legal/2008/03-bsd-license.html) 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Minimum Common API 2 | 3 | The idea behind the Minimum Common API is to document the minimal collection of Web Platform APIs that ought to be implemented by Web-interoperable runtimes. 4 | 5 | ## How are the APIs selected 6 | 7 | By looking at the APIs that are already implemented and supported in common across Node.js, Deno, and Cloudflare Workers. If there were at least two implementations among those -- either already supported or in progress -- then it was added to the list. 8 | 9 | ## Is the list finalized? 10 | 11 | Oh heavens no. The initial draft is just an initial draft. A first stab at a collection we need to work through and finalize. 12 | 13 | ## How do we decide what makes the final list? 14 | 15 | I don't know yet. That's something we still need to work through. 16 | -------------------------------------------------------------------------------- /asynclocalstorage.md: -------------------------------------------------------------------------------- 1 | # AsyncLocalStorage - Web-interoperable Runtimes portable subset 2 | 3 | This document describes a portable subset of the Node.js `AsyncLocalStorage` API 4 | that can be implemented by non-Node.js runtimes without dependency on the 5 | lower-level `async_hooks` API. 6 | 7 | The key purpose this exists is because multiple runtimes have the requirement 8 | of implementing async context tracking and want to do so in a portable, 9 | interoperable way. There is work currently underway in TC-39 to define a standard 10 | `AsyncContext` mechanism [as part of the language](https://github.com/legendecas/proposal-async-context) 11 | but completion of that work is expected to take some time. 12 | 13 | The portable subset of `AsyncLocalStorage` defined here is offered as a 14 | transitional step in advance of `AsyncContext`. This subset is intentionally 15 | limited to a model and API that aligns closely with `AsyncContext`. Runtimes 16 | implementing async context tracking are strongly encouraged to implement only 17 | this subset to best remain forwards compatible with `AsyncContext`. 18 | 19 | ## Logical model 20 | 21 | This subset uses a fundamentally different underlying logical model than what 22 | is *currently* implemented in Node.js' `AsyncLocalStorage` implementation based 23 | on `async_hooks`. I won't go into the details of Node.js' current model here, 24 | however. Instead, I'll focus on the logical model assumed by this subset. 25 | 26 | At any given time, there is a current `Async Context Frame` (which, for ease 27 | of referencing I'll just call `frame` from here on out). 28 | 29 | The `frame` is effectively an *immutable* map of `storage keys` to `value`. 30 | We refer to this as the `frame`'s `storage context`. A `storage key` 31 | is associated with exactly one `AsyncLocalStorage` object instance. 32 | 33 | There is always a `root frame` whose `storage context` is always empty. 34 | 35 | Whenever a new value is associated with a given `storage key`, a new 36 | `frame` is created with the `value` associated with that `storage key` 37 | set in the new `frame`'s `storage context`. 38 | 39 | Whenever a new `frame` is created, it inherits a *copy* of the current 40 | `frame`'s `storage context` before setting the new `value` associated 41 | with the `storage key`. 42 | 43 | In **pseudo-code**, this looks something like: 44 | 45 | **Note**: this is **NOT** the API, 46 | this is a pseudo-code illustration of the abstract model. 47 | 48 | ```js 49 | class AsyncLocalStorage { 50 | #storageKey = {}; // the storage key is opaque and unique to this 51 | // ALS instance. 52 | run(store, fn, ...args) { 53 | return AsyncContextFrame.run(this.#storageKey, store, fn, ...args); 54 | } 55 | 56 | getStore() { 57 | return AsyncContextFrame.current().get(this.#storageKey); 58 | } 59 | } 60 | 61 | class AsyncContextFrame { 62 | static #currentFrame; 63 | #storageContext = new Map(); 64 | 65 | static run(key, store, fn) { 66 | const current = AsyncContextFrame.current(); 67 | const frame = new AsyncContextFrame(current, key, store); 68 | return frame.run(fn); 69 | } 70 | 71 | static current() { return AsyncContextFrame.#currentFrame; } 72 | static exchange(frame) { 73 | const prior = AsyncContextFrame.#currentFrame; 74 | AsyncContextFrame.#currentFrame = frame; 75 | return prior; 76 | } 77 | 78 | constructor(current, key, store) { 79 | this.#storageContext = new Map(current.#storageContext.entries()); 80 | this.#storageContext.set(key, store); 81 | } 82 | 83 | run(fn, ...args) { 84 | // enter this frame... 85 | const prior = AsyncContextFrame.exchange(this); 86 | try { 87 | return fn(...args); 88 | } finally { 89 | AsyncContextFrame.exchange(prior); 90 | } 91 | } 92 | 93 | get(key) { 94 | return this.#storageContext.get(key); 95 | } 96 | } 97 | ``` 98 | 99 | Any async resource that is created needs only to grab the current `frame` 100 | and run code within its scope to access the correct `storage context`. 101 | 102 | Note that the key to this model is that the `storage context` is *immutable* 103 | once created. Mutations to the context using `asyncLocalStorage.run()` 104 | use copy-on-write semantics, causing a new `frame` to be created and 105 | entered. This is a key performance optimization over the current model 106 | implemented by Node.js. 107 | 108 | The common theme is that the async context should be captured at the moment 109 | an asynchronous task is *scheduled*. 110 | 111 | * `promise.then(fn)` *schedules* a continuation task 112 | * `setTimeout(fn, n)` *schedules* a timer task 113 | * `queueMicrotask(fn)` *schedules* a microtask 114 | * and so on 115 | 116 | This is the general rule that runtimes and applications should follow for 117 | any API that schedules asynchronous tasks to run. 118 | 119 | ## The API 120 | 121 | ### Class: AsyncLocalStorage 122 | 123 | The following is the limited subset of the `AsyncLocalStorage` API: 124 | 125 | ```js 126 | class AsyncLocalStorage { 127 | constructor(); 128 | 129 | // Creates a new frame, propagating the storage context of 130 | // the current frame to the new frame, then setting the store 131 | // as the value associated with this ALS instance. The function 132 | // is then run within the context of the newly created frame. 133 | // The return value of calling fn() is returned by run(). 134 | run(store, fn, ...args) : any; 135 | 136 | // In this subset, exit() is equivalent to calling `run(undefined, fn)` 137 | exit(fn, ...args) : any; 138 | 139 | // Returns the value of the store associated with this ALS instance 140 | // in the current frame's storage context. 141 | getStore() : any; 142 | } 143 | ``` 144 | 145 | ### Class: AsyncResource 146 | 147 | The following is the limited subset of the `AsyncResource` API: 148 | 149 | ```js 150 | class AsyncResource { 151 | // The type and options here are defined by Node.js, with the type 152 | // *currently* being required by Node.js. In this subset, the 153 | // type is still required but is *ignored*. It is required so 154 | // that code written to this subset can be portable to Node.js. 155 | // The InitOptions current is used to specify async_hooks 156 | // specific metadata that is not used by this subset. It may 157 | // be passed but should be ignored by implementations of this 158 | // subset. 159 | // On construction, the AsyncResource captures a reference to 160 | // the current async context frame. 161 | constructor(type: string, options? : InitOptions); 162 | 163 | // Synchronously calls fn in the scope of the frame captured 164 | // when the AsyncResource instance was created. 165 | runInAsyncScope(fn, thisArg?: any, ...args) : any; 166 | 167 | // Returns a new function that wraps the given fn such that when 168 | // the returned function is called, fn is called in the scope of 169 | // the frame captured when the AsyncResource instance was created. 170 | bind(fn, thisArg?: any, ...args) : Function; 171 | 172 | // Creates a new AsyncResource and returns the `resource.bind(fn)` 173 | // The type argument here is from the Node.js implementation and 174 | // is passed as the `type` argument to the `AsyncResource` 175 | // constructor. Implementations of this subset must be capable 176 | // of accepting the argument but should ignore it. 177 | static bind(fn, type?: string, thisArg?: any) : Function; 178 | } 179 | ``` 180 | 181 | ### Importing `AsyncLocalStorage` and `AsyncResource` 182 | 183 | The classes should be importable/requirable using, at least, the `node:async_hooks` 184 | specifier and may be accessed using the bare `async_hooks` specifier. For 185 | example: 186 | 187 | ``` 188 | import { AsyncLocalStorage, AsyncResource } from 'node:async_hooks' 189 | 190 | // or 191 | 192 | const { AsyncLocalStorage, AsyncResource } = require('node:async_hooks'); 193 | ``` 194 | 195 | Neither the `AsyncLocalStorage` and `AsyncResource` constructors should 196 | be accessible via the global scope (`globalThis`). 197 | 198 | ### Differences from Node.js API 199 | 200 | The Node.js `AsyncLocalStorage` includes additional APIs that assume 201 | the async context `frame` is *mutable* in place. For instance, the 202 | Node.js `asyncLocalStorage.enterWith(...)` API modifies the `value` 203 | associated with `storage key` *in-place*, without creating and entering 204 | a new frame. Such a change is not scoped to just the current sync 205 | execution and can carry several side effects. 206 | 207 | The `AsyncLocalStorage` subset defined in this document treats the 208 | async context frame as *immutable* once created and therefore does 209 | not implement the `enterWith()` or `disable()` APIs. 210 | 211 | The Node.js `AsyncResource` includes options and methods that are 212 | specific to Node.js' `async_hooks` resource *tracking* mechanisms. 213 | These aren't relevant to async context tracking and therefore are 214 | not included in this subset. 215 | 216 | ## Capturing the async context 217 | 218 | In general terms, an "async resource" is any task that is expected 219 | to capture the current async context frame and run within its scope 220 | at some point in the future. There are some async resources built into 221 | JavaScript (e.g. promises, thenables, and microtasks), some defined 222 | by Web Platform APIs (e.g. timers, background tasks, unhandled rejections), 223 | some defined by the runtime, and others defined by the application. 224 | A common characteristic of all async resources is that they are expected 225 | to capture the current async context frame and propagate it at various 226 | specific times. 227 | 228 | ### Promises 229 | 230 | A promise itself is *not* an async resource. A promise continuation task, 231 | however, *is*. In other words, when we use `then()` to schedule a continuation 232 | task for a promise, the current async context is captured and associated with 233 | that continuation task. 234 | 235 | Note that this is a variance from Node.js' current implementation which treats 236 | the promise *itself* as the async resource, capturing the context at the moment 237 | the promise is created rather than when the continuation task is created. 238 | 239 | ***This is an area that will likely continue to evolve and is currently being 240 | actively discussed. We *could* end up with a model similar to Node.js' in the 241 | end but there's still a lot of uncertainty here*** 242 | 243 | #### Thenables 244 | 245 | Thenables (promise-like objects that expose a `then()` method) should be handled 246 | in exactly the same way as a promise. 247 | 248 | Thenables do, however, have the option of being defined as extending 249 | `AsyncResource`, overriding the default behavior. 250 | 251 | *** As with Promises in general, this is an area that is still up for discussion 252 | and consideration.*** 253 | 254 | ### queueMicrotask 255 | 256 | `queueMicrotask()` must capture the async context frame that is current when 257 | `queueMicrotask()` is called and run the callback within the context of that 258 | frame. 259 | 260 | ### Timers 261 | 262 | Timers (using `setTimeout()`, `setInterval()`) must capture the async context 263 | frame that is current when the timer is created and must run the callback 264 | function within the context of that frame. 265 | 266 | ### General guidelines for runtimes and applications 267 | 268 | As described previously, async context should be captured at the moment 269 | an asynchronous task is *scheduled*. This is the general rule that runtimes 270 | and applications should follow for any API that schedules asynchronous tasks 271 | to run. 272 | 273 | For example, imagine an API that processes a stream of data by calling 274 | a set of configured callbacks. The context that is propagated to each of 275 | the callbacks should be the context that is current when the processing 276 | *started*. 277 | 278 | ```js 279 | const als = new AsyncLocalStorage(); 280 | 281 | const processor = new Processor({ 282 | onStart() { 283 | console.log(als.getStore()); // 123 284 | }, 285 | onEnd() { 286 | console.log(als.getStore()); // 123 287 | }, 288 | }); 289 | 290 | als.run(123, () => processor.start(data)); 291 | ``` 292 | 293 | Here, the call to `processor.start(data)` actually schedules the async 294 | activity, so that is the context that is propagated by default. If, 295 | alternatively, the intent is for the callbacks to run within the 296 | context that is current when the `Processor` instance is created, 297 | `AsyncResource.bind()` should be used, if the `Processor` can extend 298 | `AsyncResource` and use `this.runInAsyncScope()`. 299 | 300 | ```js 301 | const als = new AsyncLocalStorage(); 302 | 303 | const processor = new Processor({ 304 | onStart: AsyncResource.bind(() { 305 | console.log(als.getStore()); // undefined 306 | }), 307 | onEnd: AsyncResource.bind(() { 308 | console.log(als.getStore()); // undefined 309 | }), 310 | }); 311 | 312 | als.run(123, () => processor.start(data)); 313 | ``` 314 | 315 | `EventTarget` and `EventEmitter` are slightly different cases. Events 316 | are *technically not* asynchronous tasks. Both EventTarget and 317 | EventEmitter dispatch events fully synchronously so they will always 318 | run in the same context frame as the dispatchEvent/emit call. 319 | 320 | ```js 321 | const als = new AsyncLocalStorage(); 322 | const et = new EventTarget(); 323 | 324 | als.run(123, () => { 325 | et.addEventListener('foo', () => { 326 | console.log(als.getStore()); // 321 327 | }); 328 | }); 329 | 330 | als.run(321, () => { 331 | et.dispatchEvent(new Event('foo')); 332 | }); 333 | ``` 334 | 335 | To force an event listener to use the current context frame 336 | when the listener is added, use `AsyncResource.bind()`: 337 | 338 | ```js 339 | const als = new AsyncLocalStorage(); 340 | const et = new EventTarget(); 341 | 342 | als.run(123, () => { 343 | et.addEventListener('foo', AsyncResource.bind(() => { 344 | console.log(als.getStore()); // 123 345 | })); 346 | }); 347 | 348 | als.run(321, () => { 349 | et.dispatchEvent(new Event('foo')); 350 | }); 351 | ``` 352 | 353 | Alternatively, a runtime can provide custom implementations of EventEmitter 354 | or EventTarget that capture and propagate async context in other ways 355 | (for instance, Node.js' `EventEmitterAsyncResource` API). 356 | 357 | ### Unhandled rejections and the deferred promise pattern 358 | 359 | The `unhandledrejection` and `rejectionhandled` events should, by default, 360 | propagate the context that was current when those events are scheduled and 361 | *not* when the promise was created. 362 | 363 | The deferred promise pattern is an approach to using promises that separates 364 | the `resolve()` and `reject()` functions used to settle the promise from the 365 | actual promise itself. 366 | 367 | Consider the following example carefully: 368 | 369 | ```js 370 | function deferredPromise() { 371 | let resolve, reject; 372 | const promise = new Promise((a,b) => { 373 | resolve = a; 374 | reject = b; 375 | }); 376 | return { promise, resolve, reject } 377 | } 378 | 379 | const als = new AsyncLocalStorage(); 380 | 381 | addEventListener('unhandledrejection', (event) => { 382 | console.log(als.getStore()); // 123 383 | als.run('abc', () => { 384 | event.promise.catch(() => {}); 385 | }); 386 | }); 387 | 388 | addEventListener('rejectionhandled', () => { 389 | console.log(als.getStore()); // 'abc' 390 | }); 391 | 392 | const { reject, promise } = als.run(123, () => deferredPromise()); 393 | als.run(321, reject); 394 | ``` 395 | 396 | Here, the promise is created *synchronously* within the scope where the 397 | value of `als.getStore()` is `123`. However, no asynchronous task is 398 | scheduled at that time so the async context *should not be captured*. 399 | 400 | The promise is rejected within the scope where the value of `als.getStore()` 401 | is `321`. Within the implementation of `reject()`, the internal machinery 402 | will determine if the rejection is handled. If it is not, that machinery 403 | will *schedule* the asynchronous dispatch of an `unhandledrejection` event. 404 | The current async context must be captured *at that point* and restored 405 | when the `unhandledrejection` event is dispatched. 406 | 407 | When the promise rejection handler is attached inside the `unhandledrejection` 408 | event listener, prompting a subsequent dispatch of the `rejectionhandled` 409 | event, the value of `als.getStore()` is 'abc', which is propagated to the 410 | `rejectionhandled` event when it is dispatched. 411 | 412 | If someone wants the `unhandledrejection` event to receive the 413 | context at the time the promise was created, then the `reject()` should be 414 | wrapped using `AsyncResource.bind()`, for instance: 415 | 416 | ```js 417 | function deferredPromiseCurrentContext() { 418 | let resolve, reject; 419 | const promise = new Promise((a,b) => { 420 | resolve = a; 421 | reject = AsyncResource.bind(b); 422 | }); 423 | return { promise, resolve, reject } 424 | } 425 | ``` 426 | 427 | This would ensure that whenever `reject()` is called, it enters the 428 | context that was current when the promise was created. 429 | -------------------------------------------------------------------------------- /index.bs: -------------------------------------------------------------------------------- 1 |
  2 | Title: Minimum Common Web Platform API
  3 | Shortname: minimum-common-api
  4 | Group: wintercg
  5 | Status: w3c/CG-DRAFT
  6 | Level: none
  7 | URL: https://min-common-api.proposal.wintertc.org/
  8 | Repository: https://github.com/wintercg/proposal-minimum-common-api
  9 | Editor: James M Snell, Cloudflare https://cloudflare.com/, jsnell@cloudflare.com
 10 | Abstract: Minimum Common Web Platform API for Non-Browser ECMAScript-based runtimes.
 11 | Markup Shorthands: markdown yes
 12 | 
13 | 18 | 19 | Introduction {#intro} 20 | ===================== 21 | 22 | *This section is non-normative.* 23 | 24 | The Minimum Common Web Platform API is a curated subset of standardized Web Platform APIs intended to define a minimum set of capabilities common to Browser and Non-Browser JavaScript-based runtime environments. 25 | 26 | Terminology {#terminology} 27 | ========================== 28 | 29 | The Web Platform is the combination of technology standards defined by organizations such as the W3C, the WHATWG, and others as implemented by Web Browsers. 30 | 31 | A Web-interoperable Runtime is any ECMAScript-based application runtime environment that implements the subset of Web Platform APIs outlined in this specification. 32 | While this term is intentionally broad to also encompass Web Browsers, the primary focus here is on outlining expectations for non-browser runtimes. 33 | 34 | Common API Index {#api-index} 35 | ========================= 36 | 37 | All Web-interoperable Runtimes conforming to this specification SHALL implement each of the following Web Platform APIs in accordance with their normative requirements except where modified here. Where any conforming runtime environment chooses (either by necessity or otherwise) to diverge from a normative requirement of the specification, clear explanations of such divergence MUST be made clearly and readily available in the documentation. 38 | 39 | Interfaces: 40 | 41 | * {{AbortController}} 42 | * {{AbortSignal}} 43 | * {{Blob}} 44 | * {{ByteLengthQueuingStrategy}} 45 | * CompressionStream 46 | * {{CountQueuingStrategy}} 47 | * {{Crypto}} 48 | * {{CryptoKey}} 49 | * DecompressionStream 50 | * {{DOMException}} 51 | * {{ErrorEvent}} 52 | * {{Event}} 53 | * {{EventTarget}} 54 | * {{File}} 55 | * {{FormData}} 56 | 57 | Issue: The {{FormData}} constructor optionally takes {{HTMLFormElement}} and {{HTMLElement}} as parameters. 58 | TODO: Figure out what implementations without DOM support should do here. 59 | Node.js and Deno throw if the first parameter is not `undefined` but ignore the second parameter. 60 | Cloudflare Workers ignores all parameters. 61 | 62 | * {{Headers}} 63 | * {{PromiseRejectionEvent}} 64 | * {{ReadableByteStreamController}} 65 | * {{ReadableStream}} 66 | * {{ReadableStreamBYOBReader}} 67 | * {{ReadableStreamBYOBRequest}} 68 | * {{ReadableStreamDefaultController}} 69 | * {{ReadableStreamDefaultReader}} 70 | * {{Request}} 71 | * {{Response}} 72 | * {{SubtleCrypto}} 73 | * {{TextDecoder}} 74 | * {{TextDecoderStream}} 75 | * {{TextEncoder}} 76 | * {{TextEncoderStream}} 77 | * {{TransformStream}} 78 | * {{TransformStreamDefaultController}} 79 | * {{URL}} 80 | * {{URLPattern}} 81 | * {{URLSearchParams}} 82 | * {{WebAssembly}}.{{Global}} 83 | * {{WebAssembly}}.{{Instance}} 84 | * {{WebAssembly}}.{{Memory}} 85 | * {{WebAssembly}}.{{Module}} 86 | * {{WebAssembly}}.{{Table}} 87 | * {{WritableStream}} 88 | * {{WritableStreamDefaultController}} 89 | * {{WritableStreamDefaultWriter}} 90 | 91 | Global methods / properties: 92 | 93 | * globalThis 94 | * globalThis.{{atob()}} 95 | * globalThis.{{btoa()}} 96 | * globalThis.{{console}} 97 | * globalThis.{{crypto}} 98 | * globalThis.{{fetch()}} 99 | * globalThis.{{navigator}}.{{userAgent}} 100 | * globalThis.onerror (on {{GlobalEventHandlers/onerror|Window}} and {{WorkerGlobalScope/onerror|WorkerGlobalScope}}) 101 | * globalThis.onunhandledrejection (on {{WindowEventHandlers/onunhandledrejection|Window}} and {{WorkerGlobalScope/onunhandledrejection|WorkerGlobalScope}}) 102 | * globalThis.onrejectionhandled (on {{WindowEventHandlers/onrejectionhandled|Window}} and {{WorkerGlobalScope/onrejectionhandled|WorkerGlobalScope}}) 103 | * globalThis.{{performance}}.{{Performance/now()}} 104 | * globalThis.{{performance}}.{{timeOrigin}} 105 | * globalThis.{{queueMicrotask()}} 106 | * globalThis.{{setTimeout()}} / globalThis.{{clearTimeout()}} 107 | * globalThis.{{setInterval()}} / globalThis.{{clearInterval()}} 108 | * globalThis.{{structuredClone()}} 109 | * globalThis.{{WebAssembly}}.{{WebAssembly/compile()}} 110 | * globalThis.{{WebAssembly}}.{{WebAssembly/compileStreaming()}} 111 | * globalThis.{{WebAssembly}}.{{WebAssembly/instantiate()}} 112 | * globalThis.{{WebAssembly}}.{{WebAssembly/instantiateStreaming()}} 113 | * globalThis.{{WebAssembly}}.{{WebAssembly/validate()}} 114 | 115 | The Global Scope {#global-scope} 116 | ================================ 117 | 118 | The exact type of the global scope (`globalThis`) can vary across runtimes. Most Web Platform APIs are defined in terms that assume Web Browser environments that specifically expose types like {{Window}}, {{Worker}}, {{WorkerGlobalScope}}, and so forth. To simplify conformance, all Interfaces, methods, and properties defined by this specification MUST be exposed on the runtime's relevant global scope (e,g., `globalThis.crypto`, `globalThis.ReadableStream`, etc). 119 | 120 | With many runtimes, adding a new global-scoped property can introduce breaking changes when the new global conflicts with existing application code. Many Web Platform APIs define global properties using the `readonly` attribute. To avoid introducing breaking changes, runtimes conforming to this specification MAY choose to ignore the `readonly` attribute for properties being added to the global scope. 121 | 122 | The global object on {{Window}}-like and worker environments must always be an instance of {{EventTarget}}. Web-interoperable runtimes must follow the report an exception algorithm, and the JavaScript HostPromiseRejectionTracker host hook, as defined in [[HTML]]. This includes firing the {{Window/error}}, {{Window/unhandledrejection}} and {{Window/rejectionhandled}} events on the global object. 123 | 124 | Note: Some runtimes might not support firing those events following the HTML specification exactly due to legacy reasons. 125 | For example, in Node.js the global object does not implement {{EventTarget}}, and the relevant events are fired on the `process` object with the names `uncaughtException`, `unhandledRejection` and `rejectionHandled`, respectively. 126 | Such runtimes should not support the {{GlobalEventHandlers/onerror}}, {{WindowEventHandlers/onunhandledrejection}} and {{WindowEventHandlers/onrejectionhandled}} global properties, but they might implement the {{ErrorEvent}} and {{PromiseRejectionEvent}} interfaces. 127 | 128 | Requirements for navigator.userAgent {#navigator-useragent-requirements} 129 | ======================================================================== 130 | 131 | The globalThis.{{navigator}}.{{userAgent}} property is provided such that application code can reliably identify the runtime within which it is running. 132 | The value of the property is a string conforming to the `User-Agent` construction in RFC 7231: 133 | 134 |
135 |   User-Agent      = product *( RWS ( product / comment ) )
136 |   product         = token ["/" product-version]
137 |   product-version = token
138 | 
139 | 140 | While runtimes that implement globalThis.{{navigator}}.{{userAgent}} MUST provide a value that is conformant with the structure defined by RFC 7231, the value SHOULD be treated as a single, complete, opaque, unstructured value. It is RECOMMENDED that the value be limited to a single `product` token excluding the optional `product-version`. For instance, `navigator.userAgent = 'MyRuntime'`. The value SHOULD NOT include any `comment` components. 141 | 142 | Extensions {#extensions} 143 | ======================== 144 | 145 | Runtime-specific extensions to any Web Platform API MAY be implemented by conforming runtimes. Such extensions MUST be defined so that their use neither contradicts nor causes the non-conformance of normative functionality of any Web Platform API. 146 | 147 | Application use of such extensions must be carefully considered, as doing so reduces interoperability and portability of code across runtimes. 148 | -------------------------------------------------------------------------------- /w3c.json: -------------------------------------------------------------------------------- 1 | { 2 | "group": "cg/wintercg", 3 | "contacts": ["jasnell"], 4 | "repo-type": "cg-report" 5 | } 6 | --------------------------------------------------------------------------------