Host App Event Listeners Hangs Host App

So… how do people reliably create event listeners for host app events in UXP plugins??

I have been asked to provide an in house plugin, which is now otherwise completed (with a manual “Refresh” command).

I have tried every conceivable way to add an event listener for events fired by the host app, and none of them have worked properly. Most appear to cause Indesign to hang upon remove/reload in UDT or when packaged. The plugin is in vanilla JS, no react.

All I need is a hook into a change of document in order to refresh the plugin data/UI, for which “afterActivate” on “Document” event seems appropriate,. Adding the event listener works fine, but once added shutting down InDesign will hang.

I would give examples of what I have tried, but I have tried so many different ways I can’t even remember them all!

The main issue seems to be removal of the listeners when the plugin is unloaded. I have tried hooking into plugin destroy method and removing there - I am not even sure that method is being called upon Indesign closing. I added an additional command in the plugin which removes the event listeners - and if that is run manually before shutdown all seems OK.

There is absolutely nothing about this in the docs that I can find - some examples of blocking scripts - which are not really relevant to plugins.

Any help appreciated as I would like to remove the manual Refresh button before release.

Thanks
Jules.

Hi Jules!

Unfortunately, I don’t have a solution for you, but I can confirm that.

The destroy lifecycle hook works, but that’s exactly where InDesign freezes for me.

The bug was fixed in version 20 and then reintroduced in version 21.

Roland

Hi Jules,

@Roland_Dreger has given you the link to the bug above.

FWIW . . .

Before Adobe introduced the bug, I was adding event listeners in the plugin create hook and removing them in the plugin destroy hook. It worked. It was stable. It was logical.

The bug means that you cannot now have the code to remove the event listeners in the plugin destroy hook (code there triggers the crash).

The workaround I have used, which is unsatisfactory but does at least work to allow me to use event listeners, is to check for the existence of the event listeners before I create them. If they exist, I remove the existing ones and then create new ones; if they don’t exist, I just create new ones. This way I avoid duplicate event listeners. I am storing an identifying string in the label property of my event listeners so I know which are mine and which are not (so I don’t remove anyone else’s event listeners).

It is an unsatisfactory workaround because it potentially leaves my event listeners hanging around after my plugin has closed (my remove code is only called as described aboove). I was taught old-school that as a developer I should be responsible for creating and removing event listeners, but, given the bug, I don’t know of any way to do that now.

It is frustrating that after @Roland_Dreger clearly identified the bug, Adobe did briefly fix it, only to re-introduce it! I have no idea why if Adobe fixed it once they can’t fix it again.

Philip

Thanks for the info guys. It appears that Adobe don’t really want to support InDesign plugins? That bug has been around for a long time with zero response from Adobe… the documentation and lack of a type defs file… woeful. I guess resources are tight when your annual revenue is only $23B :wink:

I had also tried only adding the event listener if it was not already there. I can’t really remember if I did that when I was still trying to add with the plugin setup entrypoint or a panel create - nor can I remember the failure mode of that.

I will try again with a guarded addeventlistener on plugin create and see exactly what I get.

Jules.

Yes, let us know if, for some reason, you still can’t get it to work. I too write my plugins in vanilla JS, and I have a plugin where I am reliably catching the Document’s “afterActivate” event, so I know it can be done (with the caveat we’ve been discussing). I’d be happy to share code if it helped, but, to be honest, I think that now you know where the bug is (don’t try to do anything in the plugin destroy hook!), you’ll be OK.

Philip

OK, so I reverted to probably the thing I tried first - as it is the most simple. That is adding an event listener in the entrypoints.plugin.create method - if it has not already been added.

Using the UDT to load the plugin, this just crashes InDesign as soon as the plugin is loaded. Occasionally it doesn’t crash InDesign, but will hang it when the plugin is unloaded or ID is closed.

I suspect this is what made me start looking for other methods to add host event listeners, as I use UDT while developing. I expect everyone does.

