summaryrefslogtreecommitdiff
path: root/extensions/broadcast_channel/01_broadcast_channel.js
blob: 34f8b9e1968e8b39bb181ab37a3cf4f3f552e34d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
"use strict";

((window) => {
  const core = window.Deno.core;
  const webidl = window.__bootstrap.webidl;

  const handlerSymbol = Symbol("eventHandlers");
  function makeWrappedHandler(handler) {
    function wrappedHandler(...args) {
      if (typeof wrappedHandler.handler !== "function") {
        return;
      }
      return wrappedHandler.handler.call(this, ...args);
    }
    wrappedHandler.handler = handler;
    return wrappedHandler;
  }
  // TODO(lucacasonato) reuse when we can reuse code between web crates
  function defineEventHandler(emitter, name) {
    // HTML specification section 8.1.5.1
    Object.defineProperty(emitter, `on${name}`, {
      get() {
        return this[handlerSymbol]?.get(name)?.handler;
      },
      set(value) {
        if (!this[handlerSymbol]) {
          this[handlerSymbol] = new Map();
        }
        let handlerWrapper = this[handlerSymbol]?.get(name);
        if (handlerWrapper) {
          handlerWrapper.handler = value;
        } else {
          handlerWrapper = makeWrappedHandler(value);
          this.addEventListener(name, handlerWrapper);
        }
        this[handlerSymbol].set(name, handlerWrapper);
      },
      configurable: true,
      enumerable: true,
    });
  }

  const _name = Symbol("[[name]]");
  const _closed = Symbol("[[closed]]");
  const _rid = Symbol("[[rid]]");

  class BroadcastChannel extends EventTarget {
    [_name];
    [_closed] = false;
    [_rid];

    get name() {
      return this[_name];
    }

    constructor(name) {
      super();

      window.location;

      const prefix = "Failed to construct 'broadcastChannel'";
      webidl.requiredArguments(arguments.length, 1, { prefix });

      this[_name] = webidl.converters["DOMString"](name, {
        prefix,
        context: "Argument 1",
      });

      this[_rid] = core.opSync("op_broadcast_open", this[_name]);

      this[webidl.brand] = webidl.brand;

      this.#eventLoop();
    }

    postMessage(message) {
      webidl.assertBranded(this, BroadcastChannel);

      if (this[_closed]) {
        throw new DOMException("Already closed", "InvalidStateError");
      }

      core.opAsync("op_broadcast_send", this[_rid], core.serialize(message));
    }

    close() {
      webidl.assertBranded(this, BroadcastChannel);

      this[_closed] = true;
      core.close(this[_rid]);
    }

    async #eventLoop() {
      while (!this[_closed]) {
        const message = await core.opAsync(
          "op_broadcast_next_event",
          this[_rid],
        );

        if (message.length !== 0) {
          const event = new MessageEvent("message", {
            data: core.deserialize(message),
            origin: window.location,
          });
          event.target = this;
          this.dispatchEvent(event);
        }
      }
    }
  }

  defineEventHandler(BroadcastChannel.prototype, "message");
  defineEventHandler(BroadcastChannel.prototype, "messageerror");

  window.__bootstrap.broadcastChannel = { BroadcastChannel };
})(this);