Any way to reliably obtain a current realm's global object?

I'm developing a library function, which can be called by consumers however they like.

In the implementation of the library function, I would like to obtain a reference of the global object of the current realm, which is different from variables window and globalThis.

Suppose that a consumer's website https://a.com contains an iframe that also points to https://a.com. Since they are same-origin, a script in the child frame may call the library function loaded into the parent frame. What I would like to do is to somehow distinguish inside the library function implementation whether it is called in the main frame or in the child frame.

The reason why I'd like to do is to use window.event property. It is defined only on the global object of the realm on which the event listener is defined. So, if a parent frame and child frame both adds a listener to a single div, in each event listener's function body, and these function body calls a library's function, then in the library's function, window.event will have different value – presumably to the use of event variable in attribute event listener work.

My gut feeling is that there should be a way of doing it, but I can't find one.

Hi @theseanl - I think the answer to this is no. My understanding is that it is intentional for the spec to not allow functions to have dynamic scope. There was Function.prototype.caller but this is deprecated and not supported in all environments.

There are security focused projects where dynamic scoping would expose a way to circumvent the sandbox. Others like @mhofman and @kriskowal know more about this than me.

I don't quite understand, because I'm not trying to make functions to have dynamic scope.
I just need to obtain a reference of window in the current scope.... of the realm which initiated the current stack..

Hi @theseanl - that is a form of dynamic scope. If what the function can access changes based on where it was called from. That is dynamic scope.

Thank you for your reply. Then may I ask, whether it is by design that functions can have a some form of difference based on where it was called from, or could it be a defect of a spec? My experience is that the use of window.event precisely have that kind of difference.

+) I've never encountered things like this before, and since window.event is a deprecated API, I'm guessing that it could be a hole in the spec that spec isn't interested in fixing it.

Interesting, could you expand on how window.event is an example of dynamic scope. That property is a getter function that returns the event that is currently being processed. Testing it out I see the same value in two different event listeners created in two different same-origin iframes.

Well, my reply was hidden by a spam filter, awaiting review from moderators. Will delete this reply after it is reinstated.

Basically, the code is this:

function myAPIFunction() {
    console.log(window.event);
}

var iframe = document.createElement('iframe');

iframe.onload = () => {
    iframe.contentWindow.eval(`
        var div = document.createElement('div');
        div.id = "ClickMe";

        div.onclick = () => {
            parent.myAPIFunction()
        };

        div.textContent = 'click me';
        document.documentElement.append(div);
    `)
    iframe.contentWindow.ClickMe.addEventListener('click', myAPIFunction)
}

document.documentElement.append(iframe);

In Chrome and Firefox, it prints undefined and PointerEvent. The only difference between two listeners are on which realm it was evaluated IMO.

If I change the lines

        div.onclick = () => {
            parent.myAPIFunction()
        };

to

div.onclick=parent.myAPIFunction

in Chrome, the behavior changes to printing PointerEvent and PointerEvent, which seems to be illustrating that the difference stems from realms.
In Firefox, it doesn't change. I'm not sure which one is a browser bug. Usually Chrome is right in cases like this..

As a DOM API, this is spec'd by whatwg. And that specification does base the global it assigns the global event property to off of the callback function thats handling the event as per:

  1. Let global be listener callback’s associated Realm’s global object.
  2. Let currentEvent be undefined.
  3. If global is a Window object, then:
    1. Set currentEvent to global’s current event.
    2. If invocationTargetInShadowTree is false, then set global’s current event to event.

This is necessary so that the event reference in the scope of the callback correctly refers to the event that's being handled since the callback may not be in the same realm as where the event originated. When you wrap the api function in another function with something like

div.onclick = () => {
    parent.myAPIFunction()
};

The callback is going to be wrapper function (arrow function) which is from the current realm and that then becomes where the global event is going to be placed. The api function then won't see a global event because it never got assigned to the global of its realm.

So if your api function is not the callback being triggered for the event, and it wasn't defined in the same realm as the originating event, it won't see the global event object. And if it was the callback, you wouldn't need to rely on the global event because you'd also have the event argument passed into your function directly.

Assuming you have the event object, you could tell from which window it originated using event.target.ownerDocument.defaultView.

Thank you for referencing the relevant spec. It seems a reasonable behavior given that spec explicitly mentions the realm. However, I've mentioned the difference of the behavior between Firefox and Chrome when div.onclick is changed to parent.myAPIFunction. Which browser do you think has a bug?

The whole point of the post is that this does not work. Try it in the code above, the event.target.ownerDocument.defaultView will always point to the child frame's window. Therefore, in the event listener attached from the parent window, window.event will be undefined, and there's no way to tell which window this event is attached to.

There is an alternative if the event is a UIEvent, using UIEvent.view property, but there are many other UI-like events such as clipboard events, selection API events, which are subject to this issue. Would there be a way to determine its view? I've encountered this bug in the wild, because Google Docs uses a separate realm to listen to every user-generated interaction, and my webextension has to identify those.

I'm actually surprised it worked in Firefox. I thought they didn't support the global event at all, but it looks like they fixed it a couple of years ago. As for the behavior Chrome looks to have it right. Firefox's implementation doesn't seem to be handling that case correctly.

Right, since that is the origin of the event. Are you trying to figure out where the listener was added rather than invoked? That I don't think you'd be able to pull off.

Indeed, it's nice to see that Firefox is keeping up with the latest spec.

It is not right to say that It is because of the origin of the event. It's because where the listener was created, irrelevant with the event that is being dispatched.

This sounds like to be against to the intention of the strict mode. Now I've figured out that this issue is specific to window.event, I think it would be nice if events have a property like currentRealm whose value is changed during each event listeners, similar to how currentTarget changes.