Page MenuHomePhabricator

ULS (new version) is displayed in the wrong place on pages with <score> markup
Closed, ResolvedPublic2 Estimated Story PointsBUG REPORT

Description

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

What happens?:

The language drop-down is displayed at the top left of the page:

image.png (952×562 px, 77 KB)

What should have happened instead?:

The drop-down should appear below the button instead.

Other information (browser name/version, screenshots, etc.):

Happens on both Chrome and Firefox current stable versions; I've reproduced this consistently on pages that have a <score> tag but not on others. It also looks ULS-rewrite specific.

Event Timeline

abi_ triaged this task as High priority.
abi_ moved this task from Out of scope to In scope on the LPL Projects (ULS rewrite) board.
abi_ set the point value for this task to 2.
abi_ subscribed.

We bind the click handler to the trigger with the following code:

$( document ).on( 'click', '.mw-interlanguage-selector', loadContentLanguageSelector );

On https://en.wikipedia.org/wiki/D_major?debug=true, the ev.currentTarget is pointing to the HTMLDocument whereas on other pages it points to the .mw-interlanguage-selector.

Since it points to the HTMLDocument, Floating UI positions the dialog relative to the HTMLDocument

Change #1302200 had a related patch set uploaded (by Abijeet Patro; author: Abijeet Patro):

[mediawiki/extensions/UniversalLanguageSelector@master] ULS rewrite: Change the way we determine the trigger element

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

After bunch of back and worth with ChatGPT I got this:

The minimal reproducer is now:

$( document ).on( 'click.emptyDocHandlerTest', () => {} );

on a page where ULS has a delegated document handler:

$( document ).on( 'click', '.mw-interlanguage-selector', loadContentLanguageSelector );

So <score> triggers the bug because ext.score.popup happens to add a direct document click handler, not because the score handler mutates DOM or because <score> matches anything.

What this implies

This is most likely a jQuery event-dispatch subtlety.

When only the delegated ULS handler exists, inside loadContentLanguageSelector:

ev.currentTarget === .mw-interlanguage-selector

When a direct document click handler is also registered, jQuery’s internal dispatch path includes both:

delegated handler for .mw-interlanguage-selector
direct handler on document

Some code in ULS is probably using an event property asynchronously, or after jQuery has finished dispatching, and that value is no longer the matched delegate element. It may have become document, a jQuery wrapper, or another object by the time ULS V2 positioning uses it.

That would explain all observed facts:

<score> page loads ext.score.popup
→ ext.score.popup registers a direct document click handler
→ jQuery dispatch path changes
→ ULS V2 receives or later observes wrong trigger/reference
→ Floating UI throws getBoundingClientRect is not a function

The likely ULS fix

ULS should capture the raw DOM trigger synchronously at the start of the handler and use only that captured value later:

function loadContentLanguageSelector( ev ) {
	const triggerElement = ev.target instanceof Element ?
		ev.target.closest( '.mw-interlanguage-selector' ) :
		null;

	if ( !triggerElement ) {
		return;
	}

	const $target = $( triggerElement );

	// Avoid reinitializing ULS multiple times for an element
	if ( $target.attr( 'data-uls-loaded' ) ) {
		return;
	}

	$target.attr( 'data-uls-loaded', true );
	ev.preventDefault();

	// Later, even inside async callbacks:
	createUniversalLanguageSelector( {
		triggerElement: triggerElement,
		// ...
	} );
}

The critical part is: do not pass ev.currentTarget, ev.target, this, or $target into asynchronous code unless it has been normalized to a raw Element first.

Change #1302255 had a related patch set uploaded (by Nik Gkountas; author: Nik Gkountas):

[mediawiki/extensions/UniversalLanguageSelector@master] ULS rewrite: Capture trigger element before async module load

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

Change #1302200 abandoned by Abijeet Patro:

[mediawiki/extensions/UniversalLanguageSelector@master] ULS rewrite: Change the way we determine the trigger element

Reason:

in favor of Ia96535eb7224ad5d02ee5dce4a8f70c9d05fef0e

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

Change #1302255 merged by jenkins-bot:

[mediawiki/extensions/UniversalLanguageSelector@master] ULS rewrite: Capture trigger element before async module load

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

Change #1303009 had a related patch set uploaded (by Abijeet Patro; author: Nik Gkountas):

[mediawiki/extensions/UniversalLanguageSelector@wmf/1.47.0-wmf.7] ULS rewrite: Capture trigger element before async module load

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

Change #1303009 merged by jenkins-bot:

[mediawiki/extensions/UniversalLanguageSelector@wmf/1.47.0-wmf.7] ULS rewrite: Capture trigger element before async module load

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

Mentioned in SAL (#wikimedia-operations) [2026-06-17T13:53:14Z] <lucaswerkmeister-wmde@deploy1003> Started scap sync-world: Backport for [[gerrit:1302739|ULS rewrite: Lock body scroll when open on mobile]], [[gerrit:1302743|ULS rewrite: Fix settings dialog width and field sizing (T416512)]], [[gerrit:1303010|ULS rewrite: Show variants even when no languages are available (T426532)]], [[gerrit:1303009|ULS rewrite: Capture trigger element before async module load (T429145)]], [[gerri

Mentioned in SAL (#wikimedia-operations) [2026-06-17T13:55:11Z] <lucaswerkmeister-wmde@deploy1003> lucaswerkmeister-wmde, abi: Backport for [[gerrit:1302739|ULS rewrite: Lock body scroll when open on mobile]], [[gerrit:1302743|ULS rewrite: Fix settings dialog width and field sizing (T416512)]], [[gerrit:1303010|ULS rewrite: Show variants even when no languages are available (T426532)]], [[gerrit:1303009|ULS rewrite: Capture trigger element before async module load (T429145)]], [[ge

Mentioned in SAL (#wikimedia-operations) [2026-06-17T14:12:59Z] <lucaswerkmeister-wmde@deploy1003> Finished scap sync-world: Backport for [[gerrit:1302739|ULS rewrite: Lock body scroll when open on mobile]], [[gerrit:1302743|ULS rewrite: Fix settings dialog width and field sizing (T416512)]], [[gerrit:1303010|ULS rewrite: Show variants even when no languages are available (T426532)]], [[gerrit:1303009|ULS rewrite: Capture trigger element before async module load (T429145)]], [[gerr