Page MenuHomePhabricator

Optimize Preferences submenu modals
Closed, ResolvedPublic

Assigned To
Authored By
Dec 1 2022, 5:51 PM
Referenced Files
F35859550: image.png
Dec 12 2022, 4:21 PM
F35848306: image.png
Dec 9 2022, 8:01 PM
F35848304: image.png
Dec 9 2022, 8:01 PM
F35848298: image.png
Dec 9 2022, 8:01 PM
F35848295: image.png
Dec 9 2022, 8:01 PM
F35848292: image.png
Dec 9 2022, 8:01 PM
F35848290: image.png
Dec 9 2022, 8:01 PM
F35848288: image.png
Dec 9 2022, 8:01 PM


While T317106 was in flight, we refined our understanding of how we should take performance into consideration and also when and where to use jQuery in OOUI-centric pages. This task is intended to encompass any work related to identifying and addressing js code quality concerns, such as jQuery usage and performance.

The result should be functionally equivalent to T317106, so the acceptance criteria have been copied over.

Acceptance criteria

  • Navigating to a submenu in the new Preferences design should display a modal dialog
  • Clicking the back arrow should return users to the top of the Preferences main menu
  • The grey navigation bar and Preferences headings should no longer be visible in submenus
  • Form state should be preserved during navigation:
    • the save button should initially be disabled (gray)
    • changing a setting should enable it (blue)
    • the button should stay enabled when navigating between sections
    • changes across multiple sections should be recorded by a single click of the save button

Event Timeline

Change 863394 had a related patch set uploaded (by Jsn.sherman; author: Jsn.sherman):

[mediawiki/core@master] WIP: Optimize mobile prefs modals

update: I think this is ready for us to talk about in engineering.

When I started going over this with a fine-tooth comb, I realized that we were not fully leveraging OOUI. I think I've arrived at at a pretty good optimization that takes full advantage of the OOUI that we're already paying the cost for calling while trimming down everything else.

I have not taken the time to carefully curate the patchsets (nor add as many educational comments) as I did with my previous optimization patch, because that took a whole lot of time to do. It might be more valuable for our team to just go through the changes synchronously.

I don't believe I've created any regressions here, though we should definitely QA it because I cut some CSS that I didn't *think* was needed, but wasn't sure about, eg.

.oo-ui-window-body > #preferences {
    position: sticky;
    bottom: 0;

performance-wise: I made a function to encapsulate the work done to make the sections clickable. This is work that has to be done before the user can do anything with the form:

original initDialogs: 16.32ms

image.png (588×1 px, 70 KB)

optimized initDialogs: 0.77ms

image.png (588×1 px, 69 KB)

I did not actually upload a patch for moving the original code to a named function, so I'll just paste that mobile.js code here to make the baseline clear:

 * JavaScript for Special:Preferences: mobileLayout.
