Skip to content

Commit ceabd3b

Browse files
committed
fix(resize): prevent crashes on hidden/zero viewport states (sugarlabs#5602)
When the browser tab is hidden or the viewport height changes abruptly (e.g., toggling DevTools), the layout engine tries to calculate canvas dimensions based on invalid width/height values of 0, causing ResizeObserver loop errors or division-by-zero freezes. Changes: - Debounce TurtlesView resize listener (150ms) to prevent rapid-fire calculations - Add document.hidden checks in _onResize, handleResize, and TurtlesView to skip layout when tab is hidden - Add zero-dimension guards (w<=0, h<=0) in _onResize, handleResize, and doScale - Guard coordinate conversion methods (_invertY, screenX2turtleX, screenY2turtleY, turtleX2screenX, turtleY2screenY) against 0x0 canvas - Guard all MediaBlocks screen position/size blocks (RightPos, LeftPos, TopPos, BottomPos, Width, Height) against zero canvas/scale - Add visibilitychange listener to re-apply correct dimensions when returning to tab - Increase activity.js resize debounce from 100ms to 200ms Fixes sugarlabs#5602
1 parent 943f215 commit ceabd3b

File tree

3 files changed

+214
-100
lines changed

3 files changed

+214
-100
lines changed

js/activity.js

Lines changed: 85 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2083,7 +2083,7 @@ class Activity {
20832083
const changeText = () => {
20842084
const randomLoadMessage =
20852085
messages.load_messages[
2086-
Math.floor(Math.random() * messages.load_messages.length)
2086+
Math.floor(Math.random() * messages.load_messages.length)
20872087
];
20882088
document.getElementById("messageText").innerHTML = randomLoadMessage + "...";
20892089
counter++;
@@ -3100,11 +3100,11 @@ class Activity {
31003100
return $j("<li></li>")
31013101
.append(
31023102
'<img src="' +
3103-
(item.artwork || "") +
3104-
'" height="20px">' +
3105-
"<a> " +
3106-
item.label +
3107-
"</a>"
3103+
(item.artwork || "") +
3104+
'" height="20px">' +
3105+
"<a> " +
3106+
item.label +
3107+
"</a>"
31083108
)
31093109
.appendTo(
31103110
ul.css({
@@ -3599,6 +3599,12 @@ class Activity {
35993599
return;
36003600
}
36013601

3602+
// Skip resize when the tab is hidden — canvas reports 0×0
3603+
// and any layout work would corrupt positions.
3604+
if (document.hidden) {
3605+
return;
3606+
}
3607+
36023608
const $j = window.jQuery;
36033609
let w = 0,
36043610
h = 0;
@@ -3617,6 +3623,12 @@ class Activity {
36173623
this._outerWidth = window.outerWidth;
36183624
this._outerHeight = window.outerHeight;
36193625

3626+
// Guard against zero or invalid dimensions to prevent
3627+
// division-by-zero and ResizeObserver loop errors
3628+
if (w <= 0 || h <= 0) {
3629+
return;
3630+
}
3631+
36203632
if (document.getElementById("labelDiv").classList.contains("hasKeyboard")) {
36213633
return;
36223634
}
@@ -3790,6 +3802,11 @@ class Activity {
37903802
const defaultHeight = 900;
37913803

37923804
function handleResize() {
3805+
// Skip resize when the tab is hidden to prevent 0×0 canvas
3806+
if (document.hidden) {
3807+
return;
3808+
}
3809+
37933810
const isMaximized =
37943811
window.innerWidth === window.screen.width &&
37953812
window.innerHeight === window.screen.height;
@@ -3805,6 +3822,12 @@ class Activity {
38053822
} else {
38063823
const windowWidth = window.innerWidth;
38073824
const windowHeight = window.innerHeight;
3825+
3826+
// Guard against zero or invalid dimensions
3827+
if (windowWidth <= 0 || windowHeight <= 0) {
3828+
return;
3829+
}
3830+
38083831
container.style.width = windowWidth + "px";
38093832
container.style.height = windowHeight + "px";
38103833
canvas.width = windowWidth;
@@ -3824,11 +3847,28 @@ class Activity {
38243847
resizeTimeout = setTimeout(() => {
38253848
handleResize();
38263849
this._setupPaletteMenu();
3827-
}, 100);
3850+
}, 200);
38283851
};
38293852
this.addEventListener(window, "resize", this._handleWindowResize);
38303853
this._handleOrientationChangeResize = handleResize;
38313854
this.addEventListener(window, "orientationchange", this._handleOrientationChangeResize);
3855+
3856+
// When the user returns to the Music Blocks tab after it was
3857+
// hidden, re-apply the correct dimensions. While the tab was
3858+
// hidden the resize guards above intentionally skipped any
3859+
// layout work, so we need to catch up now.
3860+
this._handleVisibilityChange = () => {
3861+
if (!document.hidden && this.stage) {
3862+
// Use a short delay to let the browser finish
3863+
// exposing the tab and reporting real dimensions.
3864+
setTimeout(() => {
3865+
handleResize();
3866+
this._onResize(false);
3867+
}, 250);
3868+
}
3869+
};
3870+
this.addEventListener(document, "visibilitychange", this._handleVisibilityChange);
3871+
38323872
const that = this;
38333873
const resizeCanvas_ = () => {
38343874
try {
@@ -4491,8 +4531,8 @@ class Activity {
44914531
console.log(
44924532
"%cMusic Blocks",
44934533
"font-size: 24px; font-weight: bold; font-family: sans-serif; padding:20px 0 0 110px; background: url(" +
4494-
imgUrl +
4495-
") no-repeat;"
4534+
imgUrl +
4535+
") no-repeat;"
44964536
);
44974537
// eslint-disable-next-line no-console
44984538
console.log(
@@ -4564,10 +4604,10 @@ class Activity {
45644604
typeof flags !== "undefined"
45654605
? flags
45664606
: {
4567-
run: false,
4568-
show: false,
4569-
collapse: false
4570-
};
4607+
run: false,
4608+
show: false,
4609+
collapse: false
4610+
};
45714611
this.loading = true;
45724612
document.body.style.cursor = "wait";
45734613
this.doLoadAnimation();
@@ -4930,9 +4970,8 @@ class Activity {
49304970
[
49314971
"nameddo",
49324972
{
4933-
value: `V: ${parseInt(lineId) + 1} Line ${
4934-
staffBlocksMap[lineId]?.baseBlocks?.length + 1
4935-
}`
4973+
value: `V: ${parseInt(lineId) + 1} Line ${staffBlocksMap[lineId]?.baseBlocks?.length + 1
4974+
}`
49364975
}
49374976
],
49384977
0,
@@ -4941,12 +4980,12 @@ class Activity {
49414980
staffBlocksMap[lineId].baseBlocks.length === 0
49424981
? null
49434982
: staffBlocksMap[lineId].baseBlocks[
4944-
staffBlocksMap[lineId].baseBlocks.length - 1
4945-
][0][
4946-
staffBlocksMap[lineId].baseBlocks[
4947-
staffBlocksMap[lineId].baseBlocks.length - 1
4948-
][0].length - 4
4949-
][0],
4983+
staffBlocksMap[lineId].baseBlocks.length - 1
4984+
][0][
4985+
staffBlocksMap[lineId].baseBlocks[
4986+
staffBlocksMap[lineId].baseBlocks.length - 1
4987+
][0].length - 4
4988+
][0],
49504989
null
49514990
]
49524991
],
@@ -4962,9 +5001,8 @@ class Activity {
49625001
[
49635002
"text",
49645003
{
4965-
value: `V: ${parseInt(lineId) + 1} Line ${
4966-
staffBlocksMap[lineId]?.baseBlocks?.length + 1
4967-
}`
5004+
value: `V: ${parseInt(lineId) + 1} Line ${staffBlocksMap[lineId]?.baseBlocks?.length + 1
5005+
}`
49685006
}
49695007
],
49705008
0,
@@ -4999,14 +5037,14 @@ class Activity {
49995037
staffBlocksMap[staffIndex].startBlock.length - 3
50005038
][4][2] =
50015039
staffBlocksMap[staffIndex].baseBlocks[0][0][
5002-
staffBlocksMap[staffIndex].baseBlocks[0][0].length - 4
5040+
staffBlocksMap[staffIndex].baseBlocks[0][0].length - 4
50035041
][0];
50045042
// Update the first namedo block with settimbre
50055043
staffBlocksMap[staffIndex].baseBlocks[0][0][
50065044
staffBlocksMap[staffIndex].baseBlocks[0][0].length - 4
50075045
][4][0] =
50085046
staffBlocksMap[staffIndex].startBlock[
5009-
staffBlocksMap[staffIndex].startBlock.length - 3
5047+
staffBlocksMap[staffIndex].startBlock.length - 3
50105048
][0];
50115049
const repeatblockids = staffBlocksMap[staffIndex].repeatArray;
50125050
for (const repeatId of repeatblockids) {
@@ -5018,7 +5056,7 @@ class Activity {
50185056
0,
50195057
[
50205058
staffBlocksMap[staffIndex].startBlock[
5021-
staffBlocksMap[staffIndex].startBlock.length - 3
5059+
staffBlocksMap[staffIndex].startBlock.length - 3
50225060
][0] /*setribmre*/,
50235061
blockId + 1,
50245062
staffBlocksMap[staffIndex].nameddoArray[staffIndex][0],
@@ -5027,8 +5065,8 @@ class Activity {
50275065
] === null
50285066
? null
50295067
: staffBlocksMap[staffIndex].nameddoArray[staffIndex][
5030-
repeatId.end + 1
5031-
]
5068+
repeatId.end + 1
5069+
]
50325070
]
50335071
]);
50345072
staffBlocksMap[staffIndex].repeatBlock.push([
@@ -5062,7 +5100,7 @@ class Activity {
50625100
const secondnammedo = _searchIndexForMusicBlock(
50635101
staffBlocksMap[staffIndex].baseBlocks[repeatId.end + 1][0],
50645102
staffBlocksMap[staffIndex].nameddoArray[staffIndex][
5065-
repeatId.end + 1
5103+
repeatId.end + 1
50665104
]
50675105
);
50685106

@@ -5085,31 +5123,31 @@ class Activity {
50855123
const prevnameddo = _searchIndexForMusicBlock(
50865124
staffBlocksMap[staffIndex].baseBlocks[repeatId.start - 1][0],
50875125
staffBlocksMap[staffIndex].baseBlocks[repeatId.start][0][
5088-
currentnammeddo
5126+
currentnammeddo
50895127
][4][0]
50905128
);
50915129
const afternamedo = _searchIndexForMusicBlock(
50925130
staffBlocksMap[staffIndex].baseBlocks[repeatId.end][0],
50935131
staffBlocksMap[staffIndex].baseBlocks[repeatId.start][0][
5094-
currentnammeddo
5132+
currentnammeddo
50955133
][4][1]
50965134
);
50975135
let prevrepeatnameddo = -1;
50985136
if (prevnameddo === -1) {
50995137
prevrepeatnameddo = _searchIndexForMusicBlock(
51005138
staffBlocksMap[staffIndex].repeatBlock,
51015139
staffBlocksMap[staffIndex].baseBlocks[repeatId.start][0][
5102-
currentnammeddo
5140+
currentnammeddo
51035141
][4][0]
51045142
);
51055143
}
51065144
const prevBlockId =
51075145
staffBlocksMap[staffIndex].baseBlocks[repeatId.start][0][
5108-
currentnammeddo
5146+
currentnammeddo
51095147
][4][0];
51105148
const currentBlockId =
51115149
staffBlocksMap[staffIndex].baseBlocks[repeatId.start][0][
5112-
currentnammeddo
5150+
currentnammeddo
51135151
][0];
51145152

51155153
// Needs null checking optmizie
@@ -5123,7 +5161,7 @@ class Activity {
51235161
0,
51245162
[
51255163
staffBlocksMap[staffIndex].baseBlocks[repeatId.start][0][
5126-
currentnammeddo
5164+
currentnammeddo
51275165
][4][0],
51285166
blockId + 1,
51295167
currentBlockId,
@@ -6432,12 +6470,12 @@ class Activity {
64326470
return $j("<li></li>")
64336471
.append(
64346472
'<img src="' +
6435-
(item.artwork || "") +
6436-
'" height = "20px">' +
6437-
"<a>" +
6438-
" " +
6439-
item.label +
6440-
"</a>"
6473+
(item.artwork || "") +
6474+
'" height = "20px">' +
6475+
"<a>" +
6476+
" " +
6477+
item.label +
6478+
"</a>"
64416479
)
64426480
.appendTo(ul.css("z-index", 35000));
64436481
};
@@ -6561,10 +6599,10 @@ class Activity {
65616599
container.setAttribute(
65626600
"style",
65636601
"position: absolute; right:" +
6564-
(document.body.clientWidth - x) +
6565-
"px; top: " +
6566-
y +
6567-
"px;"
6602+
(document.body.clientWidth - x) +
6603+
"px; top: " +
6604+
y +
6605+
"px;"
65686606
);
65696607
document.getElementById("buttoncontainerBOTTOM").appendChild(container);
65706608
return container;

0 commit comments

Comments
 (0)