mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 02:44:02 +00:00
fix: stop Cockpit work before asynchronous UI teardown
This commit is contained in:
1 parent
947fc79182
commit
152d3e2352
11 files changed
+150
-15
No files matched your search
@@ -63,6 +63,7 @@ export class CockpitViewController {
|
||||
this.viewer = viewer;
|
||||
this.services = services;
|
||||
this.destroyed = false;
|
||||
this.disposed = false;
|
||||
this.active = false;
|
||||
this.trackedEntity = null;
|
||||
this.trackedEntityWasShown = true;
|
||||
@@ -407,10 +408,10 @@ export class CockpitViewController {
|
||||
return handleSignalClick.call(this, event);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
/** Revoke new work before the shell awaits layer restoration. */
|
||||
stop() {
|
||||
if (this.destroyed) return;
|
||||
this.destroyed = true;
|
||||
this.exit({ restoreTracking: false });
|
||||
this.regionalBriefAbort?.abort();
|
||||
this.regionalBriefAbort = null;
|
||||
this.regionalBriefRequestToken += 1;
|
||||
@@ -421,4 +422,11 @@ export class CockpitViewController {
|
||||
for (const removeListener of this._listenerRemovers.splice(0))
|
||||
removeListener?.();
|
||||
}
|
||||
|
||||
dispose() {
|
||||
if (this.disposed) return;
|
||||
this.disposed = true;
|
||||
this.stop();
|
||||
this.exit({ restoreTracking: false });
|
||||
}
|
||||
}
|
||||
@@ -141,3 +141,26 @@ test('an obsolete regional briefing cannot publish after Cockpit disposal', asyn
|
||||
env.restore();
|
||||
}
|
||||
});
|
||||
|
||||
test('stopping Cockpit revokes camera actions before deferred disposal releases rendering', () => {
|
||||
const env = environment();
|
||||
try {
|
||||
const released = [];
|
||||
const owner = new CockpitViewController(env.viewer, {
|
||||
services: {
|
||||
releaseContinuousRender: (reason) => released.push(reason),
|
||||
},
|
||||
});
|
||||
owner.active = true;
|
||||
owner.stop();
|
||||
assert.equal(env.callbacks.size, 0);
|
||||
assert.equal(owner.update(), false);
|
||||
assert.equal(owner.navigateContext(1), false);
|
||||
assert.deepEqual(released, []);
|
||||
owner.dispose();
|
||||
assert.deepEqual(released, ['cockpit']);
|
||||
assert.equal(owner.active, false);
|
||||
} finally {
|
||||
env.restore();
|
||||
}
|
||||
});
|
||||
@@ -6,6 +6,7 @@ export class CockpitDisplayPortal {
|
||||
this.layout = layout;
|
||||
this.active = false;
|
||||
this.destroyed = false;
|
||||
this.stopped = false;
|
||||
this.restoreOwner = null;
|
||||
this.generation = 0;
|
||||
this.frames = new Set();
|
||||
@@ -49,7 +50,7 @@ export class CockpitDisplayPortal {
|
||||
_frame(generation, callback) {
|
||||
const frame = requestAnimationFrame(() => {
|
||||
this.frames.delete(frame);
|
||||
if (!this.destroyed && generation === this.generation) callback();
|
||||
if (!this.stopped && generation === this.generation) callback();
|
||||
});
|
||||
this.frames.add(frame);
|
||||
}
|
||||
@@ -61,7 +62,7 @@ export class CockpitDisplayPortal {
|
||||
}
|
||||
|
||||
setActive(active, { settle = true } = {}) {
|
||||
if (this.destroyed) return;
|
||||
if (this.stopped) return;
|
||||
const nextActive = active === true;
|
||||
if (this.active === nextActive) return;
|
||||
this._cancelFrames();
|
||||
@@ -102,13 +103,25 @@ export class CockpitDisplayPortal {
|
||||
this.layout();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.destroyed) return;
|
||||
this.setActive(false, { settle: false });
|
||||
this.destroyed = true;
|
||||
stop() {
|
||||
if (this.stopped) return;
|
||||
this.stopped = true;
|
||||
this._cancelFrames();
|
||||
this.listeners.abort();
|
||||
for (const record of this.records) record.anchor.remove();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.destroyed) return;
|
||||
this.stop();
|
||||
this.destroyed = true;
|
||||
this.active = false;
|
||||
for (const record of this.records) {
|
||||
if (record.anchor.parentNode) record.anchor.after(record.group);
|
||||
record.anchor.remove();
|
||||
}
|
||||
this.cockpitPanel?.classList.remove('uses-shared-display-controls');
|
||||
if (this.standardPanel)
|
||||
this.standardPanel.scrollTop = this.standardScrollTop;
|
||||
this.records = [];
|
||||
this.restoreOwner = null;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ function fixture() {
|
||||
this.children = [];
|
||||
this.parentNode = null;
|
||||
this.scrollTop = 0;
|
||||
this.classList = { toggle() {} };
|
||||
this.classList = { toggle() {}, remove() {} };
|
||||
}
|
||||
append(node) {
|
||||
node.remove();
|
||||
@@ -181,3 +181,22 @@ test('disposal restores groups and revokes mode listeners and queued focus work'
|
||||
f.restore();
|
||||
}
|
||||
});
|
||||
|
||||
test('a stopped portal waits for cleanup without running queued focus work', () => {
|
||||
const f = fixture();
|
||||
try {
|
||||
const owner = f.create();
|
||||
owner.setActive(true);
|
||||
const queued = [...f.frames.values()];
|
||||
owner.stop();
|
||||
queued.forEach((callback) => callback());
|
||||
owner.setActive(false);
|
||||
assert.equal(f.group.parentNode, f.slot);
|
||||
assert.equal(f.frames.size, 0);
|
||||
assert.equal(f.focusCalls, 0);
|
||||
owner.destroy();
|
||||
assert.deepEqual(f.standard.children, [f.group, f.sibling]);
|
||||
} finally {
|
||||
f.restore();
|
||||
}
|
||||
});
|
||||
@@ -20,6 +20,7 @@ export function scheduleContextLayout() {
|
||||
return;
|
||||
this.contextLayoutFrame = requestAnimationFrame(() => {
|
||||
this.contextLayoutFrame = null;
|
||||
if (this.destroyed) return;
|
||||
this.syncContextLayout();
|
||||
this.syncSignalLayout();
|
||||
});
|
||||
|
||||
@@ -247,7 +247,7 @@ export function exit({ restoreTracking = true } = {}) {
|
||||
? this.entry
|
||||
: this.entry || this.entryFocusOrigin;
|
||||
this.entryFocusOrigin = null;
|
||||
if (restoreTarget?.isConnected && !restoreTarget.hidden) {
|
||||
if (!this.destroyed && restoreTarget?.isConnected && !restoreTarget.hidden) {
|
||||
restoreTarget.focus({ preventScroll: true });
|
||||
}
|
||||
return true;
|
||||
|
||||
Reference in new issue
Block a user