// Copyright (C) 2024 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import {defer} from '../base/deferred'; import {raf} from '../core/raf_scheduler'; import {AppImpl} from '../core/app_impl'; /** * This class is exposed by index.ts as window.waitForPerfettoIdle() and is used * by tests, to detect when we reach quiescence. */ const IDLE_HYSTERESIS_MS = 100; const TIMEOUT_MS = 30_000; export class IdleDetector { private promise = defer(); private deadline = performance.now() + TIMEOUT_MS; private idleSince?: number; private idleHysteresisMs = IDLE_HYSTERESIS_MS; waitForPerfettoIdle(idleHysteresisMs = IDLE_HYSTERESIS_MS): Promise { this.idleSince = undefined; this.idleHysteresisMs = idleHysteresisMs; this.scheduleNextTask(); return this.promise; } private onIdleCallback() { const now = performance.now(); if (now > this.deadline) { this.promise.reject( `Didn't reach idle within ${TIMEOUT_MS} ms, giving up` + ` ${this.idleIndicators()}`, ); return; } if (this.idleIndicators().every((x) => x)) { this.idleSince = this.idleSince ?? now; const idleDur = now - this.idleSince; if (idleDur >= this.idleHysteresisMs) { // We have been idle for more than the threshold, success. this.promise.resolve(); return; } // We are idle, but not for long enough. keep waiting this.scheduleNextTask(); return; } // Not idle, reset and repeat. this.idleSince = undefined; this.scheduleNextTask(); } private scheduleNextTask() { requestIdleCallback(() => this.onIdleCallback()); } private idleIndicators() { const reqsPending = AppImpl.instance.trace?.engine.numRequestsPending ?? 0; return [ reqsPending === 0, !raf.hasPendingRedraws, !document.getAnimations().some((a) => a.playState === 'running'), document.querySelector('.progress.progress-anim') == null, document.querySelector('.omnibox.message-mode') == null, ]; } }