saghul/txiki.js

TypeError: 'this' is expected an EventTarget object, but got another value.

EmixamPP opened this issue · 12 comments

In the browser, when executing some code in a Worker, the context is the Worker object itself, which implies that the self is not required.

For example, we can do addEventListener without requiring the self. in front of.

The issue is that some libraries make assumption of that in the code they pass to the worker, which make them unusable with txiki.js.

For reference, here is the unminified code that my library wants to run in a worker.
(() => {
    var e = {
            472: (e, t, r) => {
                var o, i;
                void 0 ===
                    (i =
                        "function" ==
                        typeof (o = function () {
                            "use strict";
                            var e = new Map(),
                                t = new Map(),
                                r = function (t) {
                                    var r = e.get(t);
                                    if (void 0 === r) throw new Error('There is no interval scheduled with the given id "'.concat(t, '".'));
                                    clearTimeout(r), e.delete(t);
                                },
                                o = function (e) {
                                    var r = t.get(e);
                                    if (void 0 === r) throw new Error('There is no timeout scheduled with the given id "'.concat(e, '".'));
                                    clearTimeout(r), t.delete(e);
                                },
                                i = function (e, t) {
                                    var r,
                                        o = performance.now();
                                    return { expected: o + (r = e - Math.max(0, o - t)), remainingDelay: r };
                                },
                                n = function e(t, r, o, i) {
                                    var n = performance.now();
                                    n > o ? postMessage({ id: null, method: "call", params: { timerId: r, timerType: i } }) : t.set(r, setTimeout(e, o - n, t, r, o, i));
                                },
                                a = function (t, r, o) {
                                    var a = i(t, o),
                                        s = a.expected,
                                        d = a.remainingDelay;
                                    e.set(r, setTimeout(n, d, e, r, s, "interval"));
                                },
                                s = function (e, r, o) {
                                    var a = i(e, o),
                                        s = a.expected,
                                        d = a.remainingDelay;
                                    t.set(r, setTimeout(n, d, t, r, s, "timeout"));
                                };
                            addEventListener("message", function (e) {
                                var t = e.data;
                                try {
                                    if ("clear" === t.method) {
                                        var i = t.id,
                                            n = t.params,
                                            d = n.timerId,
                                            c = n.timerType;
                                        if ("interval" === c) r(d), postMessage({ error: null, id: i });
                                        else {
                                            if ("timeout" !== c) throw new Error('The given type "'.concat(c, '" is not supported'));
                                            o(d), postMessage({ error: null, id: i });
                                        }
                                    } else {
                                        if ("set" !== t.method) throw new Error('The given method "'.concat(t.method, '" is not supported'));
                                        var u = t.params,
                                            l = u.delay,
                                            p = u.now,
                                            m = u.timerId,
                                            v = u.timerType;
                                        if ("interval" === v) a(l, m, p);
                                        else {
                                            if ("timeout" !== v) throw new Error('The given type "'.concat(v, '" is not supported'));
                                            s(l, m, p);
                                        }
                                    }
                                } catch (e) {
                                    postMessage({ error: { message: e.message }, id: t.id, result: null });
                                }
                            });
                        })
                            ? o.call(t, r, t, e)
                            : o) || (e.exports = i);
            },
        },
        t = {};
    function r(o) {
        var i = t[o];
        if (void 0 !== i) return i.exports;
        var n = (t[o] = { exports: {} });
        return e[o](n, n.exports, r), n.exports;
    }
    (r.n = (e) => {
        var t = e && e.__esModule ? () => e.default : () => e;
        return r.d(t, { a: t }), t;
    }),
        (r.d = (e, t) => {
            for (var o in t) r.o(t, o) && !r.o(e, o) && Object.defineProperty(e, o, { enumerable: !0, get: t[o] });
        }),
        (r.o = (e, t) => Object.prototype.hasOwnProperty.call(e, t)),
        (() => {
            "use strict";
            r(472);
        })();
})();

Can you provide a smaller version sample please?

In addition, when a message is posted to the Worker, it is wrapped inside this structure:

{
 isTrusted: false,
  [Symbol(kMessageEventData)]: [<message content>]
}

Which is not standard I think? And so, cause issues.

I will include all of that in a small test file that can be executed easily to debug the problem

It's a message event, which I think it is the standard way?

It might need tweaks, let me know what you find!

May bad, I did a typo, I've modified the code snipped.
The thing that is not standard, I think, is that the message content is wrapped inside a list.

Can you please give this a try? #558

Although, I still have the initial issue.
I created a worker-echo.js file:

import assert from 'tjs:assert';
import path from 'tjs:path';


const data = JSON.stringify({foo: 42, bar: 'baz!'});
const w = new Worker(path.join(import.meta.dirname, 'helpers', 'worker-echo.js'));
const timer = setTimeout(() => {
    w.terminate();
    assert.fail('Timeout out waiting for worker');
}, 1000);
w.onmessage = event => {
    clearTimeout(timer);
    w.terminate();
    const recvData = event.data;
    assert.eq(recvData, data, 'Message received matches');
};
w.postMessage(`${data}`);

Content of the worker-echo.js:

addEventListener('message', function(e) {
    postMessage(e.data);
});

Execute result:

$ build/tjs run tests/test-worker-echo.js                                                                                                                                                                                                          
TypeError: 'this' is expected an EventTarget object, but got another value.
    at jr (:0:0)
    at addEventListener (:0:0)
    at <anonymous> (/home/maxime/mk6/lv_binding_js/deps/txiki/tests/helpers/worker-echo.js:4:1)

Edit:
What I observe, omitting the self for postMessage is fine, but not for addEventListenner

Do you want me to publish a branch on my fork to let you run the test?

Since window also has addEventListener, this alone reproduces the problem:

addEventListener('message', function(e) {
    postMessage(e.data);
});

I guess this is wrong somehow:

Object.setPrototypeOf(window, EventTarget.prototype);

Hum, this fixes it:

self.addEventListener('message', function(e) {
    postMessage(e.data);
});

I think the problem is when things are called in the implicit globalThis object are not bound properly. Using globalThis.addEventListener also works...

Yes indeed, but would be nice that this self becomes implicit

Yep. self is globalThis is window on a worker, so I think it's the implicit binding, somehow.

Fixed in #562