Keep history entries untouched by unfinished globe picks and restore each visited entry's layers

A picked engine now lives in the switch until it runs; only then is the
address written, so a pick superseded by Back or one that fails rewrites no
entry. An engine-changing history visit holds the outgoing share link, which
neither restores nor writes the visited entry, and the running engine takes
the address back once the switch settles, restoring any entry visited while
it was held or not yet listening. Handed-off layers keep running where the
incoming address agrees with the outgoing one and follow a visited entry
where it differs. A failed recovery keeps the error on the loading screen
with an engine picker for a retry, and a failing reveal of the new globe no
longer blocks the cover removal or later requests.
This commit is contained in:
German Khokhlov committed 2026-11-03 15:24:20 +04:00
1 parent 9cc34e324f
commit a066a2d1ca
10 files changed
+539 -113

No files matched your search

+93 -31
View File
@@ -255,12 +255,91 @@ try {
),
);
assert.equal(await stored(), 'navara');
// Back/Forward must hand the session off like the picker: the Contacts
// subject and its navigation history, the layers it owns and the Director
// survive, although the earlier entry carries an older view.
await page.goBack({ waitUntil: 'domcontentloaded' });
await ready('cesium');
assert.equal((await sharpen()).intensityPct, 49);
await page.goForward();
await ready('navara');
assert.equal((await sharpen()).intensityPct, 80);
await page.evaluate(() =>
window.__godsEyeView.styleManager.setSharpen({
enabled: false,
intensityPct: 49,
}),
);
await switchTo('cesium');
await switchTo('navara');
assert.deepEqual(await sharpen(), { enabled: false, intensityPct: 49 });
// The address carries the running layer set once its debounced write lands.
const settledAddress = async () => {
let previous;
for (;;) {
await new Promise((resolve) => setTimeout(resolve, 1200));
const current = await page.evaluate(() =>
new URLSearchParams(location.hash.slice(1)).get('l'),
);
if (current === previous) return current;
previous = current;
}
};
const pick = async (kind) => {
await page.select('#globe-engine', kind);
await ready(kind);
await page.waitForFunction(
() => !document.querySelector('.engine-switch-frame'),
);
};
const visit = async (direction, kind) => {
await (direction === 'back'
? page.goBack({ waitUntil: 'domcontentloaded' })
: page.goForward());
await ready(kind);
await page.waitForFunction(
() => !document.querySelector('.engine-switch-frame'),
);
};
const quakes = () =>
page.evaluate(() => {
const { dataManager } = window.__godsEyeView;
return {
model: window.__quakeModel === dataManager.layers.get('earthquakes'),
enabled: dataManager.getEnabledLayerIds().has('earthquakes'),
};
});
// Like a same-engine Back, each entry brings back its own layer set, on
// the same layer models.
await page.evaluate(() => {
window.__quakeModel =
window.__godsEyeView.dataManager.layers.get('earthquakes');
});
const navaraLayers = await settledAddress();
await pick('cesium');
await page.evaluate(() =>
window.__godsEyeView.dataManager.setEnabled('earthquakes', false, {
origin: 'user',
}),
);
const cesiumLayers = await settledAddress();
assert.notEqual(cesiumLayers, navaraLayers);
await visit('back', 'navara');
assert.deepEqual(await quakes(), { model: true, enabled: true });
assert.equal(await settledAddress(), navaraLayers, 'Back keeps its entry');
await visit('forward', 'cesium');
assert.deepEqual(await quakes(), { model: true, enabled: false });
assert.equal(await settledAddress(), cesiumLayers, 'Forward keeps its entry');
await page.evaluate(() =>
window.__godsEyeView.dataManager.setEnabled('earthquakes', true, {
origin: 'user',
}),
);
await pick('navara');
console.log(
"PASS Back/Forward restore each entry's layers on the same models",
);
// Back/Forward hand the session off like the picker: the Contacts subject
// and its navigation history, its layers and the Director survive.
await page.evaluate(async () => {
const { dataManager, styleManager, sceneDirector } = window.__godsEyeView;
window.qaLayersBefore = [...dataManager.getEnabledLayerIds()].sort();
await dataManager.setEnabled('ais-live-vessels', true, { origin: 'user' });
const outcome = await styleManager.setContextMode('contacts');
if (!outcome.ok) throw Error(JSON.stringify(outcome));
@@ -297,20 +376,16 @@ try {
assert.equal(live.mode, 'flights');
assert.equal(live.subject, '223456789');
assert.ok(live.history.includes('ais-live-vessels:123456789'));
await page.goBack({ waitUntil: 'domcontentloaded' });
await ready('cesium');
await page.waitForFunction(
() => !document.querySelector('.engine-switch-frame'),
);
assert.equal((await sharpen()).intensityPct, 49);
await settledAddress();
const historyLength = await page.evaluate(() => history.length);
await pick('cesium');
assert.deepEqual(await session(), live, 'a pick keeps the session');
await settledAddress();
await visit('back', 'navara');
assert.deepEqual(await session(), live, 'Back keeps the session');
await page.goForward();
await ready('navara');
await page.waitForFunction(
() => !document.querySelector('.engine-switch-frame'),
);
assert.equal((await sharpen()).intensityPct, 80);
await visit('forward', 'cesium');
assert.deepEqual(await session(), live, 'Forward keeps the session');
assert.equal(await page.evaluate(() => history.length), historyLength + 1);
assert.ok(
await page.evaluate(async () => {
const { dataManager, styleManager } = window.__godsEyeView;
@@ -318,26 +393,13 @@ try {
await dataManager.setEnabled('ais-live-vessels', false, {
origin: 'user',
});
return (
result.ok &&
JSON.stringify([...dataManager.getEnabledLayerIds()].sort()) ===
JSON.stringify(window.qaLayersBefore)
);
return result.ok;
}),
'leaving Contacts after Back/Forward restores the layers it took',
);
await pick('navara');
console.log(
'PASS Back/Forward keep Contacts subject, history, layers and Director',
);
await page.evaluate(() =>
window.__godsEyeView.styleManager.setSharpen({
enabled: false,
intensityPct: 49,
}),
);
await switchTo('cesium');
await switchTo('navara');
assert.deepEqual(await sharpen(), { enabled: false, intensityPct: 49 });
await page.goto(base.href, { waitUntil: 'domcontentloaded' });
await ready('navara');
await switchTo('cesium');
+70
View File
@@ -300,6 +300,9 @@ try {
console.log(
'PASS warm Navara failure rolled back to Cesium with the same flight and cockpit',
);
await page.evaluate(() => {
window.__parkedCesium = window.__godsEyeView.engine;
});
await page.select('#globe-engine', 'navara');
await ready('navara');
assert.ok(
@@ -315,6 +318,73 @@ try {
'the retry starts a new Navara renderer on the same session',
);
console.log('PASS retry after the warm failure started Navara cold');
// Both engines fail: the warm Cesium resume and the warm Navara recovery.
// No engine runs, but the error screen offers the engine picker, and a
// retry starts Cesium cold on the same session without a reload.
const lengthBeforeDoubleFailure = await page.evaluate(() => {
window.__failResume = true;
for (const engine of [window.__godsEyeView.engine, window.__parkedCesium]) {
const request = engine.render.request;
engine.render.request = function (reason, ...args) {
if (reason === 'engine-resume' && window.__failResume) {
window.__refusedResumes = (window.__refusedResumes ?? 0) + 1;
throw new Error('QA parked renderer failure');
}
return request.call(this, reason, ...args);
};
}
window.__startDocument = document;
return history.length;
});
await page.select('#globe-engine', 'cesium');
await page.waitForSelector('#loading-screen .engine-switch-retry button', {
visible: true,
timeout: 90000,
});
assert.ok(
await page.evaluate(
(length) =>
window.__refusedResumes === 2 &&
!window.__godsEyeView &&
history.length === length &&
!document
.querySelector('#loading-screen')
.classList.contains('hidden') &&
/QA parked renderer failure/.test(
document.querySelector('#loading-screen .loader-status').textContent,
),
lengthBeforeDoubleFailure,
),
'a failed recovery shows its error and the engine picker',
);
await page.evaluate(() => {
window.__failResume = false;
});
await page.click(
'#loading-screen .engine-switch-retry [data-engine="cesium"]',
);
await ready('cesium');
assert.ok(
await page.evaluate(() => {
const { dataManager, styleManager } = window.__godsEyeView;
return (
window.__startDocument === document &&
!document.querySelector('.engine-switch-retry') &&
dataManager === window.__handoff.dataManager &&
dataManager.layers.get(window.__qaAircraft.layerId) ===
window.__handoff.entry &&
styleManager.getCockpitState().active &&
styleManager.getAircraftTrackingTarget()?.id ===
window.__qaAircraft.aircraftId &&
new URL(location.href).searchParams.get('engine') === 'cesium'
);
}),
'the retry restores the same session on Cesium',
);
console.log(
'PASS failed recovery offered the picker; the retry restored the session on Cesium',
);
await page.evaluate(() =>
window.__godsEyeView.styleManager.controlCockpit('exit'),
);