diff --git a/src/content/compatibility-flags/spec-compliant-event-handler-attributes.md b/src/content/compatibility-flags/spec-compliant-event-handler-attributes.md new file mode 100644 index 00000000000..bc4260f4191 --- /dev/null +++ b/src/content/compatibility-flags/spec-compliant-event-handler-attributes.md @@ -0,0 +1,46 @@ +--- +_build: + publishResources: false + render: never + list: never + +name: "Spec compliant `on` event handlers" +sort_date: "2026-08-14" +enable_flag: "spec_compliant_event_handler_attributes" +disable_flag: "no_spec_compliant_event_handler_attributes" +--- + +`on` event handler properties, such as `WebSocket.onmessage` and `AbortSignal.onabort`, behave the way the [DOM](https://dom.spec.whatwg.org/#interface-eventtarget) and [HTML](https://html.spec.whatwg.org/multipage/webappapis.html#event-handler-attributes) standards describe them. + +Previously, `EventTarget` looked for an `on` property on the object every time an event was dispatched and called it before any listener added with `addEventListener()`. That had two consequences: an `on` handler always ran first rather than in the order it was assigned, and a class that extends `EventTarget` and implements an `on` accessor using `addEventListener()` had its handler called twice per event. + +With this flag enabled, `EventTarget` no longer looks for `on` properties. The interfaces that the standards give event handler attributes to (`AbortSignal`, `EventSource`, `MessagePort` and `WebSocket`) implement them as accessors that register an ordinary listener, so: + +- The handler runs in the order it was assigned, in sequence with listeners added using `addEventListener()`. +- Assigning a different handler replaces the value without changing its position in the listener list. +- Assigning `null` removes it. Assigning any other non-object is treated as `null`. A non-callable object is stored and returned by the property, but never called. +- `this` inside the handler is the object the handler was assigned to. +- Returning `false` from the handler cancels the event, per the [event handler processing algorithm](https://html.spec.whatwg.org/multipage/webappapis.html#the-event-handler-processing-algorithm). Any other return value is ignored. Without this flag, returning `true` cancels the event and `false` is ignored. That is not standard behavior. The DOM standard ignores the return value of a listener added with `addEventListener()` entirely, and the Workers runtime applies its own `true` cancels rule to those listeners as well. This flag changes only `on` handlers, so listeners added with `addEventListener()` keep the existing behavior. + +```js +const controller = new AbortController(); +controller.signal.addEventListener("abort", () => console.log("a")); +controller.signal.onabort = () => console.log("b"); +controller.signal.addEventListener("abort", () => console.log("c")); +controller.abort(); +// Logs "a", "b", "c". Without this flag, it logs "b", "a", "c". +``` + +Assigning an `on` property for an event type that the interface does not define a handler for has no effect. `EventSource`, for example, defines only `onopen`, `onmessage` and `onerror`, so a named server-sent event has to be observed with `addEventListener()`: + +```js +// Has no effect. +eventSource.ontest = (event) => console.log(event.data); + +// Use this instead. +eventSource.addEventListener("test", (event) => console.log(event.data)); +``` + +Handlers on the global scope are not affected, so `onfetch`, `onscheduled` and the other global handlers in Service Worker syntax continue to work as before. + +Once this flag becomes the default for a compatibility date, add the `no_spec_compliant_event_handler_attributes` compatibility flag to keep the previous behavior. diff --git a/src/content/compatibility-flags/spec-compliant-message-event-origin.md b/src/content/compatibility-flags/spec-compliant-message-event-origin.md new file mode 100644 index 00000000000..73a46c99e10 --- /dev/null +++ b/src/content/compatibility-flags/spec-compliant-message-event-origin.md @@ -0,0 +1,34 @@ +--- +_build: + publishResources: false + render: never + list: never + +name: "Spec compliant `MessageEvent.origin`" +sort_date: "2026-08-14" +enable_flag: "spec_compliant_message_event_origin" +disable_flag: "no_spec_compliant_message_event_origin" +--- + +`MessageEvent.origin` reports what the standards say it should. Two things change. + +**An absent origin reports an empty string instead of `null`.** A message event's origin is internally nullable, and [the standard's getter](https://html.spec.whatwg.org/multipage/comms.html#dom-messageevent-origin) reports the empty string for the null case. The Workers runtime reported `null` instead. This is what a message delivered through a `MessagePort`, or from a `WebSocketPair` endpoint, now reports, since neither has a URL to take an origin from. + +```js +const { port1, port2 } = new MessageChannel(); +port2.onmessage = (event) => console.log(event.origin === ""); +port1.postMessage("hello"); +// Logs true. Without this flag, event.origin is null. +``` + +**A `WebSocket` opened from a URL reports the origin of that URL.** The [WebSocket standard](https://websockets.spec.whatwg.org/#feedback-from-the-protocol) requires the message event's origin to be the serialized origin of the WebSocket's URL, which the Workers runtime did not do. `EventSource` already reported the origin of its event stream. + +```js +const ws = new WebSocket("wss://example.com/chat"); +ws.addEventListener("message", (event) => console.log(event.origin)); +// Logs "wss://example.com". Without this flag, event.origin is null. +``` + +Code that compares `origin` against `null` needs updating either way. For the absent case, note that `""` and `null` are both falsy, so a check like `if (!event.origin)` works before and after. + +Once this flag becomes the default for a compatibility date, add the `no_spec_compliant_message_event_origin` compatibility flag to keep the previous behavior.