Skip to content

Fix modal trigger not opening modals on MediaWiki 1.43+#75

Merged
malberts merged 1 commit into
masterfrom
fix/modal-mw143
May 21, 2026
Merged

Fix modal trigger not opening modals on MediaWiki 1.43+#75
malberts merged 1 commit into
masterfrom
fix/modal-mw143

Conversation

@malberts
Copy link
Copy Markdown
Collaborator

@malberts malberts commented May 20, 2026

Partial fix for #68 — addresses the modal symptom only. The tooltip and popover symptoms of #68 will follow in a separate PR.

The deferred-content mechanism that historically passed modal markup from the parser hook to OutputPageParserOutput via ParserOutput::setExtensionData no longer works under MediaWiki 1.43+ — the ParserOutputAccess lifecycle changes between 1.39 and 1.43 mean the set-side and get-side operate on different ParserOutput instances. The modal trigger renders, but the modal container never reaches the page.

This PR switches ModalBuilder::parse() to inline emission: trigger HTML and modal container HTML are returned concatenated. The modal container is position: fixed and addressed via data-target="#id", so its DOM placement is irrelevant; it can sit as a sibling of its trigger in the article body. Block-in-inline placements are handled cleanly by RemexHtml (default since MW 1.36).

Scope

  • 5 source files: ModalBuilder.php, ParserOutputHelper.php, BootstrapComponents.php, Hooks/OutputPageParserOutput.php, ApplicationFactory.php
  • 4 parallel test files updated for the new return-string shape

Net: +41 / −123 lines. No JS, CSS, or extension.json changes — purely the deferred-content rewrite.

Two now-unused fields (ModalBuilder::$parserOutputHelper, OutputPageParserOutput::$parserOutput) and their associated constructor parameters / getter are marked @deprecated rather than removed, so this stays a SemVer-clean maintenance fix. They will be dropped in the next major release.

Verification

Click the trigger on a wikitext-<bootstrap_modal> page on two stacks (MW 1.43.8 + Chameleon BS4 baseline; MW 1.39.13 + Vector), with and without the patch:

Unpatched (master tip) Patched (this PR)
MW 1.43.8 (where #68 manifests) pr-03-mw143-unpatched-broken
click does nothing — modal div absent from DOM
pr-04-mw143-patched-modal-open

modal opens
MW 1.39.13 (oldest supported) pr-05-mw139-unpatched-modal-open
modal opens — bug doesn't manifest on this MW
pr-02-mw139-patched-modal-open
modal still opens — no regression

A Vector-only CSS override in BC suppresses the modal-backdrop on Vector (ext.bootstrapComponents.modal.vector-fix.css), so the bottom-row 1.39 screenshots show no dim overlay while the top-row 1.43/Chameleon shots do — that's pre-existing skin-specific styling, unchanged by this PR.

Tests

PHPUnit unit suite (--group mediawiki-databaseless):

Tests Failures
Baseline (master tip) 337 6
Patched 338 6

Identical 6 failures in both runs — all in BootstrapComponentsServiceTest and ImageModalTriggerTest (manual thumbnail variants). All pre-existing, unrelated to the modal-builder path. (+1 test count comes from splitting OutputPageParserOutputTest::testHookOutputPageParserOutput into the two assertions that survive the inline-emission refactor.)

Why a standalone fix

PR #74 ports BC to Bootstrap 5 and contains an equivalent modal-fix commit. Splitting this fix out lets BC 5.2.x ship a 5.2.3 maintenance release with just the MW 1.43+ unblocker for sites not yet ready for the BS5 migration.

The fix is BS-version-independent: under both BS4 and BS5 the modal container is position: fixed, so inline emission works identically on either side.

🤖 Generated with Claude Code

The deferred-content mechanism (ParserOutput::setExtensionData on the
parse side, ParserOutput::getExtensionData in the OutputPageParserOutput
hook) no longer preserves data across the parser hook -> render hook
transition under MediaWiki 1.43's ParserOutputAccess lifecycle changes.
The set-side and get-side operate on different ParserOutput instances,
so the modal container markup never reaches the page. The modal trigger
still renders but has nothing to open.

Switch to inline emission: ModalBuilder::parse() returns the trigger
HTML and the modal container HTML concatenated. The modal container is
position: fixed and addressed via data-target="#id", so its DOM
placement is no longer significant. This works identically on every
supported MediaWiki version because the underlying Bootstrap behaviour
is the same and RemexHtml (default since MW 1.36) cleanly handles a
block element inside a wikitext paragraph.

Removes the now-unused ParserOutputHelper::injectLater() helper and
the EXTENSION_DATA_DEFERRED_CONTENT_KEY constant. Updates the four
parallel test files (ModalBuilderTest, ImageModalTest, ModalTest,
OutputPageParserOutputTest) to assert the new concatenated return
shape and drop the deferred-content mock expectations.

Verified on a MW 1.39 + Vector + BC stack: modal opens identically
pre- and post-patch (no regression on the oldest supported MW
version). PHPUnit unit suite stays green relative to baseline; the
6 pre-existing ImageModalTriggerTest failures are present in both
runs and unrelated to this change.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@malberts malberts marked this pull request as ready for review May 21, 2026 09:28
@malberts malberts merged commit eb2ceda into master May 21, 2026
6 of 8 checks passed
malberts added a commit that referenced this pull request May 21, 2026
The intended-but-broken sparse-loading scheme had two gaps:

1) Only ImageModal::177 called BootstrapComponentsService::registerComponentAsActive(),
   so HooksHandler::onParserAfterParse's foreach over getActiveComponents() never
   iterated tooltip / popover / carousel / modal / etc. — their per-component
   .fix modules were never queued, JS or CSS.
2) The foreach itself only called $parserOutput->addModuleStyles(), so even for
   ImageModal the JS half of any module with a 'scripts' entry was unreachable
   through that path.

Close both gaps:

- AbstractComponent::parseComponent() now calls registerComponentAsActive() right
  after opening the nesting controller. All 11 explicit-syntax components
  (Accordion, Alert, Badge, Button, Card, Carousel, Collapse, Jumbotron, Modal,
  Popover, Tooltip) inherit this via the single chokepoint. Fetched via
  MediaWikiServices, matching the service-locator pattern already used by
  LuaLibrary, CarouselGallery, and ImageModal in the codebase.
- HooksHandler::onParserAfterParse foreach now calls both addModuleStyles() and
  addModules() per module. Style-only modules (modal.fix etc.) treat the
  addModules call as a no-op; modules with a 'scripts' entry (tooltip.fix,
  popover.fix, carousel.fix) get their init JS loaded.

Net effect: a page with only a tooltip loads only tooltip.fix. A page with
nothing loads no BC init modules at all. Sparse loading is restored as
originally intended.

Verified on a fresh MW 1.39.17 + Vector + BC stack and on a MW 1.43.8 +
Chameleon BS4 + BC stack: tooltip and popover both fire; carousel.fix stays
mw.loader state 'registered' on pages without a carousel; modal still opens
(PR #75's inline-emission fix is orthogonal and unaffected). PHPUnit unit
suite green relative to baseline (6 pre-existing failures unchanged).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant