diff --git a/lib/index.js b/lib/index.js index 6071263..fa4d0f2 100644 --- a/lib/index.js +++ b/lib/index.js @@ -2485,6 +2485,7 @@ class RTCPeerConnection extends SimpleEventTarget { this._iceConnectionState = "new"; this._iceGatheringState = "new"; this._signalingState = "stable"; + this._lastDispatchedSignalingState = "stable"; this._native = null; this._nativeCertificates = null; } @@ -2805,8 +2806,7 @@ class RTCPeerConnection extends SimpleEventTarget { this.dispatchEvent(makeEvent("icegatheringstatechange")); this._iceTransport()?.dispatchEvent(makeEvent("gatheringstatechange")); } - if (previousState !== this._signalingState) - this.dispatchEvent(makeEvent("signalingstatechange")); + if (previousState !== this._signalingState) this._dispatchSignalingStateChange(); await nextTask(); if (this._suppressNextNativeSignalingState === "stable") this._suppressNextNativeSignalingState = null; @@ -2829,8 +2829,7 @@ class RTCPeerConnection extends SimpleEventTarget { this._signalingState = "have-local-pranswer"; this._refreshIceRole(); this._updateSctpTransport(); - if (previousState !== this._signalingState) - this.dispatchEvent(makeEvent("signalingstatechange")); + if (previousState !== this._signalingState) this._dispatchSignalingStateChange(); await nextTask(); return; } @@ -2974,7 +2973,7 @@ class RTCPeerConnection extends SimpleEventTarget { this._clearNegotiationNeededIfDataMLineIsPresent(); this._refreshIceRole(); if (manuallyUpdatedSignalingState && previousSignalingState !== this._signalingState) - this.dispatchEvent(makeEvent("signalingstatechange")); + this._dispatchSignalingStateChange(); if (appliedIceRestart) { if (previousIceGatheringState === "complete") this._queueSyntheticIceRestartGathering(); this._clearIceRestartRequest(); @@ -3062,8 +3061,7 @@ class RTCPeerConnection extends SimpleEventTarget { this._signalingState = "have-remote-pranswer"; this._refreshIceRole(); this._updateSctpTransport(); - if (previousState !== this._signalingState) - this.dispatchEvent(makeEvent("signalingstatechange")); + if (previousState !== this._signalingState) this._dispatchSignalingStateChange(); await nextTask(); return; } @@ -3104,8 +3102,7 @@ class RTCPeerConnection extends SimpleEventTarget { this._signalingState = "stable"; this._refreshIceRole(); this._updateSctpTransport(); - if (previousState !== this._signalingState) - this.dispatchEvent(makeEvent("signalingstatechange")); + if (previousState !== this._signalingState) this._dispatchSignalingStateChange(); await nextTask(); if (this._suppressNextNativeSignalingState === "stable") this._suppressNextNativeSignalingState = null; @@ -3124,8 +3121,7 @@ class RTCPeerConnection extends SimpleEventTarget { this._armDataChannelNativeCloseSuppression(); this._refreshIceRole(); this._updateSctpTransport(); - if (previousState !== this._signalingState) - this.dispatchEvent(makeEvent("signalingstatechange")); + if (previousState !== this._signalingState) this._dispatchSignalingStateChange(); await nextTask(); return; } @@ -3148,8 +3144,7 @@ class RTCPeerConnection extends SimpleEventTarget { this._refreshIceRole(); this._assignDataChannelIdsFromDtlsRole(); this._updateSctpTransport(); - if (previousState !== this._signalingState) - this.dispatchEvent(makeEvent("signalingstatechange")); + if (previousState !== this._signalingState) this._dispatchSignalingStateChange(); await nextTask(); return; } @@ -3161,8 +3156,7 @@ class RTCPeerConnection extends SimpleEventTarget { this._syncSignalingStateFromDescriptions(); this._refreshIceRole(); this._updateSctpTransport(); - if (previousState !== this._signalingState) - this.dispatchEvent(makeEvent("signalingstatechange")); + if (previousState !== this._signalingState) this._dispatchSignalingStateChange(); await nextTask(); return; } @@ -3180,8 +3174,7 @@ class RTCPeerConnection extends SimpleEventTarget { this._refreshIceRole(); this._assignDataChannelIdsFromDtlsRole(); this._updateSctpTransport(); - if (previousState !== this._signalingState) - this.dispatchEvent(makeEvent("signalingstatechange")); + if (previousState !== this._signalingState) this._dispatchSignalingStateChange(); await nextTask(); return; } @@ -3204,7 +3197,7 @@ class RTCPeerConnection extends SimpleEventTarget { } this._refreshIceRole(); this._updateSctpTransport(); - this.dispatchEvent(makeEvent("signalingstatechange")); + this._dispatchSignalingStateChange(); await nextTask(); return; } @@ -3251,7 +3244,7 @@ class RTCPeerConnection extends SimpleEventTarget { if (previousSignalingState !== this._signalingState) { suppressedNativeSignalingState = this._signalingState; this._suppressNextNativeSignalingState = suppressedNativeSignalingState; - this.dispatchEvent(makeEvent("signalingstatechange")); + this._dispatchSignalingStateChange(); } if (normalized.type !== "offer") { for (const candidate of this._pendingIce.splice(0)) await this.addIceCandidate(candidate); @@ -3385,7 +3378,7 @@ class RTCPeerConnection extends SimpleEventTarget { for (const channel of this._channels.values()) { if (channel.readyState !== "closed") channel._handleClose(); } - this.dispatchEvent(makeEvent("signalingstatechange")); + this._dispatchSignalingStateChange(); this.dispatchEvent(makeEvent("iceconnectionstatechange")); this.dispatchEvent(makeEvent("connectionstatechange")); } @@ -4132,6 +4125,11 @@ class RTCPeerConnection extends SimpleEventTarget { this._signalingState = this._descriptionSignalingState(); } + _dispatchSignalingStateChange() { + this._lastDispatchedSignalingState = this._signalingState; + this.dispatchEvent(makeEvent("signalingstatechange")); + } + _setPendingLocalDescription(description) { this._pendingLocalDescription = description; this._localDescription = description || this._currentLocalDescription; @@ -4192,8 +4190,7 @@ class RTCPeerConnection extends SimpleEventTarget { this.dispatchEvent(makeEvent("icegatheringstatechange")); this._iceTransport()?.dispatchEvent(makeEvent("gatheringstatechange")); } - if (previousState !== this._signalingState) - this.dispatchEvent(makeEvent("signalingstatechange")); + if (previousState !== this._signalingState) this._dispatchSignalingStateChange(); await nextTask(); if (this._suppressNextNativeSignalingState === "stable") this._suppressNextNativeSignalingState = null; @@ -4436,8 +4433,7 @@ class RTCPeerConnection extends SimpleEventTarget { this._signalingState = "stable"; this._refreshIceRole(); this._updateSctpTransport(); - if (previousState !== this._signalingState) - this.dispatchEvent(makeEvent("signalingstatechange")); + if (previousState !== this._signalingState) this._dispatchSignalingStateChange(); await nextTask(); } @@ -4449,8 +4445,7 @@ class RTCPeerConnection extends SimpleEventTarget { this._syncSignalingStateFromDescriptions(); this._refreshIceRole(); this._updateSctpTransport(); - if (previousState !== this._signalingState) - this.dispatchEvent(makeEvent("signalingstatechange")); + if (previousState !== this._signalingState) this._dispatchSignalingStateChange(); await nextTask(); } @@ -4464,8 +4459,7 @@ class RTCPeerConnection extends SimpleEventTarget { this._syncSignalingStateFromDescriptions(); this._refreshIceRole(); this._updateSctpTransport(); - if (previousState !== this._signalingState) - this.dispatchEvent(makeEvent("signalingstatechange")); + if (previousState !== this._signalingState) this._dispatchSignalingStateChange(); await nextTask(); } @@ -4484,7 +4478,7 @@ class RTCPeerConnection extends SimpleEventTarget { this._clearNegotiationNeededIfDataMLineIsPresent(); this._refreshIceRole(); this._updateSctpTransport(); - this.dispatchEvent(makeEvent("signalingstatechange")); + this._dispatchSignalingStateChange(); await nextTask(); } @@ -4648,9 +4642,14 @@ class RTCPeerConnection extends SimpleEventTarget { break; } if (event.state !== this._descriptionSignalingState()) break; - if (event.state === this._signalingState) break; + if ( + event.state === this._signalingState && + event.state === this._lastDispatchedSignalingState + ) { + break; + } this._signalingState = event.state; - this.dispatchEvent(makeEvent("signalingstatechange")); + this._dispatchSignalingStateChange(); break; } } diff --git a/test/basic.test.js b/test/basic.test.js index f97198e..d8a0f11 100644 --- a/test/basic.test.js +++ b/test/basic.test.js @@ -231,6 +231,31 @@ test("setRemoteDescription ignores late duplicate native signalingstatechange", assert.deepEqual(states, ["have-remote-offer"]); }); +test("setLocalDescription emits repeated offer states across renegotiation", async (t) => { + const offerer = new RTCPeerConnection(); + const answerer = new RTCPeerConnection(); + t.after(() => closeAllAndWait(offerer, answerer)); + offerer.createDataChannel("initial"); + + const states = []; + offerer.addEventListener("signalingstatechange", () => states.push(offerer.signalingState)); + + const offer = await offerer.createOffer(); + await offerer.setLocalDescription(offer); + await answerer.setRemoteDescription(offer); + const answer = await answerer.createAnswer(); + await answerer.setLocalDescription(answer); + await offerer.setRemoteDescription(answer); + + assert.equal(offerer.signalingState, "stable"); + offerer.createDataChannel("renegotiated"); + const reoffer = await offerer.createOffer(); + await offerer.setLocalDescription(reoffer); + + assert.equal(offerer.signalingState, "have-local-offer"); + assert.deepEqual(states, ["have-local-offer", "stable", "have-local-offer"]); +}); + test("once event listeners are removed before invocation", () => { const target = new EventTarget(); let calls = 0;