However, it appears to (usually) work OK if the plugin is packaged and installed in CC.

So my solution for now is to just skip it if the plugin is running in UDT.

entrypoints.setup({
    plugin: {
        create() {
            setHostEvents(this._pluginInfo);
        },
    },
...
function setHostEvents(pluginInfo = null) {
    if (!pluginInfo || pluginInfo.source == 'devtools') {
        return;
    }
    const existingListener = app.eventListeners.itemByName('bslactivate');
    if (!existingListener || !existingListener.isValid) {
        const newListener = app.addEventListener('afterActivate', (event) => {
            if (
                 event &&
                 event.target &&
                 event.target.constructor.name === 'Document')
            {
                panelRefresh();
            }
        });
        newListener.name = 'bslactivate';
    }
}

Let’s see how that works out.

Again, thanks for the help.
Jules.

Hi Jules,

There has been an issue where InDesign would crash if you tried to add event listeners when InDesign was in a modal state. When I read your most recent post, I thought that was the likely cause of the crashes you describe in that post (and I was about to tell you how I have worked around that problem). However, I have just thrown together a bare bones plugin with your code (excluding the code to not load the plugin from UDT) and I cannot get it to crash InDesign. Your code is working fine for me (no crashes (as yet)) in InDesign 21.4 and in InDesign 20.5.3, in both cases loading from UDT. This makes me wonder whether Adobe have fixed the modal state problem.

Which version (or versions) of InDesign are you running? Are you on a Mac or PC?

Philip

Hi Philip

It is InDesign v21.4 on a PC (Win10). UDT is v2.2.1.2

I thought that maybe I am seeing that issue because of the specific document I am testing on… but I tried a blank document and had the same issue.

Jules.

Hi Jules,

We are using the same version of InDesign and the same version of UDT, but You’re on a PC and I’m on a Mac. I’ll try my little test plugin on a PC and see if I can crash InDesign there. I’ll get back to you when I’ve tried that.

Philip

Hi Jules,

No crash with my test plugin on a PC (I’m running Windows 11 not 10, but I can’t really do anything about that).

If you were OK with sharing your plugin with me (I think you can send it to me attached to a private message), I’d be happy to take a look to see if I can see where it is failing.

Philip

Hi

Strange. I guess there must be something odd going on with my CC/InDesign/UXP install.

I tried creating a new plugin from the quick-starter template and just edited main.js as below - still crashes or hangs for me. Quite often it appears fine, event listener function fires as expected but then hangs when I either go to unload the plugin or close ID. Get an infinite hang and have to kill the ID process.

Anyway, thanks for your help! As it appears fine when the plugin is packaged, I am OK with it as it is. I probably wont need to do much work on it in the future and have made a mental note to avoid Adobe plugins!

Thanks again
Jules.

const { entrypoints } = require('uxp');
const { app } = require('indesign');

entrypoints.setup({
    plugin: {
        create() {
            setHostEvents();
        },
    },
    commands: {
        showAlert: () => showAlert(),
    },
    panels: {
        showPanel: {
            show({ node } = {}) {},
        },
    },
});

showAlert = () => {
    const dialog = app.dialogs.add();
    const col = dialog.dialogColumns.add();
    const colText = col.staticTexts.add();
    colText.staticLabel = 'Congratulations! You just executed your first command.';

    dialog.canCancel = false;
    dialog.show();
    dialog.destroy();
    return;
};

function setHostEvents() {
    const existingListener = app.eventListeners.itemByName('bslpriceronactivate');
    if (!existingListener || !existingListener.isValid) {
        const newListener = app.addEventListener('afterActivate', (event) => {
            if (event && event.target && event.target.constructor.name === 'Document') {
                console.log('fired');
            }
        });
        newListener.name = 'bslpriceronactivate';
    }
}

Hi Jules,

I appreciate that you are OK with it as it is, but it’s been bugging me that I couldn’t figure out why your InDesign was crashing. FWIW, and perhaps for anyone coming to this thread later . . .

Your crashes have predominantly occurred when using UDT. When developing with UDT, as opposed to when you deploy a plugin, you are repeatedly loading and unloading the plugin. It would seem that when you load a plugin, add an event listener, then unload the plugin (without removing the event listener), InDesign becomes unstable (the event listener is still there but the plugin isn’t). When I saw your code I thought what you were doing should work, but when loading and unloading through UDT it didn’t (InDesign crashes). Evidently it is not enough to test for an existing valid event listener and only create it if it doesn’t exist/isn’t valid. Instead you have to test for it, remove it if it exists, create a new one. In other words, you have to create a new event listener on each load (I guess you are remaking the connection between the event listener in InDesign and your plugin each time). I changed the code for your setHostEvents() function to this:

function setHostEvents() {
	const existingListener = app.eventListeners.itemByName('bslpriceronactivate');
    if (existingListener.isValid) existingListener.remove();
	const newListener = app.addEventListener('afterActivate', (event) => {
        if (event && event.target && event.target.constructor.name === 'Document') {
            console.log('fired');
        }
        newListener.name = 'bslpriceronactivate';
    });
}

The first time I loaded the plugin with this code, InDesign crashed, but subsequently it has not, so I am assuming that that first time InDesign was still in a tangle from all my previous loads/unloads and needed to clear itself. I am pretty confident that the logic of this code – testing for existence, removing, adding – is the best we can do. I have another plugin which is set up this way and does not crash.

Of course, this is very tiresome and essentially a workaround because Adobe have introduced the bug which means we can’t remove the event listener in the plugin destroy hook (the obvious place to do it).

As a minor point, and this doesn’t affect this particular issue in any way whatsoever, I noticed that line 2 of the code for main.js that you posted is:

const { app } = require('indesign');

That line comes from Adobe’s template, but it’s not what Adobe recommends. The recommended code (since InDesign v18.4) is this:

const indesign = require('indesign');
const app = indesign.app;

as documented on this page:

https://developer.adobe.com/indesign/uxp/resources/fundamentals/dom-versioning/

Evidently Adobe haven’t updated their starter template to bring it into line with their own recommendation.

You wrote that you

have made a mental note to avoid Adobe plugins!

I completely get where you are coming from.

For myself, I just wish that Adobe would fix the bugs. This particular bug was introduced last year, fixed, then reintroduced. I drew attention to the fact that it had been reintroduced at the end of October last year, but Adobe haven’t even acknowledged my posts about it let alone fixed the bug. You only have to scroll through the posts here in the InDesign category to see much older bugs that have been identified but not fixed. UXP could be great . . .

Philip

Hi Philip

I admire your perseverance!

That change does not solve the issue for me. As before, it might work occasionally but often not. As you say, there is an issue with handlers not being cleaned up. I suspect it might be a wider issue with UDT and the resolving of eventListeners object on the app.

Even in the UDT console app.eventListeners.firstItem() for example will crash ID every time for me. That should really not be the case as firstItem() is a valid method on the eventListeners object prototype.

I do agree that the UXP concept and architecture is good - the InDesign implementation of it is not - and it doesn’t appear that will change any time soon, which is a shame.

Incidentally, did you intend for the new event listener to be named in the callback for the event?

Jules.

Hi Jules,

Oh bother :frowning:

No. That was stupid of me. It should of course be:

function setHostEvents() {
	const existingListener = app.eventListeners.itemByName('bslpriceronactivate');
    if (existingListener.isValid) existingListener.remove();
	const newListener = app.addEventListener('afterActivate', (event) => {
        if (event && event.target && event.target.constructor.name === 'Document') {
            console.log('fired');
        }
    });
    newListener.name = 'bslpriceronactivate';
}

If I have any further ideas, I’ll post again, but I think my ‘perseverance’ (should that be ‘pigheadedness’?) might have run out.

Philip