Files
sousa-gecko/widget/tests/browser/browser_idle_service_mutter.js
T
Jens Stutte dc8812d7ce Bug 1962438 - Add caching logic and sync wait to UserIdleServiceMutter. r=Thinker
Any call to DBUS is async, but our current idle service abstraction expects
all calls to be sync. As changing the API would affect quite a lot of callers,
we can get away by spinning the event loop and having a timeout for DBUS only.

If the DBUS proxy cannot be created during probe, we fall back to
IDLE_SERVICE_XSCREENSAVER as before. Once probed successfully, poll errors
are transient and do not trigger a fallback.

Differential Revision: https://phabricator.services.mozilla.com/D281221
2026-06-30 07:17:18 +00:00

115 lines
4.6 KiB
JavaScript

/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Exercises the GTK user idle service. The service uses the
// org.gnome.Mutter.IdleMonitor DBus backend -- the cache-and-async-poll logic
// from bug 1962438 -- whenever that service is reachable on the session bus,
// i.e. on any GNOME session, X11 or Wayland, including the Wayland CI workers.
// Otherwise the backend is XScreenSaver and only the generic smoke assertions
// apply. The active backend cannot be queried directly, so we detect it from
// the service's own MOZ_LOG output rather than guessing from the desktop
// environment (XDG_CURRENT_DESKTOP does not survive into the test process under
// CI) or the window protocol (Mutter is used under XWayland too).
const { TestUtils } = ChromeUtils.importESModule(
"resource://testing-common/TestUtils.sys.mjs"
);
add_task(async function () {
const idle = Cc["@mozilla.org/widget/useridleservice;1"].getService(
Ci.nsIUserIdleService
);
// Backend-agnostic smoke checks.
const first = idle.idleTime;
Assert.equal(typeof first, "number", "idleTime is a number");
Assert.greaterOrEqual(first, 0, "idleTime is non-negative");
// Route the idle service's MOZ_LOG module to a file (its output is not
// visible to the console service); logging.config.sync flushes each line so
// the readbacks below see it. Enabled up front so it captures the poll used
// to detect the active backend.
const logBase = PathUtils.join(
Services.dirsvc.get("ProfD", Ci.nsIFile).path,
"idle_mutter_test.log"
);
const logFile = `${logBase}-main.${Services.appinfo.processID}.moz_log`;
await SpecialPowers.pushPrefEnv({
set: [
["logging.config.sync", true],
["logging.config.LOG_FILE", logBase],
["logging.nsIUserIdleService", 5],
],
});
registerCleanupFunction(() =>
IOUtils.remove(logFile, { ignoreAbsent: true })
);
async function logIncludes(needle) {
if (!(await IOUtils.exists(logFile))) {
return false;
}
return (await IOUtils.readUTF8(logFile)).includes(needle);
}
// Detect the active backend from its log: a Mutter poll logs
// "PollIdleTime() request", whereas the XScreenSaver backend logs
// "UserIdleServiceX11::PollIdleTime". One idleTime read triggers a poll.
void idle.idleTime;
await TestUtils.waitForCondition(
async () =>
(await logIncludes("PollIdleTime() request")) ||
(await logIncludes("UserIdleServiceX11")),
"idle service served a poll we can attribute to a backend",
100,
100
);
if (!(await logIncludes("PollIdleTime() request"))) {
info("Idle backend is not Mutter (XScreenSaver); skipping Mutter checks.");
return;
}
info("Mutter idle backend active; exercising the cache path.");
// Two reads within the fresh-cache window (kCacheFreshMs == 1000ms) are
// served from the cache without a new DBus poll, so they must be identical.
// This deterministically exercises the synchronous fresh-cache return path.
const a = idle.idleTime;
const b = idle.idleTime;
Assert.equal(b, a, "rapid successive reads return the same cached value");
// The fresh window must time out: once more than kCacheFreshMs has elapsed,
// the next read takes the stale branch, returns the cached value, and kicks
// off a background DBus refresh. Polling idleTime crosses that boundary
// without an arbitrary fixed delay.
await TestUtils.waitForCondition(
() => {
void idle.idleTime;
return logIncludes("returns cached (stale)");
},
"cache freshness timed out and took the stale branch",
100,
100
);
// The background refresh's async DBus reply lands and updates the cache.
await TestUtils.waitForCondition(
() => logIncludes("Async handler got"),
"background DBus refresh completed and re-cached the value",
100,
100
);
// Two paths are deliberately left untested here:
// - The cold "very stale" resync (cache older than kCacheStaleMs == 5000ms,
// which falls through to a synchronous wait). Every stale read above kicks
// a background refresh that re-stamps the cache, so reaching the very-stale
// state requires sitting >5s without touching idleTime -- i.e. an arbitrary
// fixed delay, which is both slow and flaky (see no-arbitrary-setTimeout).
// - The DBus poll timeout (kPollTimeoutMs, the "timed out" path). It only
// fires when the session bus stops replying, and there is no way to make a
// healthy Mutter IdleMonitor hang from a test.
// Both are covered by manual MOZ_LOG inspection rather than automation.
});