Page MenuHomePhabricator

More than one CodeMirror instance on page causes issues with preference checkboxes
Closed, ResolvedPublicBUG REPORT

Description

Steps to replicate the issue (include links if applicable):

const require = await mw.loader.using('ext.CodeMirror.v6');
const CodeMirror = require('ext.CodeMirror.v6');

const textarea1 = $('<textarea>').attr('rows', 10)[0];
$('.mw-body-content').append(textarea1);
const cm1 = new CodeMirror(textarea1);
cm1.initialize();

const textarea2 = $('<textarea>').attr('rows', 10)[0];
$('.mw-body-content').append(textarea2);
const cm2 = new CodeMirror(textarea2);
cm2.initialize();
  • Press Ctrl+Shift+, to open preferences in each one
  • Click the label of the checkbox "Show line numbers" (not the checkbox itself!)

What happens?:

chrome_TRjCOd2olI.gif (387×1 px, 28 KB)

What should have happened instead?:
Probably the preferences of two forms should be kept in sync by doing the same sync procedure as CodeMirrorChild does on ext.CodeMirror.preferences.apply hook? I guess a case could be made for having two instances of CodeMirror with different preferences, but seems far-fetched to me? (E.g. in VS Code you can't have different word wrap settings across tabs.)

Technical details:
The cause is the fact that the checkbox inputs they have the same input ID assigned in codemirror.codex.js#L121. Clicking the label with for attribute equal to that ID checks the wrong checkbox.

Note that the second checkbox is changed not directly by the click, but by the means of the hook in codemirror.preferences.js#L614

		// Update the checked state when the preference is changed.
		mw.hook( 'ext.CodeMirror.preferences.apply' ).add( ( pref, enabled ) => {
			if ( pref === name ) {
				input.checked = enabled;
			}
		} );

Event Timeline

Jack_who_built_the_house renamed this task from More than one CodeMirror instance on page causes issues with checkboxes to More than one CodeMirror instance on page causes issues with preference checkboxes.Sep 9 2025, 5:03 PM
Jack_who_built_the_house updated the task description. (Show Details)

We can probably fix the issue with IDs easily enough, but you will likely run into other issues trying to run multiple full CodeMirror instances, particularly hooks interfering with one another. It may work but it's not something I would consider stable or officially supported at this time.

I guess a case could be made for having two instances of CodeMirror with different preferences, but seems far-fetched to me?

Indeed, that's why we have CodeMirrorChild. The idea is for preferences to be managed in one place (the "primary" CodeMirror instance).

It sounds you like just want to be able to toggle CM on child instances independently of the primary instance? If that's the case, we're probably only a few tweaks from making that doable in the CodeMirrorChild class via a fluent config setter method or something. It still feels wrong to me, though.

Mind you that toggling will set the user option usecodemirror. The same is true with the preferences and codemirror-preferences, hence why when dealing with multiple instances, it's recommended to use CodeMirrorChild. Otherwise it can get confusing on which instance is the source of truth.

It sounds you like just want to be able to toggle CM on child instances independently of the primary instance? If that's the case, we're probably only a few tweaks from making that doable in the CodeMirrorChild class via a fluent config setter method or something. It still feels wrong to me, though.

It feels wrong to me too for several reasons:

  1. Conceptually, comment form instances in Convenient Discussions are not related by parent–child relations. They are peers.
  2. CodeMirrorChild is not a mixin; it extends CodeMirror and not CodeMirrorWikiEditor. I work with CodeMirrorWikiEditor, so there would be an overhead if I used CodeMirrorChild and ditched CodeMirrorWikiEditor (duplicated code, upstream desync issues, etc.).
  3. Currently when I disable syntax highlighting on a child instance, it isn't disabled on the parent instance – only the other way around. (Which makes sense for parent–child relations, but see item 1.)
  4. Jumping to another instance when trying to change preferences in one instance is strange UI.

Mind you that toggling will set the user option usecodemirror. The same is true with the preferences and codemirror-preferences, hence why when dealing with multiple instances, it's recommended to use CodeMirrorChild. Otherwise it can get confusing on which instance is the source of truth.

Yeah, I know. With WikiEditor, the same is true of the preference for the toolbar section expanded (none or "Advanced" or "Special characters"; although that one is saved in cookies). Your mileage may vary, but I think it's conceptually OK to have the preference set by the last action as the source of truth.

[...] you will likely run into other issues trying to run multiple full CodeMirror instances, particularly hooks interfering with one another. It may work but it's not something I would consider stable or officially supported at this time.

I already stumbled upon some hooks not being cleaned up even in one instance (addMwHook() is not used for the hooks added in other classes than CodeMirror), but that didn't look critical and I didn't create a task.

I personally see value in having structures in place allowing for syncing stuff across active instances (except for the active/inactive state itself) – as a side benefit, that would possibly minimize the responsibility of CodeMirrorChild. But if it's not supported and you don't feel like introducing support for it, that's OK – I'll go the hacker way and/or maybe submit a patch myself. On a side note, I have 2–3 scripts already in works (Convenient Discussions is only one) which are going to have to deal with multiple CodeMirror instances. I see use cases for it and will try to prove it in practice.

Your mileage may vary, but I think it's conceptually OK to have the preference set by the last action as the source of truth.

E.g. on the web, you may change preferences in one tab, while old tabs of the same website may stay with old preferences. So unless you refresh the pages, they will stay outdated. The old tabs are neither invalidated nor synced due to the fact that the value in source of truth has been updated. Mechanisms to counter this are costly and rarely developed. That's the way I see it at least...

At the same time, since the mechanisms for syncing preferences in CodeMirror are already in place, I don't suggest to do anything with them (why would I). In fact, even if the active/inactive state of instances (not just the preferences) was synced, I wouldn't mind much – that doesn't seem like much of a point of contention.

And one more note: If you prefer to stop short of supporting syncing between instances, I'd still argue for effort to be made to decouple instances from each other, even if that requires decoupling hooks and such. Like with the characteristic example of conflicting HTML element IDs, it's just favored practice to allow things to work in isolation from each other. With all due respect, "Support for having multiple instances of a class" (not a singleton!) shouldn't be a thing; that should be a default. (Unlike "Support for having instances synced".)

Change #1187995 had a related patch set uploaded (by MusikAnimal; author: MusikAnimal):

[mediawiki/extensions/CodeMirror@master] CodeMirrorCodex: use unique IDs for checkboxes

https://gerrit.wikimedia.org/r/1187995

MusikAnimal changed the task status from Open to In Progress.Sep 15 2025, 12:44 AM
MusikAnimal claimed this task.

And one more note: If you prefer to stop short of supporting syncing between instances, I'd still argue for effort to be made to decouple instances from each other, even if that requires decoupling hooks and such. Like with the characteristic example of conflicting HTML element IDs, it's just favored practice to allow things to work in isolation from each other. With all due respect, "Support for having multiple instances of a class" (not a singleton!) shouldn't be a thing; that should be a default. (Unlike "Support for having instances synced".)

I concur! The story of the CodeMirror JS architecture is long and winding. It of course began with just support for #wpTextbox1, has slowly grown from there, and as you can see there is still work to be done :) I believe the MediaWiki JS hooks are the main issue, along with the IDs. The latter should be fixed with the above patch.

CodeMirrorChild can stay as-is, for the use-case were you want children and not peers, such as ProofreadPage and at Special:ExpandTemplates. So it's just CodeMirror itself and maybe a few other subclasses that need tending to.

As for the hooks, I guess any of them that we're using for internal reactivity can be reworked to use something else. The @stable hooks meant for public consumption however may pose another threat to a multi-CodeMirror environment. I'm thinking we just need to ensure we provide the hook handler with a reference to the triggering instance, and we can leave burden of preventing redundancy or otherwise unwanted behaviour to them.

Change #1187995 merged by jenkins-bot:

[mediawiki/extensions/CodeMirror@master] CodeMirrorCodex: use unique IDs for checkboxes

https://gerrit.wikimedia.org/r/1187995

MusikAnimal closed this task as Resolved.EditedSep 19 2025, 8:07 PM
MusikAnimal moved this task from Bugs to Done on the MediaWiki-extensions-CodeMirror board.

Closing as resolved. We can improve the interactivity (or lack thereof) of multiple CM instances in a separate task/patch.