( function () {
	 * Adds a ToggleSwitchWidget to control each checkboxWidget
	 * Hides each checkboxWidget
	function insertToggles() {
		var checkboxes = document.querySelectorAll( 'span.oo-ui-checkboxInputWidget' ); checkboxes, function ( checkboxWidget ) {
			var checkboxInput = checkboxWidget.querySelector( 'input' );
			var toggleSwitchWidget = new OO.ui.ToggleSwitchWidget( {
				value: checkboxInput.checked,
				disabled: checkboxInput.disabled
			} );
			toggleSwitchWidget.on( 'change', function ( value ) {
				toggleSwitchWidget.setValue( value );
				checkboxInput.checked = value;
			} );
			checkboxWidget.insertAdjacentElement( 'afterend', toggleSwitchWidget.$element[ 0 ] );
			checkboxWidget.classList.add( 'hidden' );
		} );
	$( function () {
		function initDialogs() {
			var options, windowManager, preferencesForm, prefOptionsContainer, prefContent, prefFormWrapper;
			options = OO.ui.infuse( document.querySelector( '.mw-mobile-preferences-container' ) );
			windowManager = new OO.ui.WindowManager();
			preferencesForm = document.querySelector( '#mw-prefs-form' );
			prefOptionsContainer = document.querySelector( '#mw-prefs-container' );
			prefFormWrapper = document.querySelector( '.mw-htmlform-ooui-wrapper' );

			function showContent( element ) {
				prefContent = document.querySelector( '#' + element.elementId + '-content' );
				prefContent.classList.remove( 'mw-prefs-hidden' );
				prefOptionsContainer.classList.add( 'mw-prefs-hidden' );
				prefOptionsContainer.removeAttribute( 'style' );
				preferencesForm.insertBefore( prefContent, preferencesForm.firstChild );

				function PrefDialog( config ) { this, config );

				OO.inheritClass( PrefDialog, OO.ui.Dialog ); = element.elementId;
				PrefDialog.static.escapable = false;
				PrefDialog.prototype.initialize = function () { this );
					this.content = new OO.ui.PanelLayout( { padded: true, expanded: true } );
					this.$body.append( preferencesForm );

				PrefDialog.prototype.getBodyHeight = function () {
					return this.content.$element.outerHeight( true );

				var prefDialog = new PrefDialog( { size: 'full' } );

				$( document.body ).append( windowManager.$element );
				windowManager.addWindows( [ prefDialog ] );
				windowManager.openWindow( prefDialog );

				if ( prefDialog.isOpening() ) {
					document.querySelector( '#mw-mf-viewport' ).classList.add( 'hidden' );

			options.items.forEach( function ( element ) {
				document.querySelector( '#' + element.elementId ).addEventListener( 'click', function () {
					showContent( element );
				} );

				var backButtonId = '#' + element.elementId + '-back-button';
				document.querySelector( backButtonId ).addEventListener( 'click', function () {
					prefContent.classList.add( 'mw-prefs-hidden' );
					prefOptionsContainer.classList.remove( 'mw-prefs-hidden' );
					prefFormWrapper.insertBefore( preferencesForm, prefFormWrapper.firstChild );
					document.querySelector( '#mw-mf-viewport' ).classList.remove( 'hidden' );
				} );
			} );
	} );
}() );

Test wiki created on Patch demo by JSherman (WMF) using patch(es) linked to this task:

Test wiki created on Patch demo by JSherman (WMF) using patch(es) linked to this task:

Just to follow up on a performance improvement that I failed to mention:
although the function run time is improved by a numerically significant (~21x) but imperceptible amount (~15.5ms), the total blocking time on the page offers a genuine improvement:

  • original TBT: 3607.35ms
  • optimized TBT: 955.20ms

Out of curiosity, I went and measured production en, which still didn't have the modals at time of measurement
TBT: 1010.70

image.png (1×515 px, 100 KB)

To me that would indicate that with optimization, we've made preferences work using the dialog as originally intended, dropped custom our implementation to make it more maintainable, and held (or slightly improved) performance.

Okay, I've gone ahead and implemented much of the design spec from T317110 where it was straightforward to do with CSS.
I also got some feedback from @Jdlrobson that using the close icon would be more consistent with other features. This actually makes a lot of sense for a modal dialog, so I went rogue and made the change. The spec wasn't explicit about the heading dimensions, so I made them match other areas more closely, eg.

I adjusted things to keep the spacing between the dialog body and dialog head within spec.

Test wiki created on Patch demo by JSherman (WMF) using patch(es) linked to this task:

Test wiki created on Patch demo by JSherman (WMF) using patch(es) linked to this task:

Test wiki created on Patch demo by JSherman (WMF) using patch(es) linked to this task:

This is looking great, thank you for addressing some of the submenu design elements @jsn.sherman!

I encountered a bug with the checkboxes, where they showed up alongside or instead of the toggles:

Screenshot 2022-12-09 at 10.17.59.png (1×730 px, 121 KB)

Unfortunately I can't figure out how to reproduce this at the moment. It happened after I saved a change to pronouns in the section above and navigated back to User Profile, but despite attempting all manner of combinations of settings changes and section clicks I haven't been able to make it happen again.

Happily I'm able to reproduce this as well as an intermittent save button issue that Derrick identified. I think the issue is that the modal code occasionally plucks things out of the dom too early. I should be able to shuffle around when things are happening to accommodate it.

Test wiki created on Patch demo by JSherman (WMF) using patch(es) linked to this task:


I have verified the Preferences Submenus are displaying and functioning per the Acceptance Criteria listed in the ticket Description (note: the Acceptance Criteria mentions "Clicking the Back arrow should return users to the top of the Preferences main menu Acceptance Criteria", the Back arrow/left arrow has been changed to an X but still functions as originally intended).

Below are some screenshots of the Preferences Submenus with various devices:

image.png (889×647 px, 171 KB)
image.png (884×667 px, 187 KB)
image.png (881×451 px, 254 KB)
image.png (886×458 px, 252 KB)
image.png (579×916 px, 220 KB)
image.png (576×911 px, 221 KB)
image.png (882×483 px, 150 KB)
image.png (882×435 px, 148 KB)

@Djackson-ctr thanks for your work on this; I appreciate the quick turnaround!

@Samwalton9 you might do another quick check on the latest patchdemo to verify you don't see that checkbox issue pop up.
I'm going to leave this in the queue until Monday just to give a little more time for feedback.

Test wiki created on Patch demo by JSherman (WMF) using patch(es) linked to this task:

Change 863394 merged by jenkins-bot:

[mediawiki/core@master] Optimize mobile prefs modals

Final note on performance: We did end up walking back some of the optimizations that seemed to be causing us to run ahead of other js that needs to run first.
InitDialogs: 1.69ms
TBT: 997.14ms

image.png (545×620 px, 52 KB)

That's still a ~10x speedup for the function, and a ~3.5x speedup for total blocking time