Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 55 additions & 4 deletions images/chromium-headful/client/src/components/video.vue
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,10 @@
return this.$accessor.connecting
}

get controlling() {
return this.$accessor.remote.controlling
}

get hosting() {
return this.$accessor.remote.hosting
}
Expand Down Expand Up @@ -804,14 +808,18 @@
first.target.dispatchEvent(simulatedEvent)
}

isMouseDown = false

onMouseDown(e: MouseEvent) {
this.unmuteOnInteraction()
this.isMouseDown = true

if (!this.hosting) {
this.$emit('control-attempt', e)
if (this.locked) {
return
}

if (!this.hosting || this.locked) {
if (!this.controlling) {
this.implicitHostingRequest(e)
return
}

Expand All @@ -820,14 +828,57 @@
}

onMouseUp(e: MouseEvent) {
if (!this.hosting || this.locked) {
// only if we are the one who started the mouse down
if (!this.isMouseDown) return
this.isMouseDown = false

if (this.locked) {
return
}

if (!this.controlling) {
this.implicitHostingRequest(e)
return
}

this.sendMousePos(e)
this.$client.sendData('mouseup', { key: e.button + 1 })
}

private reqMouseDown: MouseEvent | null = null
private reqMouseUp: MouseEvent | null = null

@Watch('controlling')
onControlChange(controlling: boolean) {
if (controlling && this.reqMouseDown) {
this.onMouseDown(this.reqMouseDown)
}

if (controlling && this.reqMouseUp) {
this.onMouseUp(this.reqMouseUp)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Buffered mouseup replay skipped

Medium Severity

If control is lost after a forwarded mousedown but before mouseup, the physical mouseup clears isMouseDown and only stores reqMouseUp because controlling is false. When controlling becomes true again, @Watch('controlling') replays via onMouseUp, which returns immediately when isMouseDown is false, so the remote never gets mouseup. A later implicit mousedown clears reqMouseUp without flushing that state, leaving a stuck pressed button on the remote.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0388493. Configure here.

}

this.reqMouseDown = null
this.reqMouseUp = null
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incomplete click replay stuck button

Medium Severity

Under implicit hosting, a mousedown on the overlay stores reqMouseDown and sends control/request, but reqMouseUp is only set when onMouseUp runs on the same overlay. If the pointer is released outside the overlay before control is granted, onControlChange replays only onMouseDown, sending a remote mousedown with no matching mouseup.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0388493. Configure here.


implicitHostingRequest(e: MouseEvent) {
if (this.implicitHosting) {
if (e.type === 'mousedown') {
this.reqMouseDown = e
this.reqMouseUp = null
this.$accessor.remote.request()
} else if (e.type === 'mouseup') {
this.reqMouseUp = e
}
return
}

if (e.type === 'mousedown') {
this.$emit('control-attempt', e)
}
}

onMouseMove(e: MouseEvent) {
if (!this.hosting || this.locked) {
return
Expand Down
5 changes: 4 additions & 1 deletion images/chromium-headful/client/src/store/remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ export const state = () => ({
})

export const getters = getterTree(state, {
controlling: (state, getters, root) => {
return root.user.id === state.id
},
hosting: (state, getters, root) => {
return root.user.id === state.id || state.implicitHosting
},
Expand Down Expand Up @@ -89,7 +92,7 @@ export const actions = actionTree(
},

request({ getters }) {
if (!accessor.connected || getters.hosting) {
if (!accessor.connected || getters.controlling) {
return
}

Expand Down