Skip to content

Commit d29ad3c

Browse files
committed
Fix compare graph layout refresh
1 parent ecaefd2 commit d29ad3c

4 files changed

Lines changed: 130 additions & 17 deletions

File tree

devtools/fx_viewer/templates/canvas_renderer.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ class CanvasRenderer {
4949
this.viewer.renderAll();
5050
}
5151

52+
resetInteractionState() {
53+
this.isDragging = false;
54+
this.dragMoved = false;
55+
this.lastMousePos = { x: 0, y: 0 };
56+
this.viewer.controller.handleHover(null, null);
57+
}
58+
5259
destroy() {
5360
if (this._resizeObserver) {
5461
this._resizeObserver.disconnect();

devtools/fx_viewer/templates/compare.js

Lines changed: 79 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ class FXGraphCompare {
7272
this._followSelection = true;
7373
this._openPortalMenus = [];
7474
this._currentTheme = this.viewers[0]?.controller?.state?.themeName || 'light';
75+
this._layoutRefreshQueued = false;
76+
this._layoutRefreshAgain = false;
77+
this._pendingRefreshAgainResetView = false;
78+
this._needsResetOnNextVisibleLayout = false;
7579

7680
if (this.container) {
7781
this._buildCompareDOM();
@@ -166,11 +170,11 @@ class FXGraphCompare {
166170

167171
// ResizeObserver on canvas cell
168172
if (typeof ResizeObserver !== 'undefined') {
169-
const ro = new ResizeObserver(() => {
170-
viewer.canvasRenderer.resize();
171-
viewer.renderAll();
172-
});
173+
const ro = new ResizeObserver(() => this._scheduleLayoutRefresh());
173174
ro.observe(canvasCell);
175+
if (viewer.minimapRenderer) {
176+
ro.observe(minimapCell);
177+
}
174178
this._colResizeObservers.push(ro);
175179
}
176180
});
@@ -183,21 +187,67 @@ class FXGraphCompare {
183187

184188
this._applyCompareTheme(this.viewers[0]?.controller?.state?.themeName || 'light');
185189

186-
// Resize all viewers after DOM settles (double-rAF ensures grid layout is complete)
190+
this._scheduleLayoutRefresh({ resetView: true });
191+
}
192+
193+
_scheduleLayoutRefresh(options = {}) {
194+
if (!this._root) return;
195+
if (this._layoutRefreshQueued) {
196+
this._layoutRefreshAgain = true;
197+
this._pendingRefreshAgainResetView = this._pendingRefreshAgainResetView || !!options.resetView;
198+
return;
199+
}
200+
this._pendingRefreshResetView = this._pendingRefreshResetView || !!options.resetView;
201+
this._layoutRefreshQueued = true;
187202
requestAnimationFrame(() => {
188203
requestAnimationFrame(() => {
189-
this.viewers.forEach((v) => {
190-
v.canvasRenderer.resize();
191-
if (v.minimapRenderer) {
192-
v.minimapRenderer.resize();
193-
v.minimapRenderer.generateThumbnail();
194-
}
195-
v.init();
196-
});
204+
this._layoutRefreshQueued = false;
205+
if (!this._root) return;
206+
const resetView = !!this._pendingRefreshResetView || this._needsResetOnNextVisibleLayout;
207+
this._pendingRefreshResetView = false;
208+
this._refreshViewerLayout({ resetView });
209+
if (this._layoutRefreshAgain) {
210+
const resetAgain = this._pendingRefreshAgainResetView;
211+
this._layoutRefreshAgain = false;
212+
this._pendingRefreshAgainResetView = false;
213+
this._scheduleLayoutRefresh({ resetView: resetAgain });
214+
}
197215
});
198216
});
199217
}
200218

219+
_refreshViewerLayout(options = {}) {
220+
const resetView = !!options.resetView;
221+
let sawInvalidLayout = false;
222+
this.viewers.forEach((viewer, i) => {
223+
if (!this._visibleViewers.has(this._viewerNames[i])) return;
224+
if (viewer.canvasRenderer && typeof viewer.canvasRenderer.resetInteractionState === 'function') {
225+
viewer.canvasRenderer.resetInteractionState();
226+
}
227+
if (viewer.minimapRenderer && typeof viewer.minimapRenderer.resetInteractionState === 'function') {
228+
viewer.minimapRenderer.resetInteractionState();
229+
}
230+
231+
const canvasRect = viewer.canvasContainer && viewer.canvasContainer.getBoundingClientRect();
232+
const minimapRect = viewer.minimapRenderer && viewer.minimapRenderer.container.getBoundingClientRect();
233+
const hasCanvasLayout = canvasRect && canvasRect.width > 0 && canvasRect.height > 0;
234+
const hasMinimapLayout = !viewer.minimapRenderer || (minimapRect && minimapRect.width > 0 && minimapRect.height > 0);
235+
if (!hasCanvasLayout || !hasMinimapLayout) {
236+
sawInvalidLayout = true;
237+
return;
238+
}
239+
240+
if (viewer.canvasRenderer) viewer.canvasRenderer.resize();
241+
if (viewer.minimapRenderer) {
242+
viewer.minimapRenderer.resize();
243+
viewer.minimapRenderer.generateThumbnail();
244+
}
245+
if (resetView) viewer.init();
246+
else viewer.renderAll();
247+
});
248+
this._needsResetOnNextVisibleLayout = sawInvalidLayout;
249+
}
250+
201251
_buildSidebar(sidebar) {
202252
// Layers button + menu (portal pattern — menu appended to document.body when open)
203253
const layersWrap = document.createElement('div');
@@ -489,15 +539,24 @@ class FXGraphCompare {
489539
}
490540
if (isVis) {
491541
colIdx++;
492-
v.canvasRenderer.resize();
493-
v.renderAll();
494542
}
495543
});
496544

545+
this._scheduleLayoutRefresh();
546+
497547
if (visible) {
498548
const newViewer = this.viewers[this._viewerNames.indexOf(name)];
499549
requestAnimationFrame(() => {
500-
newViewer.canvasRenderer.resize();
550+
const canvasRect = newViewer.canvasContainer && newViewer.canvasContainer.getBoundingClientRect();
551+
if (!canvasRect || canvasRect.width <= 0 || canvasRect.height <= 0) {
552+
this._needsResetOnNextVisibleLayout = true;
553+
return;
554+
}
555+
if (newViewer.canvasRenderer) newViewer.canvasRenderer.resize();
556+
if (newViewer.minimapRenderer) {
557+
newViewer.minimapRenderer.resize();
558+
newViewer.minimapRenderer.generateThumbnail();
559+
}
501560
let srcViewer = null, srcNodeId = null;
502561
this.viewers.forEach((v, i) => {
503562
if (v === newViewer || !this._visibleViewers.has(this._viewerNames[i])) return;
@@ -994,6 +1053,10 @@ class FXGraphCompare {
9941053
this._setViewerVisible(name, visible);
9951054
}
9961055

1056+
refreshLayout(options = {}) {
1057+
this._scheduleLayoutRefresh(options);
1058+
}
1059+
9971060
destroy() {
9981061
this._teardownCompareDOM();
9991062
this._offs.forEach((off) => {

devtools/fx_viewer/templates/minimap_renderer.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,10 @@ class MinimapRenderer {
168168
this.viewer.renderAll();
169169
}
170170

171+
resetInteractionState() {
172+
this.isDragging = false;
173+
}
174+
171175
render() {
172176
const dpr = window.devicePixelRatio || 1;
173177
if (this.canvas.width === 0 || this.canvas.height === 0) return;

devtools/observatory/templates/js/03_blocks.js

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,31 @@
1414
evictViewerCache,
1515
} = OBS.utils;
1616

17+
function refreshCompareLayouts(container, options) {
18+
if (!container || !(state.graphCompareInstances instanceof Map)) return;
19+
for (const [, inst] of state.graphCompareInstances) {
20+
const compare = inst && inst.compare;
21+
if (!compare || !compare._root || !compare._root.isConnected) continue;
22+
if (container !== compare._root && !container.contains(compare._root)) continue;
23+
try {
24+
if (typeof compare.refreshLayout === 'function') {
25+
compare.refreshLayout(options || {});
26+
} else {
27+
for (const v of compare.viewers || []) {
28+
try { v.canvasRenderer.resize(); } catch (_) {}
29+
try {
30+
if (v.minimapRenderer) {
31+
v.minimapRenderer.resize();
32+
v.minimapRenderer.generateThumbnail();
33+
}
34+
} catch (_) {}
35+
try { v.renderAll(); } catch (_) {}
36+
}
37+
}
38+
} catch (_) {}
39+
}
40+
}
41+
1742
function createSection(title, storageKey, collapsible) {
1843
const isCollapsible = collapsible !== false;
1944
const isCollapsed = isCollapsible && state.viewPrefs[storageKey] === false;
@@ -36,8 +61,12 @@
3661
header.onclick = () => {
3762
content.classList.toggle('hidden');
3863
header.classList.toggle('collapsed');
39-
state.viewPrefs[storageKey] = content.classList.contains('hidden') ? false : true;
64+
const isExpanded = !content.classList.contains('hidden');
65+
state.viewPrefs[storageKey] = isExpanded;
4066
localStorage.setItem('graphCollectorViewPrefs', JSON.stringify(state.viewPrefs));
67+
if (isExpanded) {
68+
requestAnimationFrame(() => refreshCompareLayouts(content));
69+
}
4170
};
4271
}
4372

@@ -369,8 +398,18 @@
369398
if (cached) {
370399
content.appendChild(cached.compare._root);
371400
requestAnimationFrame(() => {
401+
if (typeof cached.compare.refreshLayout === 'function') {
402+
try { cached.compare.refreshLayout(); } catch (_) {}
403+
return;
404+
}
372405
for (const v of cached.compare.viewers) {
373406
try { v.canvasRenderer.resize(); } catch (_) {}
407+
try {
408+
if (v.minimapRenderer) {
409+
v.minimapRenderer.resize();
410+
v.minimapRenderer.generateThumbnail();
411+
}
412+
} catch (_) {}
374413
try { v.renderAll(); } catch (_) {}
375414
}
376415
});

0 commit comments

Comments
 (0)