From c9e47a5460f45de48ee452a1650857aa317f3125 Mon Sep 17 00:00:00 2001 From: Ashish Anant Date: Sat, 3 Oct 2026 21:41:44 +0530 Subject: [PATCH] Upgrade/Install: Decode character references in Shiny Updates console output. Upgrader debug messages logged to the browser console were passed through `wp.sanitize.stripTagsAndEncodeText()`, which returns HTML-escaped text. As the console displays plain text, messages such as "Unpacking the package…" were shown with the raw character reference. This strips tags and decodes character references using `DOMParser` before logging, and skips non-string messages. Includes QUnit tests for `wp.updates.ajaxAlways()`, and fixes the `_wpUpdatesItemCounts` test fixture so that `updates.js` can be loaded in the QUnit suite. Fixes #59890. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_014nJenDidRzoohF8JCEKbZ9 --- src/js/_enqueues/wp/updates.js | 13 +++- tests/qunit/fixtures/updates.js | 10 ++- tests/qunit/index.html | 3 + .../qunit/wp-admin/js/updates-ajax-always.js | 62 +++++++++++++++++++ 4 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 tests/qunit/wp-admin/js/updates-ajax-always.js diff --git a/src/js/_enqueues/wp/updates.js b/src/js/_enqueues/wp/updates.js index 59d0ef2e6b1cb..7055a91cc1f34 100644 --- a/src/js/_enqueues/wp/updates.js +++ b/src/js/_enqueues/wp/updates.js @@ -347,8 +347,17 @@ if ( 'undefined' !== typeof response.debug && window.console && window.console.log ) { _.map( response.debug, function( message ) { - // Remove all HTML tags and write a message to the console. - window.console.log( wp.sanitize.stripTagsAndEncodeText( message ) ); + if ( 'string' !== typeof message ) { + return; + } + + /* + * Remove all HTML tags and decode character references, as the + * console displays plain text rather than HTML. + */ + window.console.log( + new DOMParser().parseFromString( message, 'text/html' ).body.textContent + ); } ); } }; diff --git a/tests/qunit/fixtures/updates.js b/tests/qunit/fixtures/updates.js index 7e31370814880..3a65a35f1b0ff 100644 --- a/tests/qunit/fixtures/updates.js +++ b/tests/qunit/fixtures/updates.js @@ -59,5 +59,13 @@ window._wpUpdatesSettings = { }; window._wpUpdatesItemCounts = { plugins: {}, - totals: {} + totals: { + counts: { + plugins: 2, + themes: 0, + wordpress: 0, + translations: 0, + total: 2 + } + } }; diff --git a/tests/qunit/index.html b/tests/qunit/index.html index 82524daa01227..37e6c843ea18a 100644 --- a/tests/qunit/index.html +++ b/tests/qunit/index.html @@ -84,6 +84,7 @@ + @@ -97,6 +98,7 @@ + @@ -157,6 +159,7 @@ + diff --git a/tests/qunit/wp-admin/js/updates-ajax-always.js b/tests/qunit/wp-admin/js/updates-ajax-always.js new file mode 100644 index 0000000000000..f133454b564a1 --- /dev/null +++ b/tests/qunit/wp-admin/js/updates-ajax-always.js @@ -0,0 +1,62 @@ +/* global QUnit, wp, sinon */ + +QUnit.module( 'wp.updates.ajaxAlways', { + beforeEach: function() { + sinon.stub( window.console, 'log' ); + sinon.stub( wp.updates, 'queueChecker' ); + }, + afterEach: function() { + window.console.log.restore(); + wp.updates.queueChecker.restore(); + wp.updates.ajaxLocked = false; + } +} ); + +QUnit.test( 'Debug messages are logged with character references decoded', function( assert ) { + wp.updates.ajaxAlways( { + debug: [ 'Downloading installation package from https://example.org/plugin.zip…' ] + } ); + + assert.ok( window.console.log.calledOnce, 'One message should be logged.' ); + assert.strictEqual( + window.console.log.getCall( 0 ).args[0], + 'Downloading installation package from https://example.org/plugin.zip…', + 'HTML tags should be stripped and the ellipsis decoded.' + ); +} ); + +QUnit.test( 'Debug messages decode named character references', function( assert ) { + wp.updates.ajaxAlways( { + debug: [ 'Tom & Jerry <3' ] + } ); + + assert.strictEqual( window.console.log.getCall( 0 ).args[0], 'Tom & Jerry <3' ); +} ); + +QUnit.test( 'Debug messages do not execute markup', function( assert ) { + window.updatesAjaxAlwaysXss = false; + + wp.updates.ajaxAlways( { + debug: [ 'Done' ] + } ); + + assert.strictEqual( window.console.log.getCall( 0 ).args[0], 'Done' ); + assert.strictEqual( window.updatesAjaxAlwaysXss, false, 'Event handlers in debug messages should not run.' ); + + delete window.updatesAjaxAlwaysXss; +} ); + +QUnit.test( 'Non-string debug messages are skipped', function( assert ) { + wp.updates.ajaxAlways( { + debug: [ null, 123, { foo: 'bar' }, 'Done.' ] + } ); + + assert.ok( window.console.log.calledOnce, 'Only the string message should be logged.' ); + assert.strictEqual( window.console.log.getCall( 0 ).args[0], 'Done.' ); +} ); + +QUnit.test( 'Nothing is logged without debug messages', function( assert ) { + wp.updates.ajaxAlways( {} ); + + assert.notOk( window.console.log.called ); +} );