Skip to content

Commit

Permalink
Better resize rate limiting
Browse files Browse the repository at this point in the history
Be more aggressive with resizing, limiting it to once ever 100 ms
instead of after a 500 ms idle period. This gives a more responsive user
experience.
  • Loading branch information
CendioOssman committed Feb 5, 2025
1 parent c821783 commit 0b5e968
Show file tree
Hide file tree
Showing 2 changed files with 140 additions and 31 deletions.
55 changes: 42 additions & 13 deletions core/rfb.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ export default class RFB extends EventTargetMixin {
this._supportsSetDesktopSize = false;
this._screenID = 0;
this._screenFlags = 0;
this._pendingRemoteResize = false;
this._lastResize = 0;

this._qemuExtKeyEventSupported = false;

Expand Down Expand Up @@ -736,15 +738,9 @@ export default class RFB extends EventTargetMixin {
this._saveExpectedClientSize();
});

if (this._resizeSession) {
// Request changing the resolution of the remote display to
// the size of the local browser viewport.

// In order to not send multiple requests before the browser-resize
// is finished we wait 0.5 seconds before sending the request.
clearTimeout(this._resizeTimeout);
this._resizeTimeout = setTimeout(this._requestRemoteResize.bind(this), 500);
}
// Request changing the resolution of the remote display to
// the size of the local browser viewport.
this._requestRemoteResize();
}

