fix(viewer): destroy a pending viewer whose element left the stage
rebuild() empties the stage while a view is pending, and the deferred destroy then failed on the viewer's null parent: a page error, and the image kept its src. The destroy now finishes and the src is released.
This commit is contained in:
@@ -157,6 +157,27 @@ test('Viewer.js is built only on a loaded image and an unbuilt viewer is release
|
||||
} finally { view.dispose(); parentScope.dispose(); f.restore(); Object.assign(globalThis, globals); }
|
||||
});
|
||||
|
||||
test('a pending Viewer whose element left the stage is still destroyed, without an error', async () => {
|
||||
const {reportedLocationAdapter:adapter} = await import('../../web/js/workspace/history-media.mjs');
|
||||
const f = mountedDom(), parentScope = createScope(), controller = borrowedController(), items = [occurrenceMedia('1',1,'c')];
|
||||
const instances = [];
|
||||
// Viewer.js's destroy() ends with `this.viewer.parentNode.removeChild(this.viewer)`, and moving on empties the stage.
|
||||
f.win.Viewer = class { constructor(node,options) { this.node = node; this.options = options; this.destroyed = 0; this.ready = true; this.viewer = document.createElement('div'); instances.push(this); }
|
||||
destroy() { if (!this.viewer.parent) throw new TypeError('null is not an object'); this.destroyed++; } resize() {} };
|
||||
const globals = {ResizeObserver:globalThis.ResizeObserver, requestAnimationFrame:globalThis.requestAnimationFrame, cancelAnimationFrame:globalThis.cancelAnimationFrame};
|
||||
Object.assign(globalThis, {ResizeObserver:class { observe() {} disconnect() {} }, requestAnimationFrame:() => 1, cancelAnimationFrame() {}});
|
||||
const view = module.createMediaViewer({container:f.container,parentScope,inspectionController:controller,adapter,sender:{open(){}}});
|
||||
try {
|
||||
view.open({items,selectedKey:'c'.repeat(64)});f.walk(f.container,n => n.tagName === 'img')[0].fire('load');
|
||||
const [loading] = instances, removed = [];
|
||||
loading.node.removeAttribute = key => removed.push(key);
|
||||
f.walk(f.container,n => n.attributes['aria-label'] === 'Close image viewer')[0].fire('click');
|
||||
loading.options.viewed.call(loading);
|
||||
assert.equal(loading.destroyed, 1, 'destroyed once its pending view settles');
|
||||
assert.deepEqual(removed, ['src'], 'and its image is released');
|
||||
} finally { view.dispose(); parentScope.dispose(); f.restore(); Object.assign(globalThis, globals); }
|
||||
});
|
||||
|
||||
test('an inline Viewer still loading its image is destroyed only after it has viewed it', async () => {
|
||||
const {reportedLocationAdapter:adapter} = await import('../../web/js/workspace/history-media.mjs');
|
||||
const f = mountedDom(), parentScope = createScope(), controller = borrowedController(), items = [occurrenceMedia('1',1,'c')];
|
||||
|
||||
@@ -267,7 +267,10 @@ export function createMediaViewer({api,container,parentScope,inspectionControlle
|
||||
let fitFrame = 0, fitWidth, fitHeight, viewSettled = false, released = false, fallback = 0;
|
||||
const release = () => {
|
||||
if (released) return;
|
||||
released = true;clearTimeout(fallback);instance.destroy();node.removeAttribute('src');
|
||||
released = true;clearTimeout(fallback);
|
||||
// Moving on empties the stage, and destroy() removes the built viewer from its parent.
|
||||
if (instance.viewer && !instance.viewer.parentNode) document.createElement('div').append(instance.viewer);
|
||||
instance.destroy();node.removeAttribute('src');
|
||||
};
|
||||
const scheduleFit = () => {
|
||||
if (!current() || fitFrame) return;
|
||||
|
||||
Reference in New Issue
Block a user