Repository: electron
Issue: [Feature Request]: `MessagePortMain` should comply with the standard `MessagePort` interface from the HTML spec
Labels: enhancement :sparkles:

### Preflight Checklist

- [X] I have read the [Contributing Guidelines](https://github.com/electron/electron/blob/main/CONTRIBUTING.md) for this project.
- [X] I agree to follow the [Code of Conduct](https://github.com/electron/electron/blob/main/CODE_OF_CONDUCT.md) that this project adheres to.
- [X] I have searched the [issue tracker](https://www.github.com/electron/electron/issues) for a feature request that matches the one I want to file, without success.

### Problem Description

I am trying to write code for handling message ports, and I want this code to be common to both my main and renderer processes. Unfortunately, this is onerous with the current `MessagePortMain` API, because it is incompatible with the standard `MessagePort` API available on the web—that is, a `MessagePortMain` instance cannot be used as though it were a `MessagePort` instance. For reference, [here is the MDN page](https://developer.mozilla.org/en-US/docs/Web/API/MessagePort) on `MessagePort`, and [here is the relevant section of the spec](https://html.spec.whatwg.org/multipage/web-messaging.html#message-ports).

The primary incompatibilities are:
- `MessagePortMain` is not an [`EventTarget`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget), and is instead only an `EventEmitter`. `MessagePortMain` also does not support related event listener attributes like `onmessage`.
- Event objects fired by `MessagePortMain` do not implement [`MessageEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent), and are instead just plain objects with `data` and `ports` properties. This object is actually compatible with the `MessageEvent` interface, but is incompatible the other way around.

### Proposed Solution

`MessagePortMain` should implement `EventTarget`, and its event objects should implement `MessageEvent`. Purely for the sake of API compatibility, `MessagePortMain` should support `messageerror` listeners, even if this event is never actually fired.

To a fair few people, this probably sounds like quite a strange request. `MessagePort` is a web API from the HTML spec using DOM event-handling methods; how would it make sense for a Node API to support that stuff?  There are two reasons why I think this is actually entirely reasonable, despite it sounding odd.

The first is essentially the use case I've described. From an API perspective, `MessagePort` and `MessagePortMain` are effectively identical in concept and behavior. It would be powerful if general-purpose code targeting the standard `MessagePort` interface could be easily used with `MessagePortMain`, and I submit that, as a matter of principle in the interests of broad ecosystem compatibility, this should be possible. But more importantly, the need to do this is especially pressing in the context of Electron. While the distinction between main code and renderer code is obviously critical from an architecture perspective, the distinction becomes quite insignificant when you're actually writing the code; it's all just 'the app'. I should be able to do what I'm trying to do: write code for handling message ports once, and use this code in both parts of my app. Again, `MessagePort` and `MessagePortMain` are effectively identical in concept and behavior, and so it is only intuitive that this should be possible.

The second reason this wouldn't be a strange thing to do is because Node itself has already done exactly this. Since at least version 14, Node has supported [a built-in `MessagePort` API](https://nodejs.org/dist/latest-v18.x/docs/api/worker_threads.html#class-messageport) that, perhaps to the surprise of some, actually does effectively comply with the spec. Node has [built-in support for `Event` and `EventTarget`](https://nodejs.org/dist/latest-v18.x/docs/api/events.html#eventtarget-and-event-api) from the DOM spec, which are implemented in its `MessagePort` API. It fires events as spec-compliant `MessageEvent` objects (the `MessageEvent` constructor is globally available in Node; try it); it supports the `onmessage` attribute for setting a listener; and it even supports `messageerror` listeners, even though [the documentation admits](https://nodejs.org/dist/latest-v18.x/docs/api/worker_threads.html#event-messageerror) that this is probably never fired in normal usage.

The fact that Node's `MessagePort` is spec-compliant means that `MessagePortMain` is inconsistent with both web message ports *and* Node message ports; general-purpose code using message ports can target both the web and Node, but not the Electron main process.

Node's `MessagePort` API is a superset of the standard API: it implements `EventEmitter` as well, and also supports other Node-specific methods. But this is fine; it provides additional methods for interested consumers of the API, while supporting the standard API for uninterested consumers. This is how I think the `MessagePortMain` API should be. To be clear, I understand that `MessagePortMain` is based on Chromium technology and so is fundamentally different from Node's `MessagePort`, but I think the fact that Node's message port implementation supports this is a compelling argument for why Electron's message port implementation should too.

### Alternatives Considered

In theory, `MessagePortMain` can be [adapted](https://en.wikipedia.org/wiki/Adapter_pattern) to provide a standard `MessagePort` interface. In practice, this is non-trivial. Mapping `addEventListener` to `on` and `removeEventListener` to `off` is simple enough, but resolving the incompatibility of the event objects is more complex. In any case, it's not ideal. This is probably the solution I'll employ until this can be resolved, assuming it can be resolved.

The idea I actually came up with first was to reduce *both* `MessagePort` and `MessagePortMain` to a common interface, and then target that interface instead. In some ways this was actually simpler, but I decided this solution was unworkable because now my code was coupled to this non-standard interface I had created when it should not have been, hampering reusability.

### Additional Information

This is not the same issue as #33086, although I guess resolving that issue would incidentally resolve this for free.