Opened 4 years ago
Last modified 5 weeks ago
#55344 assigned feature request
Resources for hidden widgets are loaded on WP dashboard
| Reported by: |
|
Owned by: | |
|---|---|---|---|
| Milestone: | Future Release | Priority: | normal |
| Severity: | normal | Version: | |
| Component: | Administration | Keywords: | needs-patch |
| Focuses: | javascript | Cc: |
Description
When plugins are placing widgets on the WordPress dashboard and these are hidden via the Screen Options the resources of those widgets are still loaded.
In the Sources tab of the browser console this can easily be checked. I've noticed this with multiple plugins, so it looks like a core bug.
It would save resources and increase performance of the dashboard if hidden items are not loaded.
I've asked in the #performance Slack channel, where I was asked to create a ticket to report this as a bug.
Change History (13)
#1
@
4 years ago
- Keywords needs-patch added
- Owner set to adamsilverstein
- Status changed from new to accepted
#2
@
4 years ago
In addition there's also a closed state of a dashboard widget. In that case the content doesn't need to be rendered until it's opened. And opened widgets that are below the fold, might even be loaded only after scrolling with lazy loading.
This ticket was mentioned in Slack in #core by josklever. View the logs.
4 years ago
This ticket was mentioned in Slack in #core by josklever. View the logs.
4 years ago
This ticket was mentioned in Slack in #core by josklever. View the logs.
4 years ago
#6
@
4 years ago
- Focuses javascript added
- Milestone changed from Awaiting Review to Future Release
- Version 5.9.1 deleted
#7
@
3 years ago
Now this is interesting.
I agree with you Jos that is is at the very least "strange" that those items are loaded.
However on the other hand it is for many many years ( which is not a good reason however to NOT take up this ticket ) the case that dashboard widgets are instantly shown/hidden with the screen options.
Applying what you mention would either ( in my backender-opinion ) require:
A) fully reload the dashboard page
or
B) use AJAX/json to load widgets.
In my experience loading javascript that was not present before trigging XHR responses is very very tricky. So that would ( to assure 100% working guarantee of all widgets out there ) only leave option A, am I correct on this ?
That means that you can only instant hide a currently shown widget just like it is now, but you _cannot_ instantly show a previously hidden widget as you will need a full page reload.
#8
@
3 years ago
- Focuses performance removed
This ticket is not performance related so I am removing the tag.
#9
@
3 years ago
I beg to differ with you on this. Yes it is minor, but still it impacts CPU (server and client) so yes -> performance.
#10
@
3 years ago
- Type changed from defect (bug) to feature request
As @adamsilverstein mentioned, the Screen Options hides the widgets using JS, and doesn't alter the loading of any JS scripts, or styles. This is how the widget hiding feature is implemented.
#11
@
11 months ago
- Owner adamsilverstein deleted
- Status changed from accepted to assigned
However on the other hand it is for many many years ( which is not a good reason however to NOT take up this ticket ) the case that dashboard widgets are instantly shown/hidden with the screen options.
This is a good point. while we could probably devise a way to dynamically load a widget activated in screen settings, a reliable solution would likely require a refresh. Regardless though, the behavior for users would change and might be considered a regression. Given the level of effort and the mixed outcome, I'm not convinced we should invest time to work on this change.
I will leave the ticket open. I am removing my self assignment though as I don't plan to work on this ticket.
This ticket was mentioned in Slack in #core-performance by adamsilverstein. View the logs.
5 months ago
#13
@
5 weeks ago
Thanks for the report and to everyone who weighed in. The problem is real and fixable without a major architectural change. This comment proposes a concrete approach.
How the current system works
The Screen Options toggle is fully client-side: postbox.js calls $postbox.hide() or $postbox.show(), then persists the new visibility state via an AJAX call to wp-ajax.php?action=closed-postboxes. The server stores the hidden widget IDs in metaboxhidden_dashboard user meta — without a page reload.
On the next full page load, get_hidden_meta_boxes( $screen ) reads that user meta and returns the list of hidden widget IDs. This list is already used server-side (e.g. to render the Screen Options checkboxes), but is never consulted before plugins enqueue their assets.
The typical plugin pattern looks like this:
function my_plugin_dashboard_setup() {
wp_enqueue_script( 'my-plugin-dashboard' ); // ← always runs, hidden or not
wp_add_dashboard_widget( 'my_plugin_widget', 'My Widget', 'my_plugin_widget_render' );
}
add_action( 'wp_dashboard_setup', 'my_plugin_dashboard_setup' );
There is no supported way for a plugin to say "only enqueue this if my widget is visible." The bug exists in core itself too — the Site Health widget is the only core widget that explicitly enqueues separate assets, and it does so unconditionally — but the primary audience for a fix is plugin authors.
UX constraint
If we skip enqueueing scripts for a hidden widget, and the user then un-hides it via Screen Options without a page reload, the widget's JS will be absent until the next full load.
This is the same trade-off that applies to all other meta-box screens in WordPress. A user toggling Screen Options on any post edit screen faces the same behaviour today. Comment #7 on this ticket already acknowledges this, and it is the accepted outcome — no additional UX change or page-reload prompt is needed.
Distinguishing hidden vs. closed
Two separate user-meta keys exist:
metaboxhidden_dashboard— widget unchecked in Screen Options → removed from the DOM entirelyclosedpostboxes_dashboard— widget collapsed (title bar only, content still in DOM)
The fix should apply only to hidden widgets. Collapsed widgets are still present in the DOM and their scripts may be needed when the user expands them without a page reload. get_hidden_meta_boxes() returns exactly the hidden list.
Proposed approaches
Approach A — New $enqueue_callback parameter on wp_add_dashboard_widget() (recommended)
Add an optional 8th parameter. The function checks the hidden list once and only invokes the callback when the widget is visible. All callers — core and plugins — can opt in with a single-line change.
/**
* @since X.X.0
*
* @param callable|null $enqueue_callback Optional. Callback that enqueues
* scripts/styles for this widget. Only called when the widget is not
* hidden via Screen Options. Default null.
*/
function wp_add_dashboard_widget(
$widget_id,
$widget_name,
$callback,
$control_callback = null,
$callback_args = null,
$context = 'normal',
$priority = 'core',
$enqueue_callback = null // ← new, backward-compatible
) {
global $wp_dashboard_control_callbacks;
$screen = get_current_screen();
if ( is_callable( $enqueue_callback ) ) {
$hidden = get_hidden_meta_boxes( $screen );
if ( ! in_array( $widget_id, $hidden, true ) ) {
call_user_func( $enqueue_callback );
}
}
// ... rest of existing function unchanged ...
}
Plugin migration is minimal:
// Before:
function my_plugin_dashboard_setup() {
wp_enqueue_script( 'my-plugin-dashboard' );
wp_add_dashboard_widget( 'my_plugin_widget', 'My Widget', 'my_plugin_widget_render' );
}
// After:
function my_plugin_dashboard_setup() {
wp_add_dashboard_widget(
'my_plugin_widget',
'My Widget',
'my_plugin_widget_render',
null, null, 'normal', 'core',
function() {
wp_enqueue_script( 'my-plugin-dashboard' );
}
);
}
Pros: Centralised, consistent, documented, backward-compatible (default null means existing calls are untouched).
Cons: Requires plugin authors to update their code to benefit; does not automatically fix existing plugins.
Approach B — Dynamic action hook
Instead of a new parameter, fire a targeted action inside wp_add_dashboard_widget() only when the widget is visible:
if ( ! in_array( $widget_id, get_hidden_meta_boxes( $screen ), true ) ) {
/**
* Fires when a dashboard widget's resources should be enqueued.
* Only fires if the widget is not hidden via Screen Options.
*
* @since X.X.0
*
* @param string $widget_id The widget ID.
*/
do_action( "wp_dashboard_widget_enqueue_{$widget_id}", $widget_id );
}
Plugin usage:
add_action( 'wp_dashboard_widget_enqueue_my_plugin_widget', function() {
wp_enqueue_script( 'my-plugin-dashboard' );
} );
Pros: Hook-based; no new function signature; trivial for plugins to adopt.
Cons: Dynamic hook name is harder to discover and grep for; does not help if enqueue calls happen before wp_add_dashboard_widget() is called.
What must not break
| Area | Concern | Safe? |
|---|---|---|
| First visit (no user pref set) | get_hidden_meta_boxes() returns [] by default for the dashboard screen → all resources enqueued as today | ✓ |
| Toggle hide → show without reload | Scripts absent until next full load — same as all other meta-box screens | ✓ (accepted) |
| Collapsed (closed) widgets | closedpostboxes_dashboard ≠ metaboxhidden_dashboard; collapsed widget scripts correctly loaded | ✓ |
Existing 7-argument callers of wp_add_dashboard_widget() | New 8th param defaults to null; all existing calls unchanged | ✓ |
wp_dashboard_setup action callbacks that enqueue before calling wp_add_dashboard_widget() | Not automatically fixed; plugin authors need to move enqueue into the callback | ✗ (known limitation, same for Approach B) |
| Multisite per-user prefs | metaboxhidden_dashboard is per-user-per-blog; get_hidden_meta_boxes() already handles this | ✓ |
| Widget ordering / drag-drop | Sortable postboxes do not affect the hidden meta | ✓ |
Tests needed
File: tests/phpunit/tests/admin/includesTemplate.php (alongside the existing test_wp_add_dashboard_widget)
test_wp_add_dashboard_widget_enqueue_callback_called_when_visible
Widget ID not inmetaboxhidden_dashboard. Assert the enqueue callback is invoked.
test_wp_add_dashboard_widget_enqueue_callback_not_called_when_hidden
Add the widget ID tometaboxhidden_dashboarduser meta. Assert the enqueue callback is not invoked.
test_wp_add_dashboard_widget_enqueue_callback_not_called_when_closed_but_not_hidden
Add the widget ID toclosedpostboxes_dashboardbut notmetaboxhidden_dashboard. Assert the callback IS invoked (collapsed ≠ hidden).
test_wp_add_dashboard_widget_null_enqueue_callback_no_error
Call with defaultnullcallback; assert no error is produced (backward-compatibility regression test).
test_wp_add_dashboard_widget_enqueue_callback_called_when_no_user_pref
Deletemetaboxhidden_dashboarduser option entirely (first-visit scenario). Assert callback IS invoked.
Recommendation
Approach A ($enqueue_callback parameter) is the better long-term API — it keeps the enqueue logic co-located with the widget registration, is self-documenting, and is easy to validate in tests. Approach B is simpler to adopt but less discoverable.
Happy to put together a patch for either approach.
Hey @josklever - thanks for reporting this issue. I can confirm that the screen options controls don't do anything to alter loading of associated scripts. Guessing this has been a bug for a long time, since the Dashboard was introduced.