Fix: welcome dialog reappears on every page when the admin is served from an origin that differs from site_url#310
Merged
Conversation
… prevent reappearance
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The first-run "Welcome to Desktop Mode" dialog is meant to appear once and
stay gone after you dismiss it. Instead, on sites where the admin is browsed
from an origin that differs from WordPress's stored
site_url— an HTTPS edgein front of an
http://-configured site, awwwvs non-wwwmismatch, areverse proxy, or a domain-mapped multisite — it re-appeared on every
classic-admin page load and could never be dismissed.
Root cause: the dialog persisted its dismissal by POSTing to the absolute
rest_url()URL, which is pinned tosite_url(). When the page itself isserved from a different origin, that POST is cross-origin, and when the
schemes differ (HTTPS page → HTTP request) it is mixed active content,
which browsers hard-block per the W3C Mixed Content spec. The request's
.catch(function(){})swallowed the block silently, theactivation-welcomeslug was never written to the
desktop_mode_seen_introsuser meta, and thegate kept rendering the dialog.
The fix re-issues the dismissal (and the "Enable it now" AJAX call) to the
origin the page was actually loaded from, where the request is same-origin
and the credentials are valid.
Root cause in detail
includes/welcome-dialog.phpbuilt its endpoints with:Both resolve to the stored
site_url()origin. When the admin is browsed froma different origin:
site_urlis HTTP, the POST is mixed activecontent → the browser blocks it unconditionally. (A cross-origin
credentialed POST is likewise blocked when the origins differ.)
fetch(...).catch(function(){})swallows the failure with no surface.desktop_mode_should_show_welcome_dialog()keeps returning
true→ the dialog renders on every page.The gate itself and the REST/persistence layer were always correct — verified
end-to-end (a request with a valid cookie + nonce + JSON body returns
200and persists the slug). The only broken link was the client-side delivery
origin.
This does not reproduce on a correctly-configured single-origin install, where
window.location.origin === site_url, so the absolute URL is alreadysame-origin — which is why a healthy site never shows the dialog twice.
The fix
includes/welcome-dialog.php(inline dialog script):sameOrigin( url )helper rebuilds any absolute URL ontowindow.location.origin, preserving the path and query. In the normalsingle-origin case this is a no-op; under an origin mismatch it keeps the
request same-origin.
persist()now sends the dismissal tosameOrigin( cfg.url )and prefersnavigator.sendBeacon, which:"Enable it now" triggers (no
keepalivecaveats), andThe
wp_restnonce rides along as?_wpnonce=(REST cookie auth reads itfrom
$_REQUEST), and theapplication/jsonBlob type lets the REST serverparse the
slugbody param. Akeepalivefetch()remains as a fallbackfor browsers without
sendBeacon.sameOrigin( cfg.ajaxUrl ).Why the credentials still validate same-origin
browsing origin regardless of which port/scheme the request uses.
wp_restnonce is session-bound, not origin-bound, so it validatesregardless of which origin the request lands on.
wordpress_logged_in_<COOKIEHASH>) derives fromsite_url()and is therefore stable across access origins.Testing
site_url(e.g. an HTTPS edge in front of an HTTP-configured site), DesktopMode off, and the intro reset — dismiss with "Got it" and navigate between
admin screens. The dialog now stays gone.
test_dismissal_is_sent_same_origintoTests_DesktopMode_WelcomeDialog, asserting the rendered dialog routes thedismissal through
window.location.originand usessendBeacon— aregression guard against reverting to the cross-origin POST. Full class is
green (6/6).
php -lclean;npm run buildgreen.Risk / compatibility
client-side delivery hardening inside the existing welcome dialog.
window.location.originalready equals thesite_urlorigin there, sosameOrigin()returns the same URL./intros) is unaffected.Files changed
includes/welcome-dialog.php—sameOrigin()helper;sendBeacon+same-origin delivery for dismissal and the enable-now AJAX call.
tests/phpunit/tests/welcomeDialog.php— regression test..gitignore— ignore/.scratch/