Make startup animation use async functions throughout

During session startup, we have the following chain:

    LayoutManager._prepareStartupAnimation()
    `- LayoutManager._startupAnimation()
       `- LayoutManager._startupAnimationComplete()       (a)
       | or
       `- LayoutManager._startupAnimationGreeter()        (b)
       | or
       `- LayoutManager._startupAnimationSession()        (c)
          `- Overview.runStartupAnimation()               (d)
             `- OverviewActor.runStartupAnimation()       (e)
                `- ControlsManager.runStartupAnimation()  (f)

Branch (b) calls LayoutManager._startupAnimationComplete() once the
animation is complete. Branch (c) does the same, except that
ControlsManager.runStartupAnimation() is an async function that awaits
LayoutManager.ensureAllocation().

LayoutManager._prepareStartupAnimation() is an `async` function, and its
caller catches & logs any error it raises.

Previously, ControlsManager.runStartupAnimation() – (f) in the above
diagram – was declared `async`, because it uses `await` internally, but
also accepted a callback, which was called on successful completion of
the function and the animation it triggers.

If an exception is raised during execution of the function, its callback
would never be called, and because the promise it implicitly returns is
discarded, the exception would never be logged either. And, stepping a
few levels up the call stack, the callback that
LayoutManager._startupAnimationSession() (c) provided would never be
called, and so LayoutManager._startupAnimationComplete() would never be
called, meaning the cover pane that prevents input would never be
removed and the session would be unusable.

Remove the callback parameter from ControlsManager.runStartupAnimation()
(f), and instead make it resolve in the case where the callback would
previously have been called.

Remove the callback parameter from OverviewActor.runStartupAnimation()
(e) – it is just a wrapper around ControlsManager.runStartupAnimation().

Adjust Overview.runStartupAnimation() (d) accordingly: rather than
passing a callback to OverviewActor.runStartupAnimation(), await its
return and then proceed. There is a slight behaviour change here:
previously, in the branch where this._syncGrab() is false, the callback
would be called before calling this.hide(), whereas now this.hide() is
called before the async function returns.

Adjust LayoutManager._startupAnimationSession() and its siblings to be
async, rather than each calling _startupAnimationComplete() directly.
Finally, in _prepareStartupAnimation()  await _startupAnimation() either
succeeding or failing, and in either case call
_startupAnimationComplete(), removing the cover pane that prevents all
input.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/3428>
This commit is contained in:
Will Thompson 2024-07-19 14:11:15 +01:00
parent acfa372d4f
commit 3d06134545
3 changed files with 57 additions and 51 deletions

View File

@ -763,39 +763,47 @@ export const LayoutManager = GObject.registerClass({
else
this.emit('startup-prepared');
this._startupAnimation();
}
_startupAnimation() {
if (Meta.is_restart())
try {
await this._startupAnimation();
} finally {
this._startupAnimationComplete();
else if (Main.sessionMode.isGreeter)
this._startupAnimationGreeter();
else
this._startupAnimationSession();
}
}
_startupAnimationGreeter() {
this.panelBox.ease({
translation_y: 0,
duration: STARTUP_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onStopped: () => this._startupAnimationComplete(),
async _startupAnimation() {
if (Meta.is_restart())
return;
if (Main.sessionMode.isGreeter)
await this._startupAnimationGreeter();
else
await this._startupAnimationSession();
}
async _startupAnimationGreeter() {
await new Promise(resolve => {
this.panelBox.ease({
translation_y: 0,
duration: STARTUP_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onStopped: () => resolve(),
});
});
}
_startupAnimationSession() {
const onStopped = () => this._startupAnimationComplete();
async _startupAnimationSession() {
if (Main.sessionMode.hasOverview) {
Main.overview.runStartupAnimation(onStopped);
await Main.overview.runStartupAnimation();
} else {
this.uiGroup.ease({
scale_x: 1,
scale_y: 1,
opacity: 255,
duration: STARTUP_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onStopped,
await new Promise(resolve => {
this.uiGroup.ease({
scale_x: 1,
scale_y: 1,
opacity: 255,
duration: STARTUP_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onStopped: () => resolve(),
});
});
}
}

View File

@ -90,8 +90,8 @@ class OverviewActor extends St.BoxLayout {
this._controls.animateFromOverview(callback);
}
runStartupAnimation(callback) {
this._controls.runStartupAnimation(callback);
async runStartupAnimation() {
await this._controls.runStartupAnimation();
}
get dash() {
@ -666,7 +666,7 @@ export class Overview extends Signals.EventEmitter {
this._overview.controls.appDisplay.selectApp(id);
}
runStartupAnimation(callback) {
async runStartupAnimation() {
Main.panel.style = 'transition-duration: 0ms;';
this._shown = true;
@ -679,23 +679,19 @@ export class Overview extends Signals.EventEmitter {
this._changeShownState(OverviewShownState.SHOWING);
this._overview.runStartupAnimation(() => {
// Overview got hidden during startup animation
if (this._shownState !== OverviewShownState.SHOWING) {
callback();
return;
}
await this._overview.runStartupAnimation();
if (!this._syncGrab()) {
callback();
this.hide();
return;
}
// Overview got hidden during startup animation
if (this._shownState !== OverviewShownState.SHOWING)
return;
Main.panel.style = null;
this._changeShownState(OverviewShownState.SHOWN);
callback();
});
if (!this._syncGrab()) {
this.hide();
return;
}
Main.panel.style = null;
this._changeShownState(OverviewShownState.SHOWN);
}
getShowAppsButton() {

View File

@ -797,7 +797,7 @@ class ControlsManager extends St.Widget {
this._stateAdjustment.gestureInProgress = false;
}
async runStartupAnimation(callback) {
async runStartupAnimation() {
this._ignoreShowAppsButtonToggle = true;
this.prepareToEnterOverview();
@ -839,14 +839,16 @@ class ControlsManager extends St.Widget {
});
// The Dash rises from the bottom. This is the last animation to finish,
// so run the callback there.
// so resolve the promise there.
this.dash.translation_y = this.dash.height + this.dash.margin_bottom;
this.dash.ease({
translation_y: 0,
delay: STARTUP_ANIMATION_TIME,
duration: STARTUP_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onStopped: () => callback(),
return new Promise(resolve => {
this.dash.ease({
translation_y: 0,
delay: STARTUP_ANIMATION_TIME,
duration: STARTUP_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onStopped: () => resolve(),
});
});
}