Public bug reported:
SUMMARY
SearchResultsView._unregisterProvider() does not check the result of
indexOf():
_unregisterProvider(provider) {
const index = this._providers.indexOf(provider);
this._providers.splice(index, 1);
if (provider.display)
provider.display.destroy();
}
If the passed provider is not in this._providers, indexOf() returns -1 and
splice(-1, 1) silently removes the LAST provider in the array - an arbitrary,
unrelated provider. Since SearchController.removeProvider() is a thin wrapper
around this method, the public API documented for extensions (gjs.guide) is
affected as well.
This is easy to trigger because _registerProvider() can silently refuse to
register a provider:
_registerProvider(provider) {
provider.searchInProgress = false;
// Filter out unwanted providers.
if (provider.appInfo &&
!this._parentalControlsManager.shouldShowApp(provider.appInfo))
return;
this._providers.push(provider);
this._ensureProviderDisplay(provider);
}
The caller has no way to know registration was refused, keeps its provider
reference, and will later call removeProvider()/_unregisterProvider() with a
provider that was never registered - killing an innocent provider each time.
REAL-WORLD IMPACT (how this was found)
On Ubuntu 26.04, the [email protected] extension registers a
provider whose appInfo is the xdg default browser. Google Chrome's deb package
ships com.google.Chrome.desktop with NoDisplay=true (an alias kept for XDG
portal app-ID matching), and "xdg-settings get default-web-browser" can
legitimately point at it. In that configuration:
1. On every extension enable (login/unlock), registration is silently refused
(shouldShowApp() -> appInfo.should_show() -> false), but the extension
keeps its reference.
2. On every extension disable (screen lock/suspend), _unregisterProvider() is
called with the never-registered provider -> splice(-1, 1) destroys the
last provider in the list.
3. Remote providers are resurrected by _reloadRemoteProviders() on
installed-changed (e.g. snap refreshes rewriting .desktop files), but the
AppSearchProvider ('applications') is only registered once, in the
SearchResultsView constructor. Once lock/unlock cycles between two reloads
eat through the tail of the list and reach it, application search is dead
for the rest of the session - the overview shows "No results" for every
app, with nothing in the logs.
The stochastic race between suspend/lock cycles and installed-changed reloads
makes the breakage look random. Only a re-login fixes it. (The extension's
side of the bug - keeping a stale reference - is filed separately against
gnome-shell-ubuntu-extensions: <LINK-BUG-EXTENSION>)
MINIMAL REPRODUCER
In Looking Glass (Alt+F2, "lg"), on any session:
Main.overview.searchController._searchResults._providers.map(p =>
p.id).join(' | ')
// note the list, then:
Main.overview.searchController.removeProvider({})
Main.overview.searchController._searchResults._providers.map(p =>
p.id).join(' | ')
// -> the last provider has been silently removed and its display destroyed
SUGGESTED FIX
_unregisterProvider(provider) {
const index = this._providers.indexOf(provider);
if (index < 0)
return; // or log a warning
this._providers.splice(index, 1);
if (provider.display)
provider.display.destroy();
}
Additionally, consider making _registerProvider()'s refusal observable (return
a boolean, or log), so callers don't end up holding references to providers
that were never registered.
** Affects: gnome-shell (Ubuntu)
Importance: Undecided
Status: New
--
You received this bug notification because you are a member of Ubuntu
Bugs, which is subscribed to Ubuntu.
https://bugs.launchpad.net/bugs/2161808
Title:
SearchResultsView._unregisterProvider() removes an arbitrary provider
when called with an unregistered provider (splice(-1, 1))
To manage notifications about this bug go to:
https://bugs.launchpad.net/ubuntu/+source/gnome-shell/+bug/2161808/+subscriptions
--
ubuntu-bugs mailing list
[email protected]
https://lists.ubuntu.com/mailman/listinfo/ubuntu-bugs