// Update state of clipping in Display object, and make sure the
Expand Down Expand Up @@ -794,16 +790,39 @@ export default class RFB extends EventTargetMixin {
// Requests a change of remote desktop size. This message is an extension
// and may only be sent if we have received an ExtendedDesktopSize message
_requestRemoteResize() {
clearTimeout(this._resizeTimeout);
this._resizeTimeout = null;
if (!this._resizeSession) {
return;
}
if (this._viewOnly) {
return;
}
if (!this._supportsSetDesktopSize) {
return;
}

// Rate limit to one pending resize at a time
if (this._pendingRemoteResize) {
return;
}

if (!this._resizeSession || this._viewOnly ||
!this._supportsSetDesktopSize) {
// And no more than once every 100ms
if ((Date.now() - this._lastResize) < 100) {
clearTimeout(this._resizeTimeout);
this._resizeTimeout = setTimeout(this._requestRemoteResize.bind(this),
100 - (Date.now() - this._lastResize));
return;
}
this._resizeTimeout = null;

const size = this._screenSize();

// Do we actually change anything?
if (size.w === this._fbWidth && size.h === this._fbHeight) {
return;
}

this._pendingRemoteResize = true;
this._lastResize = Date.now();
RFB.messages.setDesktopSize(this._sock,
Math.floor(size.w), Math.floor(size.h),
this._screenID, this._screenFlags);
Expand Down Expand Up @@ -2913,6 +2932,10 @@ export default class RFB extends EventTargetMixin {
* 2 - another client requested the resize
*/

if (this._FBU.x === 1) {
this._pendingRemoteResize = false;
}

// We need to handle errors when we requested the resize.
if (this._FBU.x === 1 && this._FBU.y !== 0) {
let msg = "";
Expand Down Expand Up @@ -2945,6 +2968,12 @@ export default class RFB extends EventTargetMixin {
this._requestRemoteResize();
}

if (this._FBU.x === 1 && this._FBU.y === 0) {
// We might have resized again whilst waiting for the
// previous request, so check if we are in sync
this._requestRemoteResize();
}

return true;
}

Expand Down
116 changes: 98 additions & 18 deletions tests/test.rfb.js
Original file line number Diff line number Diff line change
Expand Up @@ -680,7 +680,6 @@ describe('Remote Frame Buffer protocol client', function () {
// The resize will cause scrollbars on the container, this causes a
// resize observation in the browsers
fakeResizeObserver.fire();
clock.tick(1000);

// FIXME: Display implicitly calls viewportChangeSize() when
// resizing the framebuffer, hence calledTwice.
Expand Down Expand Up @@ -1042,7 +1041,6 @@ describe('Remote Frame Buffer protocol client', function () {
// The resize will cause scrollbars on the container, this causes a
// resize observation in the browsers
fakeResizeObserver.fire();
clock.tick(1000);

expect(client._display.autoscale).to.have.been.calledOnce;
expect(client._display.autoscale).to.have.been.calledWith(70, 80);
Expand Down Expand Up @@ -1079,6 +1077,7 @@ describe('Remote Frame Buffer protocol client', function () {
let height = RFB.messages.setDesktopSize.args[0][2];
sendExtendedDesktopSize(client, 1, 0, width, height, 0x7890abcd, 0x12345678);
RFB.messages.setDesktopSize.resetHistory();
clock.tick(10000);
}
});

Expand All @@ -1093,7 +1092,6 @@ describe('Remote Frame Buffer protocol client', function () {
container.style.width = '40px';
container.style.height = '50px';
fakeResizeObserver.fire();
clock.tick(1000);
expect(RFB.messages.setDesktopSize).to.not.have.been.called;

client.resizeSession = true;
Expand Down Expand Up @@ -1132,7 +1130,6 @@ describe('Remote Frame Buffer protocol client', function () {
container.style.width = '40px';
container.style.height = '50px';
fakeResizeObserver.fire();
clock.tick(1000);

expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
expect(RFB.messages.setDesktopSize).to.have.been.calledWith(
Expand All @@ -1143,21 +1140,19 @@ describe('Remote Frame Buffer protocol client', function () {
container.style.width = '40px';
container.style.height = '50px';
fakeResizeObserver.fire();
clock.tick(1000);

expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
expect(RFB.messages.setDesktopSize).to.have.been.calledWith(
sinon.match.object, 40, 50, 0x7890abcd, 0x12345678);

// Server responds with the requested size 40x50
sendExtendedDesktopSize(client, 1, 0, 40, 50, 0x7890abcd, 0x12345678);
clock.tick(1000);

RFB.messages.setDesktopSize.resetHistory();

// size is still 40x50
fakeResizeObserver.fire();
clock.tick(1000);
fakeResizeObserver.fire();

expect(RFB.messages.setDesktopSize).to.not.have.been.called;
});
Expand All @@ -1166,7 +1161,6 @@ describe('Remote Frame Buffer protocol client', function () {
container.style.width = '40px';
container.style.height = '50px';
fakeResizeObserver.fire();
clock.tick(1000);

expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
expect(RFB.messages.setDesktopSize).to.have.been.calledWith(
Expand All @@ -1175,45 +1169,135 @@ describe('Remote Frame Buffer protocol client', function () {
sendExtendedDesktopSize(client, 1, 0, 40, 50, 0x7890abcd, 0x12345678);
RFB.messages.setDesktopSize.resetHistory();

clock.tick(1000);
container.style.width = '70px';
container.style.height = '80px';
fakeResizeObserver.fire();
clock.tick(1000);

expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
expect(RFB.messages.setDesktopSize).to.have.been.calledWith(
sinon.match.object, 70, 80, 0x7890abcd, 0x12345678);
});

it('should not resize until the container size is stable', function () {
it('should rate limit resizes', function () {
container.style.width = '20px';
container.style.height = '30px';
fakeResizeObserver.fire();
clock.tick(400);

expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
expect(RFB.messages.setDesktopSize).to.have.been.calledWith(
sinon.match.object, 20, 30, 0x7890abcd, 0x12345678);

sendExtendedDesktopSize(client, 1, 0, 20, 30, 0x7890abcd, 0x12345678);
RFB.messages.setDesktopSize.resetHistory();

clock.tick(20);

container.style.width = '30px';
container.style.height = '40px';
fakeResizeObserver.fire();

expect(RFB.messages.setDesktopSize).to.not.have.been.called;

clock.tick(20);

container.style.width = '40px';
container.style.height = '50px';
fakeResizeObserver.fire();
clock.tick(400);

expect(RFB.messages.setDesktopSize).to.not.have.been.called;

clock.tick(200);
clock.tick(80);

expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
expect(RFB.messages.setDesktopSize).to.have.been.calledWith(
sinon.match.object, 40, 50, 0x7890abcd, 0x12345678);
});

it('should not have overlapping resize requests', function () {
container.style.width = '40px';
container.style.height = '50px';
fakeResizeObserver.fire();

expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;

RFB.messages.setDesktopSize.resetHistory();

clock.tick(1000);
container.style.width = '20px';
container.style.height = '30px';
fakeResizeObserver.fire();

expect(RFB.messages.setDesktopSize).to.not.have.been.called;
});

it('should finalize any pending resizes', function () {
container.style.width = '40px';
container.style.height = '50px';
fakeResizeObserver.fire();

expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;

RFB.messages.setDesktopSize.resetHistory();

clock.tick(1000);
container.style.width = '20px';
container.style.height = '30px';
fakeResizeObserver.fire();

expect(RFB.messages.setDesktopSize).to.not.have.been.called;

// Server responds with the requested size 40x50
sendExtendedDesktopSize(client, 1, 0, 40, 50, 0x7890abcd, 0x12345678);

expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
expect(RFB.messages.setDesktopSize).to.have.been.calledWith(
sinon.match.object, 20, 30, 0x7890abcd, 0x12345678);
});

it('should not finalize any pending resize if not needed', function () {
container.style.width = '40px';
container.style.height = '50px';
fakeResizeObserver.fire();

expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;

RFB.messages.setDesktopSize.resetHistory();

// Server responds with the requested size 40x50
sendExtendedDesktopSize(client, 1, 0, 40, 50, 0x7890abcd, 0x12345678);

expect(RFB.messages.setDesktopSize).to.not.have.been.called;
});

it('should not finalize any pending resizes on errors', function () {
container.style.width = '40px';
container.style.height = '50px';
fakeResizeObserver.fire();

expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;

RFB.messages.setDesktopSize.resetHistory();

clock.tick(1000);
container.style.width = '20px';
container.style.height = '30px';
fakeResizeObserver.fire();

expect(RFB.messages.setDesktopSize).to.not.have.been.called;

// Server failed the requested size 40x50
sendExtendedDesktopSize(client, 1, 1, 40, 50, 0x7890abcd, 0x12345678);

expect(RFB.messages.setDesktopSize).to.not.have.been.called;
});

it('should not resize when resize is disabled', function () {
client._resizeSession = false;

container.style.width = '40px';
container.style.height = '50px';
fakeResizeObserver.fire();
clock.tick(1000);

expect(RFB.messages.setDesktopSize).to.not.have.been.called;
});
Expand All @@ -1224,7 +1308,6 @@ describe('Remote Frame Buffer protocol client', function () {
container.style.width = '40px';
container.style.height = '50px';
fakeResizeObserver.fire();
clock.tick(1000);

expect(RFB.messages.setDesktopSize).to.not.have.been.called;
});
Expand All @@ -1235,7 +1318,6 @@ describe('Remote Frame Buffer protocol client', function () {
container.style.width = '40px';
container.style.height = '50px';
fakeResizeObserver.fire();
clock.tick(1000);

expect(RFB.messages.setDesktopSize).to.not.have.been.called;
});
Expand All @@ -1248,15 +1330,13 @@ describe('Remote Frame Buffer protocol client', function () {
sendExtendedDesktopSize(client, 0, 0, 100, 100, 0xabababab, 0x11223344);
// The scrollbars cause the ResizeObserver to fire
fakeResizeObserver.fire();
clock.tick(1000);

expect(RFB.messages.setDesktopSize).to.not.have.been.called;

// An actual size change must not be ignored afterwards
container.style.width = '120px';
container.style.height = '130px';
fakeResizeObserver.fire();
clock.tick(1000);

expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
expect(RFB.messages.setDesktopSize).to.have.been.calledWith(
Expand Down

0 comments on commit 0b5e968

Please sign in to comment.