Skip to content

Commit cf12f86

Browse files
committed
Address code review findings
- Escape JSON output with JSON_HEX_TAG to prevent XSS via </script> in query text. - Add timeout to shadow root polling (max 10 seconds). - Debounce MutationObserver to reduce overhead during re-renders. - Add missing await on toBeVisible() in QM 3.x test. - Add toPass() retry for SQLite toggle check in QM 4.0 test. - Document QM source reference for admin bar query filtering.
1 parent 4a13cdd commit cf12f86

File tree

3 files changed

+29
-16
lines changed

3 files changed

+29
-16
lines changed

packages/plugin-sqlite-database-integration/integrations/query-monitor/collector.php

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,11 @@ public function output() {
7777
return;
7878
}
7979

80-
// Re-index: QM's collector may skip some $wpdb->queries entries
81-
// (e.g., admin bar queries), so we need to map our indices to
82-
// match the rows QM actually renders. QM iterates $wpdb->queries
83-
// sequentially and skips entries containing 'wp_admin_bar' in the
84-
// stack trace. We replicate that filtering here.
80+
// Re-index: QM's DB queries collector skips $wpdb->queries entries
81+
// where the stack trace contains 'wp_admin_bar'. We replicate that
82+
// filtering to align our row indices with QM's rendered rows.
83+
// See: QM_Collector_DB_Queries::process_db_object() in QM's
84+
// collectors/db_queries.php.
8585
global $wpdb;
8686
$mapped = array();
8787
$row_index = 0;
@@ -97,9 +97,11 @@ public function output() {
9797
}
9898

9999
// Output JSON data for the JS module.
100+
// JSON_HEX_TAG escapes < and > to \u003C and \u003E, preventing
101+
// a literal "</script>" in query text from breaking out of the tag.
100102
printf(
101103
'<script type="application/json" id="qm-sqlite-data">%s</script>',
102-
wp_json_encode( $mapped )
104+
wp_json_encode( $mapped, JSON_HEX_TAG )
103105
);
104106

105107
// Output the JS module inline.

packages/plugin-sqlite-database-integration/integrations/query-monitor/query-monitor-sqlite.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,13 @@
115115
injectSQLiteInfo( shadowRoot );
116116

117117
// Observe for future renders (panel switches, re-renders).
118+
// Debounce to avoid excessive work during rapid Preact re-renders.
119+
var debounceTimer;
118120
var observer = new MutationObserver( function() {
119-
injectSQLiteInfo( shadowRoot );
121+
clearTimeout( debounceTimer );
122+
debounceTimer = setTimeout( function() {
123+
injectSQLiteInfo( shadowRoot );
124+
}, 100 );
120125
} );
121126
observer.observe( shadowRoot, { childList: true, subtree: true } );
122127
}
@@ -128,10 +133,13 @@
128133
// detect attachShadow(), so we poll. QM's module script is deferred
129134
// and attaches the shadow root on DOMContentLoaded, which may fire
130135
// after our inline script's DOMContentLoaded handler.
136+
var pollCount = 0;
131137
var pollInterval = setInterval( function() {
132138
if ( container.shadowRoot ) {
133139
clearInterval( pollInterval );
134140
onShadowReady( container.shadowRoot );
141+
} else if ( ++pollCount > 200 ) {
142+
clearInterval( pollInterval );
135143
}
136144
}, 50 );
137145
}

tests/e2e/specs/query-monitor-plugin.test.js

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,17 @@ test.describe( 'Query Monitor plugin', () => {
7575
} ).toPass();
7676

7777
// Click the SQLite toggle button for the first query row.
78-
await container.evaluate( ( el ) => {
79-
const shadow = el.shadowRoot;
80-
const toggle = shadow.querySelector( '.qm-sqlite-toggle' );
81-
if ( ! toggle ) {
82-
throw new Error( 'SQLite toggle button not found' );
83-
}
84-
toggle.click();
85-
} );
78+
// The toggle is injected by a debounced MutationObserver, so retry.
79+
await expect( async () => {
80+
await container.evaluate( ( el ) => {
81+
const shadow = el.shadowRoot;
82+
const toggle = shadow.querySelector( '.qm-sqlite-toggle' );
83+
if ( ! toggle ) {
84+
throw new Error( 'SQLite toggle button not found' );
85+
}
86+
toggle.click();
87+
} );
88+
} ).toPass();
8689

8790
// Verify the SQLite query is displayed.
8891
await expect( async () => {
@@ -129,7 +132,7 @@ test.describe( 'Query Monitor plugin', () => {
129132

130133
// Check that the query is logged with SQLite information.
131134
await sqlCell.getByLabel( 'Toggle SQLite queries' ).click();
132-
expect(
135+
await expect(
133136
page
134137
.locator( '.qm-sqlite-query', {
135138
hasText:

0 commit comments

Comments
 (0)