From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- dom/media/webrtc/tests/crashtests/1770075.html | 8 + dom/media/webrtc/tests/crashtests/1789908.html | 25 + dom/media/webrtc/tests/crashtests/1799168.html | 16 + dom/media/webrtc/tests/crashtests/1816708.html | 21 + dom/media/webrtc/tests/crashtests/1821477.html | 16 + dom/media/webrtc/tests/crashtests/crashtests.list | 7 + dom/media/webrtc/tests/fuzztests/moz.build | 22 + .../webrtc/tests/fuzztests/sdp_parser_libfuzz.cpp | 30 + .../mochitests/NetworkPreparationChromeScript.js | 43 + .../tests/mochitests/addTurnsSelfsignedCert.js | 32 + dom/media/webrtc/tests/mochitests/blacksilence.js | 134 ++ dom/media/webrtc/tests/mochitests/dataChannel.js | 352 +++ dom/media/webrtc/tests/mochitests/head.js | 1445 ++++++++++++ .../tests/mochitests/helpers_from_wpt/sdp.js | 889 +++++++ dom/media/webrtc/tests/mochitests/iceTestUtils.js | 302 +++ .../tests/mochitests/identity/identityPcTest.js | 79 + .../webrtc/tests/mochitests/identity/idp-bad.js | 1 + .../webrtc/tests/mochitests/identity/idp-min.js | 24 + .../mochitests/identity/idp-redirect-http-trick.js | 3 + .../identity/idp-redirect-http-trick.js^headers^ | 2 + .../tests/mochitests/identity/idp-redirect-http.js | 3 + .../identity/idp-redirect-http.js^headers^ | 2 + .../identity/idp-redirect-https-double.js | 3 + .../identity/idp-redirect-https-double.js^headers^ | 2 + .../identity/idp-redirect-https-odd-path.js | 3 + .../idp-redirect-https-odd-path.js^headers^ | 2 + .../mochitests/identity/idp-redirect-https.js | 3 + .../identity/idp-redirect-https.js^headers^ | 2 + dom/media/webrtc/tests/mochitests/identity/idp.js | 119 + dom/media/webrtc/tests/mochitests/identity/idp.sjs | 18 + .../webrtc/tests/mochitests/identity/login.html | 31 + .../webrtc/tests/mochitests/identity/mochitest.ini | 47 + .../mochitests/identity/test_fingerprints.html | 91 + .../identity/test_getIdentityAssertion.html | 101 + .../tests/mochitests/identity/test_idpproxy.html | 178 ++ .../mochitests/identity/test_loginNeeded.html | 72 + .../test_peerConnection_asymmetricIsolation.html | 31 + .../identity/test_peerConnection_peerIdentity.html | 21 + .../identity/test_setIdentityProvider.html | 67 + .../test_setIdentityProviderWithErrors.html | 57 + .../webrtc/tests/mochitests/mediaStreamPlayback.js | 241 ++ dom/media/webrtc/tests/mochitests/mochitest.ini | 65 + .../tests/mochitests/mochitest_datachannel.ini | 52 + .../tests/mochitests/mochitest_getusermedia.ini | 105 + .../tests/mochitests/mochitest_peerconnection.ini | 311 +++ dom/media/webrtc/tests/mochitests/network.js | 16 + dom/media/webrtc/tests/mochitests/nonTrickleIce.js | 97 + dom/media/webrtc/tests/mochitests/parser_rtp.js | 131 + dom/media/webrtc/tests/mochitests/pc.js | 2495 ++++++++++++++++++++ .../peerconnection_audio_forced_sample_rate.js | 32 + dom/media/webrtc/tests/mochitests/sdpUtils.js | 398 ++++ dom/media/webrtc/tests/mochitests/simulcast.js | 232 ++ dom/media/webrtc/tests/mochitests/stats.js | 1596 +++++++++++++ dom/media/webrtc/tests/mochitests/templates.js | 615 +++++ .../webrtc/tests/mochitests/test_1488832.html | 37 + .../webrtc/tests/mochitests/test_1717318.html | 26 + dom/media/webrtc/tests/mochitests/test_a_noOp.html | 32 + .../mochitests/test_dataChannel_basicAudio.html | 25 + .../test_dataChannel_basicAudioVideo.html | 26 + .../test_dataChannel_basicAudioVideoCombined.html | 26 + .../test_dataChannel_basicAudioVideoNoBundle.html | 27 + .../mochitests/test_dataChannel_basicDataOnly.html | 24 + .../mochitests/test_dataChannel_basicVideo.html | 25 + .../mochitests/test_dataChannel_bug1013809.html | 27 + ...test_dataChannel_dataOnlyBufferedAmountLow.html | 25 + .../mochitests/test_dataChannel_dtlsVersions.html | 38 + .../test_dataChannel_hostnameObfuscation.html | 59 + .../tests/mochitests/test_dataChannel_noOffer.html | 33 + .../tests/mochitests/test_dataChannel_stats.html | 50 + .../mochitests/test_defaultAudioConstraints.html | 80 + .../tests/mochitests/test_enumerateDevices.html | 141 ++ .../test_enumerateDevices_getUserMediaFake.html | 63 + .../mochitests/test_enumerateDevices_iframe.html | 28 + .../test_enumerateDevices_iframe_pre_gum.html | 22 + .../mochitests/test_enumerateDevices_legacy.html | 147 ++ .../test_enumerateDevices_navigation.html | 54 + .../mochitests/test_fingerprinting_resistance.html | 112 + .../tests/mochitests/test_forceSampleRate.html | 23 + .../test_getUserMedia_GC_MediaStream.html | 59 + .../test_getUserMedia_active_autoplay.html | 61 + .../test_getUserMedia_addTrackRemoveTrack.html | 169 ++ ...t_getUserMedia_addtrack_removetrack_events.html | 110 + .../mochitests/test_getUserMedia_audioCapture.html | 104 + .../test_getUserMedia_audioConstraints.html | 93 + ...erMedia_audioConstraints_concurrentIframes.html | 157 ++ ...erMedia_audioConstraints_concurrentStreams.html | 123 + .../mochitests/test_getUserMedia_basicAudio.html | 27 + .../test_getUserMedia_basicAudio_loopback.html | 99 + .../test_getUserMedia_basicScreenshare.html | 260 ++ .../test_getUserMedia_basicTabshare.html | 67 + .../mochitests/test_getUserMedia_basicVideo.html | 30 + .../test_getUserMedia_basicVideoAudio.html | 30 + ...erMedia_basicVideo_playAfterLoadedmetadata.html | 42 + .../test_getUserMedia_basicWindowshare.html | 39 + .../mochitests/test_getUserMedia_bug1223696.html | 54 + .../mochitests/test_getUserMedia_callbacks.html | 35 + .../mochitests/test_getUserMedia_constraints.html | 166 ++ .../test_getUserMedia_cubebDisabled.html | 42 + ...test_getUserMedia_cubebDisabledFakeStreams.html | 43 + .../mochitests/test_getUserMedia_getTrackById.html | 50 + .../mochitests/test_getUserMedia_gumWithinGum.html | 38 + .../test_getUserMedia_loadedmetadata.html | 39 + ...est_getUserMedia_mediaElementCapture_audio.html | 116 + ...st_getUserMedia_mediaElementCapture_tracks.html | 179 ++ ...est_getUserMedia_mediaElementCapture_video.html | 91 + .../test_getUserMedia_mediaStreamClone.html | 258 ++ .../test_getUserMedia_mediaStreamConstructors.html | 171 ++ .../test_getUserMedia_mediaStreamTrackClone.html | 170 ++ .../test_getUserMedia_nonDefaultRate.html | 37 + .../mochitests/test_getUserMedia_peerIdentity.html | 51 + .../mochitests/test_getUserMedia_permission.html | 104 + .../test_getUserMedia_permission_iframe.html | 30 + .../test_getUserMedia_playAudioTwice.html | 25 + .../test_getUserMedia_playVideoAudioTwice.html | 26 + .../test_getUserMedia_playVideoTwice.html | 26 + .../mochitests/test_getUserMedia_scarySources.html | 51 + .../test_getUserMedia_spinEventLoop.html | 28 + .../test_getUserMedia_trackCloneCleanup.html | 32 + .../mochitests/test_getUserMedia_trackEnded.html | 68 + .../webrtc/tests/mochitests/test_groupId.html | 53 + .../webrtc/tests/mochitests/test_multi_mics.html | 66 + .../tests/mochitests/test_ondevicechange.html | 180 ++ ...nection_addAudioTrackToExistingVideoStream.html | 55 + .../test_peerConnection_addDataChannel.html | 33 + ...test_peerConnection_addDataChannelNoBundle.html | 44 + .../test_peerConnection_addSecondAudioStream.html | 45 + ...eerConnection_addSecondAudioStreamNoBundle.html | 53 + .../test_peerConnection_addSecondVideoStream.html | 53 + ...eerConnection_addSecondVideoStreamNoBundle.html | 60 + ...peerConnection_addtrack_removetrack_events.html | 75 + ...eerConnection_answererAddSecondAudioStream.html | 32 + .../test_peerConnection_audioChannels.html | 102 + .../test_peerConnection_audioCodecs.html | 81 + ...st_peerConnection_audioContributingSources.html | 144 ++ ...onnection_audioRenegotiationInactiveAnswer.html | 69 + ...peerConnection_audioSynchronizationSources.html | 95 + ..._audioSynchronizationSourcesUnidirectional.html | 54 + .../mochitests/test_peerConnection_basicAudio.html | 25 + ...onnection_basicAudioDynamicPtMissingRtpmap.html | 36 + .../test_peerConnection_basicAudioNATRelay.html | 47 + .../test_peerConnection_basicAudioNATRelayTCP.html | 42 + ...onnection_basicAudioNATRelayTCPWithStun300.html | 54 + .../test_peerConnection_basicAudioNATRelayTLS.html | 41 + ...erConnection_basicAudioNATRelayWithStun300.html | 53 + .../test_peerConnection_basicAudioNATSrflx.html | 44 + ...est_peerConnection_basicAudioNoisyUDPBlock.html | 41 + ...test_peerConnection_basicAudioPcmaPcmuOnly.html | 39 + .../test_peerConnection_basicAudioRelayPolicy.html | 83 + .../test_peerConnection_basicAudioRequireEOC.html | 35 + ...ection_basicAudioVerifyRtpHeaderExtensions.html | 63 + .../test_peerConnection_basicAudioVideo.html | 24 + ...est_peerConnection_basicAudioVideoCombined.html | 24 + ...est_peerConnection_basicAudioVideoNoBundle.html | 25 + ...onnection_basicAudioVideoNoBundleNoRtcpMux.html | 39 + ...st_peerConnection_basicAudioVideoNoRtcpMux.html | 38 + ...peerConnection_basicAudioVideoTransceivers.html | 31 + ...peerConnection_basicAudioVideoVerifyExtmap.html | 97 + ...ection_basicAudioVideoVerifyExtmapSendonly.html | 97 + ...ction_basicAudioVideoVerifyTooLongMidFails.html | 47 + ...erConnection_basicAudio_forced_higher_rate.html | 19 + ...eerConnection_basicAudio_forced_lower_rate.html | 19 + .../test_peerConnection_basicH264Video.html | 26 + .../test_peerConnection_basicScreenshare.html | 56 + .../mochitests/test_peerConnection_basicVideo.html | 23 + ...ection_basicVideoVerifyRtpHeaderExtensions.html | 82 + .../test_peerConnection_basicWindowshare.html | 25 + .../mochitests/test_peerConnection_bug1013809.html | 25 + .../mochitests/test_peerConnection_bug1042791.html | 36 + .../mochitests/test_peerConnection_bug1227781.html | 37 + .../mochitests/test_peerConnection_bug1512281.html | 47 + .../mochitests/test_peerConnection_bug1773067.html | 32 + .../mochitests/test_peerConnection_bug822674.html | 26 + .../mochitests/test_peerConnection_bug825703.html | 140 ++ .../mochitests/test_peerConnection_bug827843.html | 50 + .../mochitests/test_peerConnection_bug834153.html | 36 + .../mochitests/test_peerConnection_callbacks.html | 86 + ...est_peerConnection_captureStream_canvas_2d.html | 81 + ...rConnection_captureStream_canvas_2d_noSSRC.html | 83 + ..._peerConnection_captureStream_canvas_webgl.html | 130 + .../test_peerConnection_capturedVideo.html | 81 + .../test_peerConnection_certificates.html | 185 ++ .../test_peerConnection_checkPacketDumpHook.html | 107 + .../mochitests/test_peerConnection_close.html | 134 ++ .../test_peerConnection_closeDuringIce.html | 79 + ...est_peerConnection_codecNegotiationFailure.html | 111 + .../test_peerConnection_constructedStream.html | 67 + ...peerConnection_disabledVideoPreNegotiation.html | 45 + .../test_peerConnection_encodingsNegotiation.html | 85 + .../test_peerConnection_errorCallbacks.html | 55 + .../test_peerConnection_extmapRenegotiation.html | 325 +++ ...nection_forwarding_basicAudioVideoCombined.html | 41 + ..._peerConnection_gatherWithSetConfiguration.html | 450 ++++ .../test_peerConnection_gatherWithStun300.html | 269 +++ .../test_peerConnection_gatherWithStun300IPv6.html | 283 +++ .../mochitests/test_peerConnection_glean.html | 488 ++++ .../mochitests/test_peerConnection_iceFailure.html | 84 + .../mochitests/test_peerConnection_insertDTMF.html | 76 + .../test_peerConnection_localReofferRollback.html | 44 + .../test_peerConnection_localRollback.html | 47 + .../test_peerConnection_maxFsConstraint.html | 112 + ...onnection_multiple_captureStream_canvas_2d.html | 115 + .../test_peerConnection_noTrickleAnswer.html | 25 + .../test_peerConnection_noTrickleOffer.html | 25 + .../test_peerConnection_noTrickleOfferAnswer.html | 26 + .../test_peerConnection_nonDefaultRate.html | 200 ++ ...t_peerConnection_offerRequiresReceiveAudio.html | 23 + ...t_peerConnection_offerRequiresReceiveVideo.html | 23 + ...rConnection_offerRequiresReceiveVideoAudio.html | 23 + .../test_peerConnection_portRestrictions.html | 63 + .../test_peerConnection_promiseSendOnly.html | 61 + .../test_peerConnection_recordReceiveTrack.html | 101 + .../mochitests/test_peerConnection_relayOnly.html | 60 + .../test_peerConnection_remoteReofferRollback.html | 50 + .../test_peerConnection_remoteRollback.html | 51 + .../test_peerConnection_removeAudioTrack.html | 57 + ...est_peerConnection_removeThenAddAudioTrack.html | 87 + ...Connection_removeThenAddAudioTrackNoBundle.html | 76 + ...est_peerConnection_removeThenAddVideoTrack.html | 98 + ...Connection_removeThenAddVideoTrackNoBundle.html | 89 + .../test_peerConnection_removeVideoTrack.html | 64 + ...st_peerConnection_renderAfterRenegotiation.html | 89 + ...ction_replaceNullTrackThenRenegotiateAudio.html | 53 + ...ction_replaceNullTrackThenRenegotiateVideo.html | 63 + .../test_peerConnection_replaceTrack.html | 187 ++ .../test_peerConnection_replaceTrack_camera.html | 48 + .../test_peerConnection_replaceTrack_disabled.html | 60 + ...est_peerConnection_replaceTrack_microphone.html | 46 + ...peerConnection_replaceVideoThenRenegotiate.html | 74 + .../mochitests/test_peerConnection_restartIce.html | 41 + .../test_peerConnection_restartIceBadAnswer.html | 58 + ...onnection_restartIceLocalAndRemoteRollback.html | 82 + ...eLocalAndRemoteRollbackNoSubsequentRestart.html | 77 + ...est_peerConnection_restartIceLocalRollback.html | 76 + ...restartIceLocalRollbackNoSubsequentRestart.html | 60 + .../test_peerConnection_restartIceNoBundle.html | 43 + ...peerConnection_restartIceNoBundleNoRtcpMux.html | 44 + .../test_peerConnection_restartIceNoRtcpMux.html | 43 + ...rConnection_restrictBandwidthTargetBitrate.html | 29 + ...t_peerConnection_restrictBandwidthWithTias.html | 30 + .../mochitests/test_peerConnection_rtcp_rsize.html | 81 + .../test_peerConnection_scaleResolution.html | 119 + ...onnection_scaleResolution_oldSetParameters.html | 122 + ...t_peerConnection_sender_and_receiver_stats.html | 73 + ...rConnection_setLocalAnswerInHaveLocalOffer.html | 34 + ...test_peerConnection_setLocalAnswerInStable.html | 34 + ...rConnection_setLocalOfferInHaveRemoteOffer.html | 31 + .../test_peerConnection_setParameters.html | 470 ++++ ..._peerConnection_setParameters_maxFramerate.html | 63 + ...etParameters_maxFramerate_oldSetParameters.html | 60 + ...rConnection_setParameters_oldSetParameters.html | 86 + ...ection_setParameters_scaleResolutionDownBy.html | 98 + ...ers_scaleResolutionDownBy_oldSetParameters.html | 96 + ...onnection_setRemoteAnswerInHaveRemoteOffer.html | 34 + ...est_peerConnection_setRemoteAnswerInStable.html | 34 + ...rConnection_setRemoteOfferInHaveLocalOffer.html | 37 + .../test_peerConnection_simulcastAnswer.html | 121 + ...peerConnection_simulcastAnswer_lowResFirst.html | 113 + ...mulcastAnswer_lowResFirst_oldSetParameters.html | 115 + ...onnection_simulcastAnswer_oldSetParameters.html | 115 + ...test_peerConnection_simulcastOddResolution.html | 183 ++ ...on_simulcastOddResolution_oldSetParameters.html | 172 ++ .../test_peerConnection_simulcastOffer.html | 109 + ..._peerConnection_simulcastOffer_lowResFirst.html | 109 + ...imulcastOffer_lowResFirst_oldSetParameters.html | 112 + ...Connection_simulcastOffer_oldSetParameters.html | 112 + .../mochitests/test_peerConnection_stats.html | 42 + .../test_peerConnection_stats_jitter.html | 58 + .../test_peerConnection_stats_oneway.html | 65 + .../test_peerConnection_stats_relayProtocol.html | 58 + .../test_peerConnection_stereoFmtpPref.html | 61 + .../test_peerConnection_syncSetDescription.html | 53 + .../test_peerConnection_telephoneEventFirst.html | 56 + ...t_peerConnection_threeUnbundledConnections.html | 134 ++ .../test_peerConnection_throwInCallbacks.html | 83 + .../mochitests/test_peerConnection_toJSON.html | 39 + .../test_peerConnection_trackDisabling.html | 108 + .../test_peerConnection_trackDisabling_clones.html | 162 ++ ...test_peerConnection_trackless_sender_stats.html | 56 + .../test_peerConnection_twoAudioStreams.html | 23 + ...t_peerConnection_twoAudioTracksInOneStream.html | 37 + .../test_peerConnection_twoAudioVideoStreams.html | 26 + ...eerConnection_twoAudioVideoStreamsCombined.html | 70 + ...ction_twoAudioVideoStreamsCombinedNoBundle.html | 107 + .../test_peerConnection_twoVideoStreams.html | 23 + ...t_peerConnection_twoVideoTracksInOneStream.html | 37 + ...erConnection_verifyAudioAfterRenegotiation.html | 99 + .../test_peerConnection_verifyDescriptions.html | 58 + ...erConnection_verifyVideoAfterRenegotiation.html | 123 + .../test_peerConnection_videoCodecs.html | 142 ++ ...onnection_videoRenegotiationInactiveAnswer.html | 95 + .../mochitests/test_peerConnection_webAudio.html | 43 + .../webrtc/tests/mochitests/test_selftest.html | 37 + .../webrtc/tests/mochitests/test_setSinkId.html | 83 + .../test_setSinkId_default_addTrack.html | 52 + .../mochitests/test_setSinkId_preMutedElement.html | 100 + .../tests/mochitests/test_unfocused_pref.html | 49 + dom/media/webrtc/tests/mochitests/turnConfig.js | 16 + 297 files changed, 29234 insertions(+) create mode 100644 dom/media/webrtc/tests/crashtests/1770075.html create mode 100644 dom/media/webrtc/tests/crashtests/1789908.html create mode 100644 dom/media/webrtc/tests/crashtests/1799168.html create mode 100644 dom/media/webrtc/tests/crashtests/1816708.html create mode 100644 dom/media/webrtc/tests/crashtests/1821477.html create mode 100644 dom/media/webrtc/tests/crashtests/crashtests.list create mode 100644 dom/media/webrtc/tests/fuzztests/moz.build create mode 100644 dom/media/webrtc/tests/fuzztests/sdp_parser_libfuzz.cpp create mode 100644 dom/media/webrtc/tests/mochitests/NetworkPreparationChromeScript.js create mode 100644 dom/media/webrtc/tests/mochitests/addTurnsSelfsignedCert.js create mode 100644 dom/media/webrtc/tests/mochitests/blacksilence.js create mode 100644 dom/media/webrtc/tests/mochitests/dataChannel.js create mode 100644 dom/media/webrtc/tests/mochitests/head.js create mode 100644 dom/media/webrtc/tests/mochitests/helpers_from_wpt/sdp.js create mode 100644 dom/media/webrtc/tests/mochitests/iceTestUtils.js create mode 100644 dom/media/webrtc/tests/mochitests/identity/identityPcTest.js create mode 100644 dom/media/webrtc/tests/mochitests/identity/idp-bad.js create mode 100644 dom/media/webrtc/tests/mochitests/identity/idp-min.js create mode 100644 dom/media/webrtc/tests/mochitests/identity/idp-redirect-http-trick.js create mode 100644 dom/media/webrtc/tests/mochitests/identity/idp-redirect-http-trick.js^headers^ create mode 100644 dom/media/webrtc/tests/mochitests/identity/idp-redirect-http.js create mode 100644 dom/media/webrtc/tests/mochitests/identity/idp-redirect-http.js^headers^ create mode 100644 dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-double.js create mode 100644 dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-double.js^headers^ create mode 100644 dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-odd-path.js create mode 100644 dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-odd-path.js^headers^ create mode 100644 dom/media/webrtc/tests/mochitests/identity/idp-redirect-https.js create mode 100644 dom/media/webrtc/tests/mochitests/identity/idp-redirect-https.js^headers^ create mode 100644 dom/media/webrtc/tests/mochitests/identity/idp.js create mode 100644 dom/media/webrtc/tests/mochitests/identity/idp.sjs create mode 100644 dom/media/webrtc/tests/mochitests/identity/login.html create mode 100644 dom/media/webrtc/tests/mochitests/identity/mochitest.ini create mode 100644 dom/media/webrtc/tests/mochitests/identity/test_fingerprints.html create mode 100644 dom/media/webrtc/tests/mochitests/identity/test_getIdentityAssertion.html create mode 100644 dom/media/webrtc/tests/mochitests/identity/test_idpproxy.html create mode 100644 dom/media/webrtc/tests/mochitests/identity/test_loginNeeded.html create mode 100644 dom/media/webrtc/tests/mochitests/identity/test_peerConnection_asymmetricIsolation.html create mode 100644 dom/media/webrtc/tests/mochitests/identity/test_peerConnection_peerIdentity.html create mode 100644 dom/media/webrtc/tests/mochitests/identity/test_setIdentityProvider.html create mode 100644 dom/media/webrtc/tests/mochitests/identity/test_setIdentityProviderWithErrors.html create mode 100644 dom/media/webrtc/tests/mochitests/mediaStreamPlayback.js create mode 100644 dom/media/webrtc/tests/mochitests/mochitest.ini create mode 100644 dom/media/webrtc/tests/mochitests/mochitest_datachannel.ini create mode 100644 dom/media/webrtc/tests/mochitests/mochitest_getusermedia.ini create mode 100644 dom/media/webrtc/tests/mochitests/mochitest_peerconnection.ini create mode 100644 dom/media/webrtc/tests/mochitests/network.js create mode 100644 dom/media/webrtc/tests/mochitests/nonTrickleIce.js create mode 100644 dom/media/webrtc/tests/mochitests/parser_rtp.js create mode 100644 dom/media/webrtc/tests/mochitests/pc.js create mode 100644 dom/media/webrtc/tests/mochitests/peerconnection_audio_forced_sample_rate.js create mode 100644 dom/media/webrtc/tests/mochitests/sdpUtils.js create mode 100644 dom/media/webrtc/tests/mochitests/simulcast.js create mode 100644 dom/media/webrtc/tests/mochitests/stats.js create mode 100644 dom/media/webrtc/tests/mochitests/templates.js create mode 100644 dom/media/webrtc/tests/mochitests/test_1488832.html create mode 100644 dom/media/webrtc/tests/mochitests/test_1717318.html create mode 100644 dom/media/webrtc/tests/mochitests/test_a_noOp.html create mode 100644 dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudio.html create mode 100644 dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudioVideo.html create mode 100644 dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudioVideoCombined.html create mode 100644 dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudioVideoNoBundle.html create mode 100644 dom/media/webrtc/tests/mochitests/test_dataChannel_basicDataOnly.html create mode 100644 dom/media/webrtc/tests/mochitests/test_dataChannel_basicVideo.html create mode 100644 dom/media/webrtc/tests/mochitests/test_dataChannel_bug1013809.html create mode 100644 dom/media/webrtc/tests/mochitests/test_dataChannel_dataOnlyBufferedAmountLow.html create mode 100644 dom/media/webrtc/tests/mochitests/test_dataChannel_dtlsVersions.html create mode 100644 dom/media/webrtc/tests/mochitests/test_dataChannel_hostnameObfuscation.html create mode 100644 dom/media/webrtc/tests/mochitests/test_dataChannel_noOffer.html create mode 100644 dom/media/webrtc/tests/mochitests/test_dataChannel_stats.html create mode 100644 dom/media/webrtc/tests/mochitests/test_defaultAudioConstraints.html create mode 100644 dom/media/webrtc/tests/mochitests/test_enumerateDevices.html create mode 100644 dom/media/webrtc/tests/mochitests/test_enumerateDevices_getUserMediaFake.html create mode 100644 dom/media/webrtc/tests/mochitests/test_enumerateDevices_iframe.html create mode 100644 dom/media/webrtc/tests/mochitests/test_enumerateDevices_iframe_pre_gum.html create mode 100644 dom/media/webrtc/tests/mochitests/test_enumerateDevices_legacy.html create mode 100644 dom/media/webrtc/tests/mochitests/test_enumerateDevices_navigation.html create mode 100644 dom/media/webrtc/tests/mochitests/test_fingerprinting_resistance.html create mode 100644 dom/media/webrtc/tests/mochitests/test_forceSampleRate.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_GC_MediaStream.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_active_autoplay.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_addTrackRemoveTrack.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_addtrack_removetrack_events.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_audioCapture.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_audioConstraints.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_audioConstraints_concurrentIframes.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_audioConstraints_concurrentStreams.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_basicAudio.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_basicAudio_loopback.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_basicScreenshare.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_basicTabshare.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_basicVideo.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_basicVideoAudio.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_basicVideo_playAfterLoadedmetadata.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_basicWindowshare.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_bug1223696.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_callbacks.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_constraints.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_cubebDisabled.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_cubebDisabledFakeStreams.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_getTrackById.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_gumWithinGum.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_loadedmetadata.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaElementCapture_audio.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaElementCapture_tracks.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaElementCapture_video.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaStreamClone.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaStreamConstructors.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaStreamTrackClone.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_nonDefaultRate.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_peerIdentity.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_permission.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_permission_iframe.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_playAudioTwice.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_playVideoAudioTwice.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_playVideoTwice.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_scarySources.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_spinEventLoop.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_trackCloneCleanup.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_trackEnded.html create mode 100644 dom/media/webrtc/tests/mochitests/test_groupId.html create mode 100644 dom/media/webrtc/tests/mochitests/test_multi_mics.html create mode 100644 dom/media/webrtc/tests/mochitests/test_ondevicechange.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_addAudioTrackToExistingVideoStream.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_addDataChannel.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_addDataChannelNoBundle.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondAudioStream.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondAudioStreamNoBundle.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondVideoStream.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondVideoStreamNoBundle.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_addtrack_removetrack_events.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_answererAddSecondAudioStream.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_audioChannels.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_audioCodecs.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_audioContributingSources.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_audioRenegotiationInactiveAnswer.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_audioSynchronizationSources.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_audioSynchronizationSourcesUnidirectional.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudio.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioDynamicPtMissingRtpmap.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelay.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTCP.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTCPWithStun300.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTLS.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayWithStun300.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATSrflx.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNoisyUDPBlock.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioPcmaPcmuOnly.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioRelayPolicy.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioRequireEOC.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVerifyRtpHeaderExtensions.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideo.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoCombined.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoNoBundle.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoNoBundleNoRtcpMux.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoNoRtcpMux.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoTransceivers.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoVerifyExtmap.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoVerifyExtmapSendonly.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoVerifyTooLongMidFails.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudio_forced_higher_rate.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudio_forced_lower_rate.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicH264Video.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicScreenshare.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicVideo.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicVideoVerifyRtpHeaderExtensions.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicWindowshare.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_bug1013809.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_bug1042791.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_bug1227781.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_bug1512281.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_bug1773067.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_bug822674.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_bug825703.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_bug827843.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_bug834153.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_callbacks.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_captureStream_canvas_2d.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_captureStream_canvas_2d_noSSRC.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_captureStream_canvas_webgl.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_capturedVideo.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_certificates.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_checkPacketDumpHook.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_close.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_closeDuringIce.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_codecNegotiationFailure.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_constructedStream.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_disabledVideoPreNegotiation.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_encodingsNegotiation.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_errorCallbacks.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_extmapRenegotiation.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_forwarding_basicAudioVideoCombined.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_gatherWithSetConfiguration.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_gatherWithStun300.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_gatherWithStun300IPv6.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_glean.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_iceFailure.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_insertDTMF.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_localReofferRollback.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_localRollback.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_maxFsConstraint.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_multiple_captureStream_canvas_2d.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_noTrickleAnswer.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_noTrickleOffer.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_noTrickleOfferAnswer.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_nonDefaultRate.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_offerRequiresReceiveAudio.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_offerRequiresReceiveVideo.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_offerRequiresReceiveVideoAudio.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_portRestrictions.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_promiseSendOnly.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_recordReceiveTrack.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_relayOnly.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_remoteReofferRollback.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_remoteRollback.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_removeAudioTrack.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddAudioTrack.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddAudioTrackNoBundle.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddVideoTrack.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddVideoTrackNoBundle.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_removeVideoTrack.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_renderAfterRenegotiation.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_replaceNullTrackThenRenegotiateAudio.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_replaceNullTrackThenRenegotiateVideo.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack_camera.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack_disabled.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack_microphone.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_replaceVideoThenRenegotiate.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_restartIce.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceBadAnswer.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalAndRemoteRollback.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalAndRemoteRollbackNoSubsequentRestart.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalRollback.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalRollbackNoSubsequentRestart.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoBundle.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoBundleNoRtcpMux.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoRtcpMux.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_restrictBandwidthTargetBitrate.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_restrictBandwidthWithTias.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_rtcp_rsize.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_scaleResolution.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_scaleResolution_oldSetParameters.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_sender_and_receiver_stats.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_setLocalAnswerInHaveLocalOffer.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_setLocalAnswerInStable.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_setLocalOfferInHaveRemoteOffer.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_maxFramerate.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_maxFramerate_oldSetParameters.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_oldSetParameters.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_scaleResolutionDownBy.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_scaleResolutionDownBy_oldSetParameters.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_setRemoteAnswerInHaveRemoteOffer.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_setRemoteAnswerInStable.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_setRemoteOfferInHaveLocalOffer.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer_lowResFirst.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer_lowResFirst_oldSetParameters.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer_oldSetParameters.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOddResolution.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOddResolution_oldSetParameters.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer_lowResFirst.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer_lowResFirst_oldSetParameters.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer_oldSetParameters.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_stats.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_stats_jitter.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_stats_oneway.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_stats_relayProtocol.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_stereoFmtpPref.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_syncSetDescription.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_telephoneEventFirst.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_threeUnbundledConnections.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_throwInCallbacks.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_toJSON.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_trackDisabling.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_trackDisabling_clones.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_trackless_sender_stats.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioStreams.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioTracksInOneStream.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioVideoStreams.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioVideoStreamsCombined.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioVideoStreamsCombinedNoBundle.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_twoVideoStreams.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_twoVideoTracksInOneStream.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_verifyAudioAfterRenegotiation.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_verifyDescriptions.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_verifyVideoAfterRenegotiation.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_videoCodecs.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_videoRenegotiationInactiveAnswer.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_webAudio.html create mode 100644 dom/media/webrtc/tests/mochitests/test_selftest.html create mode 100644 dom/media/webrtc/tests/mochitests/test_setSinkId.html create mode 100644 dom/media/webrtc/tests/mochitests/test_setSinkId_default_addTrack.html create mode 100644 dom/media/webrtc/tests/mochitests/test_setSinkId_preMutedElement.html create mode 100644 dom/media/webrtc/tests/mochitests/test_unfocused_pref.html create mode 100644 dom/media/webrtc/tests/mochitests/turnConfig.js (limited to 'dom/media/webrtc/tests') diff --git a/dom/media/webrtc/tests/crashtests/1770075.html b/dom/media/webrtc/tests/crashtests/1770075.html new file mode 100644 index 0000000000..4d451216bd --- /dev/null +++ b/dom/media/webrtc/tests/crashtests/1770075.html @@ -0,0 +1,8 @@ + diff --git a/dom/media/webrtc/tests/crashtests/1789908.html b/dom/media/webrtc/tests/crashtests/1789908.html new file mode 100644 index 0000000000..3d58d3dc6b --- /dev/null +++ b/dom/media/webrtc/tests/crashtests/1789908.html @@ -0,0 +1,25 @@ + diff --git a/dom/media/webrtc/tests/crashtests/1799168.html b/dom/media/webrtc/tests/crashtests/1799168.html new file mode 100644 index 0000000000..6c5c9db237 --- /dev/null +++ b/dom/media/webrtc/tests/crashtests/1799168.html @@ -0,0 +1,16 @@ + diff --git a/dom/media/webrtc/tests/crashtests/1816708.html b/dom/media/webrtc/tests/crashtests/1816708.html new file mode 100644 index 0000000000..c7ba824041 --- /dev/null +++ b/dom/media/webrtc/tests/crashtests/1816708.html @@ -0,0 +1,21 @@ + + + + + + diff --git a/dom/media/webrtc/tests/crashtests/1821477.html b/dom/media/webrtc/tests/crashtests/1821477.html new file mode 100644 index 0000000000..c37bd6bd02 --- /dev/null +++ b/dom/media/webrtc/tests/crashtests/1821477.html @@ -0,0 +1,16 @@ + + + diff --git a/dom/media/webrtc/tests/crashtests/crashtests.list b/dom/media/webrtc/tests/crashtests/crashtests.list new file mode 100644 index 0000000000..64434b28e2 --- /dev/null +++ b/dom/media/webrtc/tests/crashtests/crashtests.list @@ -0,0 +1,7 @@ +defaults pref(media.navigator.permission.disabled,true) pref(media.devices.insecure.enabled,true) pref(media.getusermedia.insecure.enabled,true) + +load 1770075.html +load 1789908.html +load 1799168.html +load 1816708.html +skip-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) load 1821477.html diff --git a/dom/media/webrtc/tests/fuzztests/moz.build b/dom/media/webrtc/tests/fuzztests/moz.build new file mode 100644 index 0000000000..fef388e6c9 --- /dev/null +++ b/dom/media/webrtc/tests/fuzztests/moz.build @@ -0,0 +1,22 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Library("FuzzingSdp") + +LOCAL_INCLUDES += [ + "/dom/media/webrtc", + "/ipc/chromium/src", + "/media/webrtc", +] + +# Add libFuzzer configuration directives +include("/tools/fuzzing/libfuzzer-config.mozbuild") + +SOURCES += [ + "sdp_parser_libfuzz.cpp", +] + +FINAL_LIBRARY = "xul-gtest" diff --git a/dom/media/webrtc/tests/fuzztests/sdp_parser_libfuzz.cpp b/dom/media/webrtc/tests/fuzztests/sdp_parser_libfuzz.cpp new file mode 100644 index 0000000000..3451d6fd21 --- /dev/null +++ b/dom/media/webrtc/tests/fuzztests/sdp_parser_libfuzz.cpp @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include + +#include "gtest/gtest.h" + +#include "FuzzingInterface.h" + +#include "sdp/SipccSdpParser.h" + +using namespace mozilla; + +static mozilla::UniquePtr sdpPtr; +static SipccSdpParser mParser; + +int FuzzingInitSdpParser(int* argc, char*** argv) { return 0; } + +static int RunSdpParserFuzzing(const uint8_t* data, size_t size) { + std::string message(reinterpret_cast(data), size); + + sdpPtr = mParser.Parse(message); + + return 0; +} + +MOZ_FUZZING_INTERFACE_RAW(FuzzingInitSdpParser, RunSdpParserFuzzing, SdpParser); diff --git a/dom/media/webrtc/tests/mochitests/NetworkPreparationChromeScript.js b/dom/media/webrtc/tests/mochitests/NetworkPreparationChromeScript.js new file mode 100644 index 0000000000..d3872f1519 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/NetworkPreparationChromeScript.js @@ -0,0 +1,43 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var browser = Services.wm.getMostRecentWindow("navigator:browser"); +var connection = browser.navigator.mozMobileConnections[0]; + +// provide a fake APN and enable data connection. +// enable 3G radio +function enableRadio() { + if (connection.radioState !== "enabled") { + connection.setRadioEnabled(true); + } +} + +// disable 3G radio +function disableRadio() { + if (connection.radioState === "enabled") { + connection.setRadioEnabled(false); + } +} + +addMessageListener("prepare-network", function (message) { + connection.addEventListener("datachange", function onDataChange() { + if (connection.data.connected) { + connection.removeEventListener("datachange", onDataChange); + Services.prefs.setIntPref("network.proxy.type", 2); + sendAsyncMessage("network-ready", true); + } + }); + + enableRadio(); +}); + +addMessageListener("network-cleanup", function (message) { + connection.addEventListener("datachange", function onDataChange() { + if (!connection.data.connected) { + connection.removeEventListener("datachange", onDataChange); + Services.prefs.setIntPref("network.proxy.type", 2); + sendAsyncMessage("network-disabled", true); + } + }); + disableRadio(); +}); diff --git a/dom/media/webrtc/tests/mochitests/addTurnsSelfsignedCert.js b/dom/media/webrtc/tests/mochitests/addTurnsSelfsignedCert.js new file mode 100644 index 0000000000..1e8be3a397 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/addTurnsSelfsignedCert.js @@ -0,0 +1,32 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* eslint-env mozilla/chrome-script */ + +"use strict"; + +// This is only usable from the parent process, even for doing simple stuff like +// serializing a cert. +var gCertMaker = Cc["@mozilla.org/security/x509certdb;1"].getService( + Ci.nsIX509CertDB +); + +var gCertOverrides = Cc["@mozilla.org/security/certoverride;1"].getService( + Ci.nsICertOverrideService +); + +addMessageListener("add-turns-certs", certs => { + var port = 5349; + certs.forEach(certDescription => { + var cert = gCertMaker.constructX509FromBase64(certDescription.cert); + gCertOverrides.rememberValidityOverride( + certDescription.hostname, + port, + {}, + cert, + false + ); + }); + sendAsyncMessage("certs-added"); +}); diff --git a/dom/media/webrtc/tests/mochitests/blacksilence.js b/dom/media/webrtc/tests/mochitests/blacksilence.js new file mode 100644 index 0000000000..5ea35f8a7f --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/blacksilence.js @@ -0,0 +1,134 @@ +(function (global) { + "use strict"; + + // an invertible check on the condition. + // if the constraint is applied, then the check is direct + // if not applied, then the result should be reversed + function check(constraintApplied, condition, message) { + var good = constraintApplied ? condition : !condition; + message = + (constraintApplied ? "with" : "without") + + " constraint: should " + + (constraintApplied ? "" : "not ") + + message + + " = " + + (good ? "OK" : "waiting..."); + info(message); + return good; + } + + function mkElement(type) { + // This makes an unattached element. + // It's not rendered to save the cycles that costs on b2g emulator + // and it gets dropped (and GC'd) when the test is done. + var e = document.createElement(type); + e.width = 32; + e.height = 24; + document.getElementById("display").appendChild(e); + return e; + } + + // Runs checkFunc until it reports success. + // This is kludgy, but you have to wait for media to start flowing, and it + // can't be any old media, it has to include real data, for which we have no + // reliable signals to use as a trigger. + function periodicCheck(checkFunc) { + var resolve; + var done = false; + // This returns a function so that we create 10 closures in the loop, not + // one; and so that the timers don't all start straight away + var waitAndCheck = counter => () => { + if (done) { + return Promise.resolve(); + } + return new Promise(r => setTimeout(r, 200 << counter)).then(() => { + if (checkFunc()) { + done = true; + resolve(); + } + }); + }; + + var chain = Promise.resolve(); + for (var i = 0; i < 10; ++i) { + chain = chain.then(waitAndCheck(i)); + } + return new Promise(r => (resolve = r)); + } + + function isSilence(audioData) { + var silence = true; + for (var i = 0; i < audioData.length; ++i) { + if (audioData[i] !== 128) { + silence = false; + } + } + return silence; + } + + function checkAudio(constraintApplied, stream) { + var audio = mkElement("audio"); + audio.srcObject = stream; + audio.play(); + + var context = new AudioContext(); + var source = context.createMediaStreamSource(stream); + var analyser = context.createAnalyser(); + source.connect(analyser); + analyser.connect(context.destination); + + return periodicCheck(() => { + var sampleCount = analyser.frequencyBinCount; + info("got some audio samples: " + sampleCount); + var buffer = new Uint8Array(sampleCount); + analyser.getByteTimeDomainData(buffer); + + var silent = check( + constraintApplied, + isSilence(buffer), + "be silence for audio" + ); + return sampleCount > 0 && silent; + }).then(() => { + source.disconnect(); + analyser.disconnect(); + audio.pause(); + ok(true, "audio is " + (constraintApplied ? "" : "not ") + "silent"); + }); + } + + function checkVideo(constraintApplied, stream) { + var video = mkElement("video"); + video.srcObject = stream; + video.play(); + + return periodicCheck(() => { + try { + var canvas = mkElement("canvas"); + var ctx = canvas.getContext("2d"); + // Have to guard drawImage with the try as well, due to bug 879717. If + // we get an error, this round fails, but that failure is usually just + // transitory. + ctx.drawImage(video, 0, 0); + ctx.getImageData(0, 0, 1, 1); + return check( + constraintApplied, + false, + "throw on getImageData for video" + ); + } catch (e) { + return check( + constraintApplied, + e.name === "SecurityError", + "get a security error: " + e.name + ); + } + }).then(() => { + video.pause(); + ok(true, "video is " + (constraintApplied ? "" : "not ") + "protected"); + }); + } + + global.audioIsSilence = checkAudio; + global.videoIsBlack = checkVideo; +})(this); diff --git a/dom/media/webrtc/tests/mochitests/dataChannel.js b/dom/media/webrtc/tests/mochitests/dataChannel.js new file mode 100644 index 0000000000..eac52f96ab --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/dataChannel.js @@ -0,0 +1,352 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Returns the contents of a blob as text + * + * @param {Blob} blob + The blob to retrieve the contents from + */ +function getBlobContent(blob) { + return new Promise(resolve => { + var reader = new FileReader(); + // Listen for 'onloadend' which will always be called after a success or failure + reader.onloadend = event => resolve(event.target.result); + reader.readAsText(blob); + }); +} + +var commandsCreateDataChannel = [ + function PC_REMOTE_EXPECT_DATA_CHANNEL(test) { + test.pcRemote.expectDataChannel(); + }, + + function PC_LOCAL_CREATE_DATA_CHANNEL(test) { + var channel = test.pcLocal.createDataChannel({}); + is(channel.binaryType, "blob", channel + " is of binary type 'blob'"); + + is( + test.pcLocal.signalingState, + STABLE, + "Create datachannel does not change signaling state" + ); + return test.pcLocal.observedNegotiationNeeded; + }, +]; + +var commandsWaitForDataChannel = [ + function PC_LOCAL_VERIFY_DATA_CHANNEL_STATE(test) { + return test.pcLocal.dataChannels[0].opened; + }, + + function PC_REMOTE_VERIFY_DATA_CHANNEL_STATE(test) { + return test.pcRemote.nextDataChannel.then(channel => channel.opened); + }, +]; + +var commandsCheckDataChannel = [ + function SEND_MESSAGE(test) { + var message = "Lorem ipsum dolor sit amet"; + + info("Sending message:" + message); + return test.send(message).then(result => { + is( + result.data, + message, + "Message correctly transmitted from pcLocal to pcRemote." + ); + }); + }, + + function SEND_BLOB(test) { + var contents = "At vero eos et accusam et justo duo dolores et ea rebum."; + var blob = new Blob([contents], { type: "text/plain" }); + + info("Sending blob"); + return test + .send(blob) + .then(result => { + ok(result.data instanceof Blob, "Received data is of instance Blob"); + is(result.data.size, blob.size, "Received data has the correct size."); + + return getBlobContent(result.data); + }) + .then(recv_contents => + is(recv_contents, contents, "Received data has the correct content.") + ); + }, + + function CREATE_SECOND_DATA_CHANNEL(test) { + return test.createDataChannel({}).then(result => { + is( + result.remote.binaryType, + "blob", + "remote data channel is of binary type 'blob'" + ); + }); + }, + + function SEND_MESSAGE_THROUGH_LAST_OPENED_CHANNEL(test) { + var channels = test.pcRemote.dataChannels; + var message = "I am the Omega"; + + info("Sending message:" + message); + return test.send(message).then(result => { + is( + channels.indexOf(result.channel), + channels.length - 1, + "Last channel used" + ); + is(result.data, message, "Received message has the correct content."); + }); + }, + + function SEND_MESSAGE_THROUGH_FIRST_CHANNEL(test) { + var message = "Message through 1st channel"; + var options = { + sourceChannel: test.pcLocal.dataChannels[0], + targetChannel: test.pcRemote.dataChannels[0], + }; + + info("Sending message:" + message); + return test.send(message, options).then(result => { + is( + test.pcRemote.dataChannels.indexOf(result.channel), + 0, + "1st channel used" + ); + is(result.data, message, "Received message has the correct content."); + }); + }, + + function SEND_MESSAGE_BACK_THROUGH_FIRST_CHANNEL(test) { + var message = "Return a message also through 1st channel"; + var options = { + sourceChannel: test.pcRemote.dataChannels[0], + targetChannel: test.pcLocal.dataChannels[0], + }; + + info("Sending message:" + message); + return test.send(message, options).then(result => { + is( + test.pcLocal.dataChannels.indexOf(result.channel), + 0, + "1st channel used" + ); + is(result.data, message, "Return message has the correct content."); + }); + }, + + function CREATE_NEGOTIATED_DATA_CHANNEL_MAX_RETRANSMITS(test) { + var options = { + negotiated: true, + id: 5, + protocol: "foo/bar", + ordered: false, + maxRetransmits: 500, + }; + return test.createDataChannel(options).then(result => { + is( + result.local.binaryType, + "blob", + result.remote + " is of binary type 'blob'" + ); + is( + result.local.id, + options.id, + result.local + " id is:" + result.local.id + ); + is( + result.local.protocol, + options.protocol, + result.local + " protocol is:" + result.local.protocol + ); + is( + result.local.reliable, + false, + result.local + " reliable is:" + result.local.reliable + ); + is( + result.local.ordered, + options.ordered, + result.local + " ordered is:" + result.local.ordered + ); + is( + result.local.maxRetransmits, + options.maxRetransmits, + result.local + " maxRetransmits is:" + result.local.maxRetransmits + ); + is( + result.local.maxPacketLifeTime, + null, + result.local + " maxPacketLifeTime is:" + result.local.maxPacketLifeTime + ); + + is( + result.remote.binaryType, + "blob", + result.remote + " is of binary type 'blob'" + ); + is( + result.remote.id, + options.id, + result.remote + " id is:" + result.remote.id + ); + is( + result.remote.protocol, + options.protocol, + result.remote + " protocol is:" + result.remote.protocol + ); + is( + result.remote.reliable, + false, + result.remote + " reliable is:" + result.remote.reliable + ); + is( + result.remote.ordered, + options.ordered, + result.remote + " ordered is:" + result.remote.ordered + ); + is( + result.remote.maxRetransmits, + options.maxRetransmits, + result.remote + " maxRetransmits is:" + result.remote.maxRetransmits + ); + is( + result.remote.maxPacketLifeTime, + null, + result.remote + + " maxPacketLifeTime is:" + + result.remote.maxPacketLifeTime + ); + }); + }, + + function SEND_MESSAGE_THROUGH_LAST_OPENED_CHANNEL2(test) { + var channels = test.pcRemote.dataChannels; + var message = "I am the walrus; Goo goo g'joob"; + + info("Sending message:" + message); + return test.send(message).then(result => { + is( + channels.indexOf(result.channel), + channels.length - 1, + "Last channel used" + ); + is(result.data, message, "Received message has the correct content."); + }); + }, + + function CREATE_NEGOTIATED_DATA_CHANNEL_MAX_PACKET_LIFE_TIME(test) { + var options = { + ordered: false, + maxPacketLifeTime: 10, + }; + return test.createDataChannel(options).then(result => { + is( + result.local.binaryType, + "blob", + result.local + " is of binary type 'blob'" + ); + is( + result.local.protocol, + "", + result.local + " protocol is:" + result.local.protocol + ); + is( + result.local.reliable, + false, + result.local + " reliable is:" + result.local.reliable + ); + is( + result.local.ordered, + options.ordered, + result.local + " ordered is:" + result.local.ordered + ); + is( + result.local.maxRetransmits, + null, + result.local + " maxRetransmits is:" + result.local.maxRetransmits + ); + is( + result.local.maxPacketLifeTime, + options.maxPacketLifeTime, + result.local + " maxPacketLifeTime is:" + result.local.maxPacketLifeTime + ); + + is( + result.remote.binaryType, + "blob", + result.remote + " is of binary type 'blob'" + ); + is( + result.remote.protocol, + "", + result.remote + " protocol is:" + result.remote.protocol + ); + is( + result.remote.reliable, + false, + result.remote + " reliable is:" + result.remote.reliable + ); + is( + result.remote.ordered, + options.ordered, + result.remote + " ordered is:" + result.remote.ordered + ); + is( + result.remote.maxRetransmits, + null, + result.remote + " maxRetransmits is:" + result.remote.maxRetransmits + ); + is( + result.remote.maxPacketLifeTime, + options.maxPacketLifeTime, + result.remote + + " maxPacketLifeTime is:" + + result.remote.maxPacketLifeTime + ); + }); + }, + + function SEND_MESSAGE_THROUGH_LAST_OPENED_CHANNEL3(test) { + var channels = test.pcRemote.dataChannels; + var message = "Nice to see you working maxPacketLifeTime"; + + info("Sending message:" + message); + return test.send(message).then(result => { + is( + channels.indexOf(result.channel), + channels.length - 1, + "Last channel used" + ); + is(result.data, message, "Received message has the correct content."); + }); + }, +]; + +var commandsCheckLargeXfer = [ + function SEND_BIG_BUFFER(test) { + var size = 2 * 1024 * 1024; // SCTP internal buffer is now 1MB, so use 2MB to ensure the buffer gets full + var buffer = new ArrayBuffer(size); + // note: type received is always blob for binary data + var options = {}; + options.bufferedAmountLowThreshold = 64 * 1024; + info("Sending arraybuffer"); + return test.send(buffer, options).then(result => { + ok(result.data instanceof Blob, "Received data is of instance Blob"); + is(result.data.size, size, "Received data has the correct size."); + }); + }, +]; + +function addInitialDataChannel(chain) { + chain.insertBefore("PC_LOCAL_CREATE_OFFER", commandsCreateDataChannel); + chain.insertBefore( + "PC_LOCAL_WAIT_FOR_MEDIA_FLOW", + commandsWaitForDataChannel + ); + chain.removeAfter("PC_REMOTE_CHECK_ICE_CONNECTIONS"); + chain.append(commandsCheckDataChannel); +} diff --git a/dom/media/webrtc/tests/mochitests/head.js b/dom/media/webrtc/tests/mochitests/head.js new file mode 100644 index 0000000000..7c9f6d52de --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/head.js @@ -0,0 +1,1445 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var Cc = SpecialPowers.Cc; +var Ci = SpecialPowers.Ci; + +// Specifies if we want fake audio streams for this run +let WANT_FAKE_AUDIO = true; +// Specifies if we want fake video streams for this run +let WANT_FAKE_VIDEO = true; +let TEST_AUDIO_FREQ = 1000; + +/** + * Reads the current values of preferences affecting fake and loopback devices + * and sets the WANT_FAKE_AUDIO and WANT_FAKE_VIDEO gloabals appropriately. + */ +function updateConfigFromFakeAndLoopbackPrefs() { + let audioDevice = SpecialPowers.getCharPref("media.audio_loopback_dev", ""); + if (audioDevice) { + WANT_FAKE_AUDIO = false; + dump("TEST DEVICES: Got loopback audio: " + audioDevice + "\n"); + } else { + WANT_FAKE_AUDIO = true; + dump( + "TEST DEVICES: No test device found in media.audio_loopback_dev, using fake audio streams.\n" + ); + } + let videoDevice = SpecialPowers.getCharPref("media.video_loopback_dev", ""); + if (videoDevice) { + WANT_FAKE_VIDEO = false; + dump("TEST DEVICES: Got loopback video: " + videoDevice + "\n"); + } else { + WANT_FAKE_VIDEO = true; + dump( + "TEST DEVICES: No test device found in media.video_loopback_dev, using fake video streams.\n" + ); + } +} + +updateConfigFromFakeAndLoopbackPrefs(); + +/** + * Global flag to skip LoopbackTone + */ +let DISABLE_LOOPBACK_TONE = false; +/** + * Helper class to setup a sine tone of a given frequency. + */ +class LoopbackTone { + constructor(audioContext, frequency) { + if (!audioContext) { + throw new Error("You must provide a valid AudioContext"); + } + this.oscNode = audioContext.createOscillator(); + var gainNode = audioContext.createGain(); + gainNode.gain.value = 0.5; + this.oscNode.connect(gainNode); + gainNode.connect(audioContext.destination); + this.changeFrequency(frequency); + } + + // Method should be used when WANT_FAKE_AUDIO is false. + start() { + if (!this.oscNode) { + throw new Error("Attempt to start a stopped LoopbackTone"); + } + info(`Start loopback tone at ${this.oscNode.frequency.value}`); + this.oscNode.start(); + } + + // Change the frequency of the tone. It can be used after start. + // Frequency will change on the fly. No need to stop and create a new instance. + changeFrequency(frequency) { + if (!this.oscNode) { + throw new Error("Attempt to change frequency on a stopped LoopbackTone"); + } + this.oscNode.frequency.value = frequency; + } + + stop() { + if (!this.oscNode) { + throw new Error("Attempt to stop a stopped LoopbackTone"); + } + this.oscNode.stop(); + this.oscNode = null; + } +} +// Object that holds the default loopback tone. +var DefaultLoopbackTone = null; + +/** + * This class provides helpers around analysing the audio content in a stream + * using WebAudio AnalyserNodes. + * + * @constructor + * @param {object} stream + * A MediaStream object whose audio track we shall analyse. + */ +function AudioStreamAnalyser(ac, stream) { + this.audioContext = ac; + this.stream = stream; + this.sourceNodes = []; + this.analyser = this.audioContext.createAnalyser(); + // Setting values lower than default for speedier testing on emulators + this.analyser.smoothingTimeConstant = 0.2; + this.analyser.fftSize = 1024; + this.connectTrack = t => { + let source = this.audioContext.createMediaStreamSource( + new MediaStream([t]) + ); + this.sourceNodes.push(source); + source.connect(this.analyser); + }; + this.stream.getAudioTracks().forEach(t => this.connectTrack(t)); + this.onaddtrack = ev => this.connectTrack(ev.track); + this.stream.addEventListener("addtrack", this.onaddtrack); + this.data = new Uint8Array(this.analyser.frequencyBinCount); +} + +AudioStreamAnalyser.prototype = { + /** + * Get an array of frequency domain data for our stream's audio track. + * + * @returns {array} A Uint8Array containing the frequency domain data. + */ + getByteFrequencyData() { + this.analyser.getByteFrequencyData(this.data); + return this.data; + }, + + /** + * Append a canvas to the DOM where the frequency data are drawn. + * Useful to debug tests. + */ + enableDebugCanvas() { + var cvs = (this.debugCanvas = document.createElement("canvas")); + const content = document.getElementById("content"); + content.insertBefore(cvs, content.children[0]); + + // Easy: 1px per bin + cvs.width = this.analyser.frequencyBinCount; + cvs.height = 128; + cvs.style.border = "1px solid red"; + + var c = cvs.getContext("2d"); + c.fillStyle = "black"; + + var self = this; + function render() { + c.clearRect(0, 0, cvs.width, cvs.height); + var array = self.getByteFrequencyData(); + for (var i = 0; i < array.length; i++) { + c.fillRect(i, cvs.height - array[i] / 2, 1, cvs.height); + } + if (!cvs.stopDrawing) { + requestAnimationFrame(render); + } + } + requestAnimationFrame(render); + }, + + /** + * Stop drawing of and remove the debug canvas from the DOM if it was + * previously added. + */ + disableDebugCanvas() { + if (!this.debugCanvas || !this.debugCanvas.parentElement) { + return; + } + + this.debugCanvas.stopDrawing = true; + this.debugCanvas.parentElement.removeChild(this.debugCanvas); + }, + + /** + * Disconnects the input stream from our internal analyser node. + * Call this to reduce main thread processing, mostly necessary on slow + * devices. + */ + disconnect() { + this.disableDebugCanvas(); + this.sourceNodes.forEach(n => n.disconnect()); + this.sourceNodes = []; + this.stream.removeEventListener("addtrack", this.onaddtrack); + }, + + /** + * Return a Promise, that will be resolved when the function passed as + * argument, when called, returns true (meaning the analysis was a + * success). The promise is rejected if the cancel promise resolves first. + * + * @param {function} analysisFunction + * A function that performs an analysis, and resolves with true if the + * analysis was a success (i.e. it found what it was looking for) + * @param {promise} cancel + * A promise that on resolving will reject the promise we returned. + */ + async waitForAnalysisSuccess( + analysisFunction, + cancel = wait(60000, new Error("Audio analysis timed out")) + ) { + let aborted = false; + cancel.then(() => (aborted = true)); + + // We need to give the Analyser some time to start gathering data. + await wait(200); + + do { + await new Promise(resolve => requestAnimationFrame(resolve)); + if (aborted) { + throw await cancel; + } + } while (!analysisFunction(this.getByteFrequencyData())); + }, + + /** + * Return the FFT bin index for a given frequency. + * + * @param {double} frequency + * The frequency for whicht to return the bin number. + * @returns {integer} the index of the bin in the FFT array. + */ + binIndexForFrequency(frequency) { + return ( + 1 + + Math.round( + (frequency * this.analyser.fftSize) / this.audioContext.sampleRate + ) + ); + }, + + /** + * Reverse operation, get the frequency for a bin index. + * + * @param {integer} index an index in an FFT array + * @returns {double} the frequency for this bin + */ + frequencyForBinIndex(index) { + return ((index - 1) * this.audioContext.sampleRate) / this.analyser.fftSize; + }, +}; + +/** + * Creates a MediaStream with an audio track containing a sine tone at the + * given frequency. + * + * @param {AudioContext} ac + * AudioContext in which to create the OscillatorNode backing the stream + * @param {double} frequency + * The frequency in Hz of the generated sine tone + * @returns {MediaStream} the MediaStream containing sine tone audio track + */ +function createOscillatorStream(ac, frequency) { + var osc = ac.createOscillator(); + osc.frequency.value = frequency; + + var oscDest = ac.createMediaStreamDestination(); + osc.connect(oscDest); + osc.start(); + return oscDest.stream; +} + +/** + * Create the necessary HTML elements for head and body as used by Mochitests + * + * @param {object} meta + * Meta information of the test + * @param {string} meta.title + * Description of the test + * @param {string} [meta.bug] + * Bug the test was created for + * @param {boolean} [meta.visible=false] + * Visibility of the media elements + */ +function realCreateHTML(meta) { + var test = document.getElementById("test"); + + // Create the head content + var elem = document.createElement("meta"); + elem.setAttribute("charset", "utf-8"); + document.head.appendChild(elem); + + var title = document.createElement("title"); + title.textContent = meta.title; + document.head.appendChild(title); + + // Create the body content + var anchor = document.createElement("a"); + anchor.textContent = meta.title; + if (meta.bug) { + anchor.setAttribute( + "href", + "https://bugzilla.mozilla.org/show_bug.cgi?id=" + meta.bug + ); + } else { + anchor.setAttribute("target", "_blank"); + } + + document.body.insertBefore(anchor, test); + + var display = document.createElement("p"); + display.setAttribute("id", "display"); + document.body.insertBefore(display, test); + + var content = document.createElement("div"); + content.setAttribute("id", "content"); + content.style.display = meta.visible ? "block" : "none"; + document.body.appendChild(content); +} + +/** + * Creates an element of the given type, assigns the given id, sets the controls + * and autoplay attributes and adds it to the content node. + * + * @param {string} type + * Defining if we should create an "audio" or "video" element + * @param {string} id + * A string to use as the element id. + */ +function createMediaElement(type, id) { + const element = document.createElement(type); + element.setAttribute("id", id); + element.setAttribute("height", 100); + element.setAttribute("width", 150); + element.setAttribute("controls", "controls"); + element.setAttribute("autoplay", "autoplay"); + element.setAttribute("muted", "muted"); + element.muted = true; + document.getElementById("content").appendChild(element); + + return element; +} + +/** + * Returns an existing element for the given track with the given idPrefix, + * as it was added by createMediaElementForTrack(). + * + * @param {MediaStreamTrack} track + * Track used as the element's source. + * @param {string} idPrefix + * A string to use as the element id. The track id will also be appended. + */ +function getMediaElementForTrack(track, idPrefix) { + return document.getElementById(idPrefix + "_" + track.id); +} + +/** + * Create a media element with a track as source and attach it to the content + * node. + * + * @param {MediaStreamTrack} track + * Track for use as source. + * @param {string} idPrefix + * A string to use as the element id. The track id will also be appended. + * @return {HTMLMediaElement} The created HTML media element + */ +function createMediaElementForTrack(track, idPrefix) { + const id = idPrefix + "_" + track.id; + const element = createMediaElement(track.kind, id); + element.srcObject = new MediaStream([track]); + + return element; +} + +/** + * Wrapper function for mediaDevices.getUserMedia used by some tests. Whether + * to use fake devices or not is now determined in pref further below instead. + * + * @param {Dictionary} constraints + * The constraints for this mozGetUserMedia callback + */ +function getUserMedia(constraints) { + // Tests may have changed the values of prefs, so recheck + updateConfigFromFakeAndLoopbackPrefs(); + if ( + !WANT_FAKE_AUDIO && + !constraints.fake && + constraints.audio && + !DISABLE_LOOPBACK_TONE + ) { + // Loopback device is configured, start the default loopback tone + if (!DefaultLoopbackTone) { + TEST_AUDIO_FREQ = 440; + DefaultLoopbackTone = new LoopbackTone( + new AudioContext(), + TEST_AUDIO_FREQ + ); + DefaultLoopbackTone.start(); + } + // Disable input processing mode when it's not explicity enabled. + // This is to avoid distortion of the loopback tone + constraints.audio = Object.assign( + {}, + { autoGainControl: false }, + { echoCancellation: false }, + { noiseSuppression: false }, + constraints.audio + ); + } else { + // Fake device configured, ensure our test freq is correct. + TEST_AUDIO_FREQ = 1000; + } + info("Call getUserMedia for " + JSON.stringify(constraints)); + return navigator.mediaDevices + .getUserMedia(constraints) + .then(stream => (checkMediaStreamTracks(constraints, stream), stream)); +} + +// These are the promises we use to track that the prerequisites for the test +// are in place before running it. +var setTestOptions; +var testConfigured = new Promise(r => (setTestOptions = r)); + +function pushPrefs(...p) { + return SpecialPowers.pushPrefEnv({ set: p }); +} + +async function withPrefs(prefs, func) { + await SpecialPowers.pushPrefEnv({ set: prefs }); + try { + return await func(); + } finally { + await SpecialPowers.popPrefEnv(); + } +} + +function setupEnvironment() { + var defaultMochitestPrefs = { + set: [ + ["media.peerconnection.enabled", true], + ["media.peerconnection.identity.enabled", true], + ["media.peerconnection.identity.timeout", 120000], + ["media.peerconnection.ice.stun_client_maximum_transmits", 14], + ["media.peerconnection.ice.trickle_grace_period", 30000], + ["media.navigator.permission.disabled", true], + // If either fake audio or video is desired we enable fake streams. + // If loopback devices are set they will be chosen instead of fakes in gecko. + ["media.navigator.streams.fake", WANT_FAKE_AUDIO || WANT_FAKE_VIDEO], + ["media.getusermedia.audiocapture.enabled", true], + ["media.getusermedia.screensharing.enabled", true], + ["media.getusermedia.window.focus_source.enabled", false], + ["media.recorder.audio_node.enabled", true], + ["media.peerconnection.ice.obfuscate_host_addresses", false], + ["media.peerconnection.nat_simulator.filtering_type", ""], + ["media.peerconnection.nat_simulator.mapping_type", ""], + ["media.peerconnection.nat_simulator.block_tcp", false], + ["media.peerconnection.nat_simulator.block_udp", false], + ["media.peerconnection.nat_simulator.redirect_address", ""], + ["media.peerconnection.nat_simulator.redirect_targets", ""], + ], + }; + + if (navigator.userAgent.includes("Android")) { + defaultMochitestPrefs.set.push( + ["media.navigator.video.default_width", 320], + ["media.navigator.video.default_height", 240], + ["media.navigator.video.max_fr", 10], + ["media.autoplay.default", Ci.nsIAutoplay.ALLOWED] + ); + } + + // Platform codec prefs should be matched because fake H.264 GMP codec doesn't + // produce/consume real bitstreams. [TODO] remove after bug 1509012 is fixed. + const platformEncoderEnabled = SpecialPowers.getBoolPref( + "media.webrtc.platformencoder" + ); + defaultMochitestPrefs.set.push([ + "media.navigator.mediadatadecoder_h264_enabled", + platformEncoderEnabled, + ]); + + // Running as a Mochitest. + SimpleTest.requestFlakyTimeout("WebRTC inherently depends on timeouts"); + window.finish = () => SimpleTest.finish(); + SpecialPowers.pushPrefEnv(defaultMochitestPrefs, setTestOptions); + + // We don't care about waiting for this to complete, we just want to ensure + // that we don't build up a huge backlog of GC work. + SpecialPowers.exactGC(); +} + +// [TODO] remove after bug 1509012 is fixed. +async function matchPlatformH264CodecPrefs() { + const hasHW264 = + SpecialPowers.getBoolPref("media.webrtc.platformencoder") && + !SpecialPowers.getBoolPref("media.webrtc.platformencoder.sw_only") && + (navigator.userAgent.includes("Android") || + navigator.userAgent.includes("Mac OS X")); + + await pushPrefs( + ["media.webrtc.platformencoder", hasHW264], + ["media.navigator.mediadatadecoder_h264_enabled", hasHW264] + ); +} + +async function runTestWhenReady(testFunc) { + setupEnvironment(); + const options = await testConfigured; + try { + await testFunc(options); + } catch (e) { + ok( + false, + `Error executing test: ${e} +${e.stack ? e.stack : ""}` + ); + } finally { + SimpleTest.finish(); + } +} + +/** + * Checks that the media stream tracks have the expected amount of tracks + * with the correct attributes based on the type and constraints given. + * + * @param {Object} constraints specifies whether the stream should have + * audio, video, or both + * @param {String} type the type of media stream tracks being checked + * @param {sequence} mediaStreamTracks the media stream + * tracks being checked + */ +function checkMediaStreamTracksByType(constraints, type, mediaStreamTracks) { + if (constraints[type]) { + is(mediaStreamTracks.length, 1, "One " + type + " track shall be present"); + + if (mediaStreamTracks.length) { + is(mediaStreamTracks[0].kind, type, "Track kind should be " + type); + ok(mediaStreamTracks[0].id, "Track id should be defined"); + ok(!mediaStreamTracks[0].muted, "Track should not be muted"); + } + } else { + is(mediaStreamTracks.length, 0, "No " + type + " tracks shall be present"); + } +} + +/** + * Check that the given media stream contains the expected media stream + * tracks given the associated audio & video constraints provided. + * + * @param {Object} constraints specifies whether the stream should have + * audio, video, or both + * @param {MediaStream} mediaStream the media stream being checked + */ +function checkMediaStreamTracks(constraints, mediaStream) { + checkMediaStreamTracksByType( + constraints, + "audio", + mediaStream.getAudioTracks() + ); + checkMediaStreamTracksByType( + constraints, + "video", + mediaStream.getVideoTracks() + ); +} + +/** + * Check that a media stream contains exactly a set of media stream tracks. + * + * @param {MediaStream} mediaStream the media stream being checked + * @param {Array} tracks the tracks that should exist in mediaStream + * @param {String} [message] an optional message to pass to asserts + */ +function checkMediaStreamContains(mediaStream, tracks, message) { + message = message ? message + ": " : ""; + tracks.forEach(t => + ok( + mediaStream.getTrackById(t.id), + message + "MediaStream " + mediaStream.id + " contains track " + t.id + ) + ); + is( + mediaStream.getTracks().length, + tracks.length, + message + "MediaStream " + mediaStream.id + " contains no extra tracks" + ); +} + +function checkMediaStreamCloneAgainstOriginal(clone, original) { + isnot(clone.id.length, 0, "Stream clone should have an id string"); + isnot(clone, original, "Stream clone should be different from the original"); + isnot( + clone.id, + original.id, + "Stream clone's id should be different from the original's" + ); + is( + clone.getAudioTracks().length, + original.getAudioTracks().length, + "All audio tracks should get cloned" + ); + is( + clone.getVideoTracks().length, + original.getVideoTracks().length, + "All video tracks should get cloned" + ); + is(clone.active, original.active, "Active state should be preserved"); + original + .getTracks() + .forEach(t => + ok(!clone.getTrackById(t.id), "The clone's tracks should be originals") + ); +} + +function checkMediaStreamTrackCloneAgainstOriginal(clone, original) { + isnot(clone.id.length, 0, "Track clone should have an id string"); + isnot(clone, original, "Track clone should be different from the original"); + isnot( + clone.id, + original.id, + "Track clone's id should be different from the original's" + ); + is( + clone.kind, + original.kind, + "Track clone's kind should be same as the original's" + ); + is( + clone.enabled, + original.enabled, + "Track clone's kind should be same as the original's" + ); + is( + clone.readyState, + original.readyState, + "Track clone's readyState should be same as the original's" + ); + is( + clone.muted, + original.muted, + "Track clone's muted state should be same as the original's" + ); +} + +/*** Utility methods */ + +/** The dreadful setTimeout, use sparingly */ +function wait(time, message) { + return new Promise(r => setTimeout(() => r(message), time)); +} + +/** The even more dreadful setInterval, use even more sparingly */ +function waitUntil(func, time) { + return new Promise(resolve => { + var interval = setInterval(() => { + if (func()) { + clearInterval(interval); + resolve(); + } + }, time || 200); + }); +} + +/** Time out while waiting for a promise to get resolved or rejected. */ +var timeout = (promise, time, msg) => + Promise.race([ + promise, + wait(time).then(() => Promise.reject(new Error(msg))), + ]); + +/** Adds a |finally| function to a promise whose argument is invoked whether the + * promise is resolved or rejected, and that does not interfere with chaining.*/ +var addFinallyToPromise = promise => { + promise.finally = func => { + return promise.then( + result => { + func(); + return Promise.resolve(result); + }, + error => { + func(); + return Promise.reject(error); + } + ); + }; + return promise; +}; + +/** Use event listener to call passed-in function on fire until it returns true */ +var listenUntil = (target, eventName, onFire) => { + return new Promise(resolve => + target.addEventListener(eventName, function callback(event) { + var result = onFire(event); + if (result) { + target.removeEventListener(eventName, callback); + resolve(result); + } + }) + ); +}; + +/* Test that a function throws the right error */ +function mustThrowWith(msg, reason, f) { + try { + f(); + ok(false, msg + " must throw"); + } catch (e) { + is(e.name, reason, msg + " must throw: " + e.message); + } +} + +/* Get a dummy audio track */ +function getSilentTrack() { + let ctx = new AudioContext(), + oscillator = ctx.createOscillator(); + let dst = oscillator.connect(ctx.createMediaStreamDestination()); + oscillator.start(); + return Object.assign(dst.stream.getAudioTracks()[0], { enabled: false }); +} + +function getBlackTrack({ width = 640, height = 480 } = {}) { + let canvas = Object.assign(document.createElement("canvas"), { + width, + height, + }); + canvas.getContext("2d").fillRect(0, 0, width, height); + let stream = canvas.captureStream(); + return Object.assign(stream.getVideoTracks()[0], { enabled: false }); +} + +/*** Test control flow methods */ + +/** + * Generates a callback function fired only under unexpected circumstances + * while running the tests. The generated function kills off the test as well + * gracefully. + * + * @param {String} [message] + * An optional message to show if no object gets passed into the + * generated callback method. + */ +function generateErrorCallback(message) { + var stack = new Error().stack.split("\n"); + stack.shift(); // Don't include this instantiation frame + + /** + * @param {object} aObj + * The object fired back from the callback + */ + return aObj => { + if (aObj) { + if (aObj.name && aObj.message) { + ok( + false, + "Unexpected callback for '" + + aObj.name + + "' with message = '" + + aObj.message + + "' at " + + JSON.stringify(stack) + ); + } else { + ok( + false, + "Unexpected callback with = '" + + aObj + + "' at: " + + JSON.stringify(stack) + ); + } + } else { + ok( + false, + "Unexpected callback with message = '" + + message + + "' at: " + + JSON.stringify(stack) + ); + } + throw new Error("Unexpected callback"); + }; +} + +var unexpectedEventArrived; +var rejectOnUnexpectedEvent = new Promise((x, reject) => { + unexpectedEventArrived = reject; +}); + +/** + * Generates a callback function fired only for unexpected events happening. + * + * @param {String} description + Description of the object for which the event has been fired + * @param {String} eventName + Name of the unexpected event + */ +function unexpectedEvent(message, eventName) { + var stack = new Error().stack.split("\n"); + stack.shift(); // Don't include this instantiation frame + + return e => { + var details = + "Unexpected event '" + + eventName + + "' fired with message = '" + + message + + "' at: " + + JSON.stringify(stack); + ok(false, details); + unexpectedEventArrived(new Error(details)); + }; +} + +/** + * Implements the one-shot event pattern used throughout. Each of the 'onxxx' + * attributes on the wrappers can be set with a custom handler. Prior to the + * handler being set, if the event fires, it causes the test execution to halt. + * That handler is used exactly once, after which the original, error-generating + * handler is re-installed. Thus, each event handler is used at most once. + * + * @param {object} wrapper + * The wrapper on which the psuedo-handler is installed + * @param {object} obj + * The real source of events + * @param {string} event + * The name of the event + */ +function createOneShotEventWrapper(wrapper, obj, event) { + var onx = "on" + event; + var unexpected = unexpectedEvent(wrapper, event); + wrapper[onx] = unexpected; + obj[onx] = e => { + info(wrapper + ': "on' + event + '" event fired'); + e.wrapper = wrapper; + wrapper[onx](e); + wrapper[onx] = unexpected; + }; +} + +/** + * Returns a promise that resolves when `target` has raised an event with the + * given name the given number of times. Cancel the returned promise by passing + * in a `cancel` promise and resolving it. + * + * @param {object} target + * The target on which the event should occur. + * @param {string} name + * The name of the event that should occur. + * @param {integer} count + * Optional number of times the event should be raised before resolving. + * @param {promise} cancel + * Optional promise that on resolving rejects the returned promise, + * so we can avoid logging results after a test has finished. + * @returns {promise} A promise that resolves to the last of the seen events. + */ +function haveEvents(target, name, count, cancel) { + var listener; + var counter = count || 1; + return Promise.race([ + (cancel || new Promise(() => {})).then(e => Promise.reject(e)), + new Promise(resolve => + target.addEventListener( + name, + (listener = e => --counter < 1 && resolve(e)) + ) + ), + ]).then(e => (target.removeEventListener(name, listener), e)); +} + +/** + * Returns a promise that resolves when `target` has raised an event with the + * given name. Cancel the returned promise by passing in a `cancel` promise and + * resolving it. + * + * @param {object} target + * The target on which the event should occur. + * @param {string} name + * The name of the event that should occur. + * @param {promise} cancel + * Optional promise that on resolving rejects the returned promise, + * so we can avoid logging results after a test has finished. + * @returns {promise} A promise that resolves to the seen event. + */ +function haveEvent(target, name, cancel) { + return haveEvents(target, name, 1, cancel); +} + +/** + * Returns a promise that resolves if the target has not seen the given event + * after one crank (or until the given timeoutPromise resolves) of the event + * loop. + * + * @param {object} target + * The target on which the event should not occur. + * @param {string} name + * The name of the event that should not occur. + * @param {promise} timeoutPromise + * Optional promise defining how long we should wait before resolving. + * @returns {promise} A promise that is rejected if we see the given event, or + * resolves after a timeout otherwise. + */ +function haveNoEvent(target, name, timeoutPromise) { + return haveEvent(target, name, timeoutPromise || wait(0)).then( + () => Promise.reject(new Error("Too many " + name + " events")), + () => {} + ); +} + +/** + * Returns a promise that resolves after the target has seen the given number + * of events but no such event in a following crank of the event loop. + * + * @param {object} target + * The target on which the events should occur. + * @param {string} name + * The name of the event that should occur. + * @param {integer} count + * Optional number of times the event should be raised before resolving. + * @param {promise} cancel + * Optional promise that on resolving rejects the returned promise, + * so we can avoid logging results after a test has finished. + * @returns {promise} A promise that resolves to the last of the seen events. + */ +function haveEventsButNoMore(target, name, count, cancel) { + return haveEvents(target, name, count, cancel).then(e => + haveNoEvent(target, name).then(() => e) + ); +} + +/* + * Resolves the returned promise with an object with usage and reportCount + * properties. `usage` is in the same units as reported by the reporter for + * `path`. + */ +const collectMemoryUsage = async path => { + const MemoryReporterManager = Cc[ + "@mozilla.org/memory-reporter-manager;1" + ].getService(Ci.nsIMemoryReporterManager); + + let usage = 0; + let reportCount = 0; + await new Promise(resolve => + MemoryReporterManager.getReports( + (aProcess, aPath, aKind, aUnits, aAmount, aDesc) => { + if (aPath != path) { + return; + } + ++reportCount; + usage += aAmount; + }, + null, + resolve, + null, + /* anonymized = */ false + ) + ); + return { usage, reportCount }; +}; + +// Some DNS helper functions +const dnsLookup = async hostname => { + // Convenience API for various networking related stuff. _Almost_ convenient + // enough. + const neckoDashboard = SpecialPowers.Cc[ + "@mozilla.org/network/dashboard;1" + ].getService(Ci.nsIDashboard); + + const results = await new Promise(r => { + neckoDashboard.requestDNSLookup(hostname, results => { + r(SpecialPowers.wrap(results)); + }); + }); + + // |address| is an array-like dictionary (ie; keys are all integers). + // We convert to an array to make it less unwieldy. + const addresses = [...results.address]; + info(`DNS results for ${hostname}: ${JSON.stringify(addresses)}`); + return addresses; +}; + +const dnsLookupV4 = async hostname => { + const addresses = await dnsLookup(hostname); + return addresses.filter(address => !address.includes(":")); +}; + +const dnsLookupV6 = async hostname => { + const addresses = await dnsLookup(hostname); + return addresses.filter(address => address.includes(":")); +}; + +const getTurnHostname = turnUrl => { + const urlNoParams = turnUrl.split("?")[0]; + // Strip off scheme + const hostAndMaybePort = urlNoParams.split(":", 2)[1]; + if (hostAndMaybePort[0] == "[") { + // IPV6 literal, strip out '[', and split at closing ']' + return hostAndMaybePort.substring(1).split("]")[0]; + } + return hostAndMaybePort.split(":")[0]; +}; + +// Yo dawg I heard you like yo dawg I heard you like Proxies +// Example: let value = await GleanTest.category.metric.testGetValue(); +// For labeled metrics: +// let value = await GleanTest.category.metric["label"].testGetValue(); +// Please don't try to use the string "testGetValue" as a label. +const GleanTest = new Proxy( + {}, + { + get(target, categoryName, receiver) { + return new Proxy( + {}, + { + get(target, metricName, receiver) { + return new Proxy( + { + async testGetValue() { + return SpecialPowers.spawnChrome( + [categoryName, metricName], + async (categoryName, metricName) => { + await Services.fog.testFlushAllChildren(); + const window = this.browsingContext.topChromeWindow; + return window.Glean[categoryName][ + metricName + ].testGetValue(); + } + ); + }, + }, + { + get(target, prop, receiver) { + // The only prop that will be there is testGetValue, but we + // might add more later. + if (prop in target) { + return target[prop]; + } + + // |prop| must be a label? + const label = prop; + return { + async testGetValue() { + return SpecialPowers.spawnChrome( + [categoryName, metricName, label], + async (categoryName, metricName, label) => { + await Services.fog.testFlushAllChildren(); + const window = this.browsingContext.topChromeWindow; + return window.Glean[categoryName][metricName][ + label + ].testGetValue(); + } + ); + }, + }; + }, + } + ); + }, + } + ); + }, + } +); + +/** + * This class executes a series of functions in a continuous sequence. + * Promise-bearing functions are executed after the previous promise completes. + * + * @constructor + * @param {object} framework + * A back reference to the framework which makes use of the class. It is + * passed to each command callback. + * @param {function[]} commandList + * Commands to set during initialization + */ +function CommandChain(framework, commandList) { + this._framework = framework; + this.commands = commandList || []; +} + +CommandChain.prototype = { + /** + * Start the command chain. This returns a promise that always resolves + * cleanly (this catches errors and fails the test case). + */ + execute() { + return this.commands + .reduce((prev, next, i) => { + if (typeof next !== "function" || !next.name) { + throw new Error("registered non-function" + next); + } + + return prev.then(() => { + info("Run step " + (i + 1) + ": " + next.name); + return Promise.race([next(this._framework), rejectOnUnexpectedEvent]); + }); + }, Promise.resolve()) + .catch(e => + ok( + false, + "Error in test execution: " + + e + + (typeof e.stack === "string" + ? " " + e.stack.split("\n").join(" ... ") + : "") + ) + ); + }, + + /** + * Add new commands to the end of the chain + */ + append(commands) { + this.commands = this.commands.concat(commands); + }, + + /** + * Returns the index of the specified command in the chain. + * @param {occurrence} Optional param specifying which occurrence to match, + * with 0 representing the first occurrence. + */ + indexOf(functionOrName, occurrence) { + occurrence = occurrence || 0; + return this.commands.findIndex(func => { + if (typeof functionOrName === "string") { + if (func.name !== functionOrName) { + return false; + } + } else if (func !== functionOrName) { + return false; + } + if (occurrence) { + --occurrence; + return false; + } + return true; + }); + }, + + mustHaveIndexOf(functionOrName, occurrence) { + var index = this.indexOf(functionOrName, occurrence); + if (index == -1) { + throw new Error("Unknown test: " + functionOrName); + } + return index; + }, + + /** + * Inserts the new commands after the specified command. + */ + insertAfter(functionOrName, commands, all, occurrence) { + this._insertHelper(functionOrName, commands, 1, all, occurrence); + }, + + /** + * Inserts the new commands after every occurrence of the specified command + */ + insertAfterEach(functionOrName, commands) { + this._insertHelper(functionOrName, commands, 1, true); + }, + + /** + * Inserts the new commands before the specified command. + */ + insertBefore(functionOrName, commands, all, occurrence) { + this._insertHelper(functionOrName, commands, 0, all, occurrence); + }, + + _insertHelper(functionOrName, commands, delta, all, occurrence) { + occurrence = occurrence || 0; + for ( + var index = this.mustHaveIndexOf(functionOrName, occurrence); + index !== -1; + index = this.indexOf(functionOrName, ++occurrence) + ) { + this.commands = [].concat( + this.commands.slice(0, index + delta), + commands, + this.commands.slice(index + delta) + ); + if (!all) { + break; + } + } + }, + + /** + * Removes the specified command, returns what was removed. + */ + remove(functionOrName, occurrence) { + return this.commands.splice( + this.mustHaveIndexOf(functionOrName, occurrence), + 1 + ); + }, + + /** + * Removes all commands after the specified one, returns what was removed. + */ + removeAfter(functionOrName, occurrence) { + return this.commands.splice( + this.mustHaveIndexOf(functionOrName, occurrence) + 1 + ); + }, + + /** + * Removes all commands before the specified one, returns what was removed. + */ + removeBefore(functionOrName, occurrence) { + return this.commands.splice( + 0, + this.mustHaveIndexOf(functionOrName, occurrence) + ); + }, + + /** + * Replaces a single command, returns what was removed. + */ + replace(functionOrName, commands) { + this.insertBefore(functionOrName, commands); + return this.remove(functionOrName); + }, + + /** + * Replaces all commands after the specified one, returns what was removed. + */ + replaceAfter(functionOrName, commands, occurrence) { + var oldCommands = this.removeAfter(functionOrName, occurrence); + this.append(commands); + return oldCommands; + }, + + /** + * Replaces all commands before the specified one, returns what was removed. + */ + replaceBefore(functionOrName, commands) { + var oldCommands = this.removeBefore(functionOrName); + this.insertBefore(functionOrName, commands); + return oldCommands; + }, + + /** + * Remove all commands whose name match the specified regex. + */ + filterOut(id_match) { + this.commands = this.commands.filter(c => !id_match.test(c.name)); + }, +}; + +function AudioStreamHelper() { + this._context = new AudioContext(); +} + +AudioStreamHelper.prototype = { + checkAudio(stream, analyser, fun) { + /* + analyser.enableDebugCanvas(); + return analyser.waitForAnalysisSuccess(fun) + .then(() => analyser.disableDebugCanvas()); + */ + return analyser.waitForAnalysisSuccess(fun); + }, + + checkAudioFlowing(stream) { + var analyser = new AudioStreamAnalyser(this._context, stream); + var freq = analyser.binIndexForFrequency(TEST_AUDIO_FREQ); + return this.checkAudio(stream, analyser, array => array[freq] > 200); + }, + + checkAudioNotFlowing(stream) { + var analyser = new AudioStreamAnalyser(this._context, stream); + var freq = analyser.binIndexForFrequency(TEST_AUDIO_FREQ); + return this.checkAudio(stream, analyser, array => array[freq] < 50); + }, +}; + +class VideoFrameEmitter { + constructor(color1, color2, width, height) { + if (!width) { + width = 50; + } + if (!height) { + height = width; + } + this._helper = new CaptureStreamTestHelper2D(width, height); + this._canvas = this._helper.createAndAppendElement( + "canvas", + "source_canvas" + ); + this._canvas.width = width; + this._canvas.height = height; + this._color1 = color1 ? color1 : this._helper.green; + this._color2 = color2 ? color2 : this._helper.red; + // Make sure this is initted + this._helper.drawColor(this._canvas, this._color1); + this._stream = this._canvas.captureStream(); + this._started = false; + } + + stream() { + return this._stream; + } + + helper() { + return this._helper; + } + + colors(color1, color2) { + this._color1 = color1 ? color1 : this._helper.green; + this._color2 = color2 ? color2 : this._helper.red; + try { + this._helper.drawColor(this._canvas, this._color1); + } catch (e) { + // ignore; stream might have shut down + } + } + + size(width, height) { + this._canvas.width = width; + this._canvas.height = height; + } + + start() { + if (this._started) { + info("*** emitter already started"); + return; + } + + let i = 0; + this._started = true; + this._intervalId = setInterval(() => { + try { + this._helper.drawColor(this._canvas, i ? this._color1 : this._color2); + i = 1 - i; + } catch (e) { + // ignore; stream might have shut down, and we don't bother clearing + // the setInterval. + } + }, 500); + } + + stop() { + if (this._started) { + clearInterval(this._intervalId); + this._started = false; + } + } +} + +class VideoStreamHelper { + constructor() { + this._helper = new CaptureStreamTestHelper2D(50, 50); + } + + async checkHasFrame(video, { offsetX, offsetY, threshold } = {}) { + const h = this._helper; + await h.waitForPixel( + video, + px => { + let result = h.isOpaquePixelNot(px, h.black, threshold); + info( + "Checking that we have a frame, got [" + + Array.from(px) + + "]. Ref=[" + + Array.from(h.black.data) + + "]. Threshold=" + + threshold + + ". Pass=" + + result + ); + return result; + }, + { offsetX, offsetY } + ); + } + + async checkVideoPlaying( + video, + { offsetX = 10, offsetY = 10, threshold = 16 } = {} + ) { + const h = this._helper; + await this.checkHasFrame(video, { offsetX, offsetY, threshold }); + let startPixel = { + data: h.getPixel(video, offsetX, offsetY), + name: "startcolor", + }; + await h.waitForPixel( + video, + px => { + let result = h.isPixelNot(px, startPixel, threshold); + info( + "Checking playing, [" + + Array.from(px) + + "] vs [" + + Array.from(startPixel.data) + + "]. Threshold=" + + threshold + + " Pass=" + + result + ); + return result; + }, + { offsetX, offsetY } + ); + } + + async checkVideoPaused( + video, + { offsetX = 10, offsetY = 10, threshold = 16, time = 5000 } = {} + ) { + const h = this._helper; + await this.checkHasFrame(video, { offsetX, offsetY, threshold }); + let startPixel = { + data: h.getPixel(video, offsetX, offsetY), + name: "startcolor", + }; + try { + await h.waitForPixel( + video, + px => { + let result = h.isOpaquePixelNot(px, startPixel, threshold); + info( + "Checking paused, [" + + Array.from(px) + + "] vs [" + + Array.from(startPixel.data) + + "]. Threshold=" + + threshold + + " Pass=" + + result + ); + return result; + }, + { offsetX, offsetY, cancel: wait(time, "timeout") } + ); + ok(false, "Frame changed within " + time / 1000 + " seconds"); + } catch (e) { + is( + e, + "timeout", + "Frame shouldn't change for " + time / 1000 + " seconds" + ); + } + } +} + +(function () { + var el = document.createElement("link"); + el.rel = "stylesheet"; + el.type = "text/css"; + el.href = "/tests/SimpleTest/test.css"; + document.head.appendChild(el); +})(); diff --git a/dom/media/webrtc/tests/mochitests/helpers_from_wpt/sdp.js b/dom/media/webrtc/tests/mochitests/helpers_from_wpt/sdp.js new file mode 100644 index 0000000000..6460f64a44 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/helpers_from_wpt/sdp.js @@ -0,0 +1,889 @@ +/* eslint-env node */ +"use strict"; + +// SDP helpers. +var SDPUtils = {}; + +// Generate an alphanumeric identifier for cname or mids. +// TODO: use UUIDs instead? https://gist.github.com/jed/982883 +SDPUtils.generateIdentifier = function() { + return Math.random() + .toString(36) + .substr(2, 10); +}; + +// The RTCP CNAME used by all peerconnections from the same JS. +SDPUtils.localCName = SDPUtils.generateIdentifier(); + +// Splits SDP into lines, dealing with both CRLF and LF. +SDPUtils.splitLines = function(blob) { + return blob + .trim() + .split("\n") + .map(function(line) { + return line.trim(); + }); +}; +// Splits SDP into sessionpart and mediasections. Ensures CRLF. +SDPUtils.splitSections = function(blob) { + var parts = blob.split("\nm="); + return parts.map(function(part, index) { + return (index > 0 ? "m=" + part : part).trim() + "\r\n"; + }); +}; + +// returns the session description. +SDPUtils.getDescription = function(blob) { + var sections = SDPUtils.splitSections(blob); + return sections && sections[0]; +}; + +// returns the individual media sections. +SDPUtils.getMediaSections = function(blob) { + var sections = SDPUtils.splitSections(blob); + sections.shift(); + return sections; +}; + +// Returns lines that start with a certain prefix. +SDPUtils.matchPrefix = function(blob, prefix) { + return SDPUtils.splitLines(blob).filter(function(line) { + return line.indexOf(prefix) === 0; + }); +}; + +// Parses an ICE candidate line. Sample input: +// candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8 +// rport 55996" +SDPUtils.parseCandidate = function(line) { + var parts; + // Parse both variants. + if (line.indexOf("a=candidate:") === 0) { + parts = line.substring(12).split(" "); + } else { + parts = line.substring(10).split(" "); + } + + var candidate = { + foundation: parts[0], + component: parseInt(parts[1], 10), + protocol: parts[2].toLowerCase(), + priority: parseInt(parts[3], 10), + ip: parts[4], + address: parts[4], // address is an alias for ip. + port: parseInt(parts[5], 10), + // skip parts[6] == 'typ' + type: parts[7], + }; + + for (var i = 8; i < parts.length; i += 2) { + switch (parts[i]) { + case "raddr": + candidate.relatedAddress = parts[i + 1]; + break; + case "rport": + candidate.relatedPort = parseInt(parts[i + 1], 10); + break; + case "tcptype": + candidate.tcpType = parts[i + 1]; + break; + case "ufrag": + candidate.ufrag = parts[i + 1]; // for backward compability. + candidate.usernameFragment = parts[i + 1]; + break; + default: + // extension handling, in particular ufrag + candidate[parts[i]] = parts[i + 1]; + break; + } + } + return candidate; +}; + +// Translates a candidate object into SDP candidate attribute. +SDPUtils.writeCandidate = function(candidate) { + var sdp = []; + sdp.push(candidate.foundation); + sdp.push(candidate.component); + sdp.push(candidate.protocol.toUpperCase()); + sdp.push(candidate.priority); + sdp.push(candidate.address || candidate.ip); + sdp.push(candidate.port); + + var type = candidate.type; + sdp.push("typ"); + sdp.push(type); + if (type !== "host" && candidate.relatedAddress && candidate.relatedPort) { + sdp.push("raddr"); + sdp.push(candidate.relatedAddress); + sdp.push("rport"); + sdp.push(candidate.relatedPort); + } + if (candidate.tcpType && candidate.protocol.toLowerCase() === "tcp") { + sdp.push("tcptype"); + sdp.push(candidate.tcpType); + } + if (candidate.usernameFragment || candidate.ufrag) { + sdp.push("ufrag"); + sdp.push(candidate.usernameFragment || candidate.ufrag); + } + return "candidate:" + sdp.join(" "); +}; + +// Parses an ice-options line, returns an array of option tags. +// a=ice-options:foo bar +SDPUtils.parseIceOptions = function(line) { + return line.substr(14).split(" "); +}; + +// Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input: +// a=rtpmap:111 opus/48000/2 +SDPUtils.parseRtpMap = function(line) { + var parts = line.substr(9).split(" "); + var parsed = { + payloadType: parseInt(parts.shift(), 10), // was: id + }; + + parts = parts[0].split("/"); + + parsed.name = parts[0]; + parsed.clockRate = parseInt(parts[1], 10); // was: clockrate + parsed.channels = parts.length === 3 ? parseInt(parts[2], 10) : 1; + // legacy alias, got renamed back to channels in ORTC. + parsed.numChannels = parsed.channels; + return parsed; +}; + +// Generate an a=rtpmap line from RTCRtpCodecCapability or +// RTCRtpCodecParameters. +SDPUtils.writeRtpMap = function(codec) { + var pt = codec.payloadType; + if (codec.preferredPayloadType !== undefined) { + pt = codec.preferredPayloadType; + } + var channels = codec.channels || codec.numChannels || 1; + return ( + "a=rtpmap:" + + pt + + " " + + codec.name + + "/" + + codec.clockRate + + (channels !== 1 ? "/" + channels : "") + + "\r\n" + ); +}; + +// Parses an a=extmap line (headerextension from RFC 5285). Sample input: +// a=extmap:2 urn:ietf:params:rtp-hdrext:toffset +// a=extmap:2/sendonly urn:ietf:params:rtp-hdrext:toffset +SDPUtils.parseExtmap = function(line) { + var parts = line.substr(9).split(" "); + return { + id: parseInt(parts[0], 10), + direction: parts[0].indexOf("/") > 0 ? parts[0].split("/")[1] : "sendrecv", + uri: parts[1], + }; +}; + +// Generates a=extmap line from RTCRtpHeaderExtensionParameters or +// RTCRtpHeaderExtension. +SDPUtils.writeExtmap = function(headerExtension) { + return ( + "a=extmap:" + + (headerExtension.id || headerExtension.preferredId) + + (headerExtension.direction && headerExtension.direction !== "sendrecv" + ? "/" + headerExtension.direction + : "") + + " " + + headerExtension.uri + + "\r\n" + ); +}; + +// Parses an ftmp line, returns dictionary. Sample input: +// a=fmtp:96 vbr=on;cng=on +// Also deals with vbr=on; cng=on +SDPUtils.parseFmtp = function(line) { + var parsed = {}; + var kv; + var parts = line.substr(line.indexOf(" ") + 1).split(";"); + for (var j = 0; j < parts.length; j++) { + kv = parts[j].trim().split("="); + parsed[kv[0].trim()] = kv[1]; + } + return parsed; +}; + +// Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters. +SDPUtils.writeFmtp = function(codec) { + var line = ""; + var pt = codec.payloadType; + if (codec.preferredPayloadType !== undefined) { + pt = codec.preferredPayloadType; + } + if (codec.parameters && Object.keys(codec.parameters).length) { + var params = []; + Object.keys(codec.parameters).forEach(function(param) { + if (codec.parameters[param]) { + params.push(param + "=" + codec.parameters[param]); + } else { + params.push(param); + } + }); + line += "a=fmtp:" + pt + " " + params.join(";") + "\r\n"; + } + return line; +}; + +// Parses an rtcp-fb line, returns RTCPRtcpFeedback object. Sample input: +// a=rtcp-fb:98 nack rpsi +SDPUtils.parseRtcpFb = function(line) { + var parts = line.substr(line.indexOf(" ") + 1).split(" "); + return { + type: parts.shift(), + parameter: parts.join(" "), + }; +}; +// Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters. +SDPUtils.writeRtcpFb = function(codec) { + var lines = ""; + var pt = codec.payloadType; + if (codec.preferredPayloadType !== undefined) { + pt = codec.preferredPayloadType; + } + if (codec.rtcpFeedback && codec.rtcpFeedback.length) { + // FIXME: special handling for trr-int? + codec.rtcpFeedback.forEach(function(fb) { + lines += + "a=rtcp-fb:" + + pt + + " " + + fb.type + + (fb.parameter && fb.parameter.length ? " " + fb.parameter : "") + + "\r\n"; + }); + } + return lines; +}; + +// Parses an RFC 5576 ssrc media attribute. Sample input: +// a=ssrc:3735928559 cname:something +SDPUtils.parseSsrcMedia = function(line) { + var sp = line.indexOf(" "); + var parts = { + ssrc: parseInt(line.substr(7, sp - 7), 10), + }; + var colon = line.indexOf(":", sp); + if (colon > -1) { + parts.attribute = line.substr(sp + 1, colon - sp - 1); + parts.value = line.substr(colon + 1); + } else { + parts.attribute = line.substr(sp + 1); + } + return parts; +}; + +SDPUtils.parseSsrcGroup = function(line) { + var parts = line.substr(13).split(" "); + return { + semantics: parts.shift(), + ssrcs: parts.map(function(ssrc) { + return parseInt(ssrc, 10); + }), + }; +}; + +// Extracts the MID (RFC 5888) from a media section. +// returns the MID or undefined if no mid line was found. +SDPUtils.getMid = function(mediaSection) { + var mid = SDPUtils.matchPrefix(mediaSection, "a=mid:")[0]; + if (mid) { + return mid.substr(6); + } +}; + +SDPUtils.parseFingerprint = function(line) { + var parts = line.substr(14).split(" "); + return { + algorithm: parts[0].toLowerCase(), // algorithm is case-sensitive in Edge. + value: parts[1], + }; +}; + +// Extracts DTLS parameters from SDP media section or sessionpart. +// FIXME: for consistency with other functions this should only +// get the fingerprint line as input. See also getIceParameters. +SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) { + var lines = SDPUtils.matchPrefix( + mediaSection + sessionpart, + "a=fingerprint:" + ); + // Note: a=setup line is ignored since we use the 'auto' role. + // Note2: 'algorithm' is not case sensitive except in Edge. + return { + role: "auto", + fingerprints: lines.map(SDPUtils.parseFingerprint), + }; +}; + +// Serializes DTLS parameters to SDP. +SDPUtils.writeDtlsParameters = function(params, setupType) { + var sdp = "a=setup:" + setupType + "\r\n"; + params.fingerprints.forEach(function(fp) { + sdp += "a=fingerprint:" + fp.algorithm + " " + fp.value + "\r\n"; + }); + return sdp; +}; + +// Parses a=crypto lines into +// https://rawgit.com/aboba/edgertc/master/msortc-rs4.html#dictionary-rtcsrtpsdesparameters-members +SDPUtils.parseCryptoLine = function(line) { + var parts = line.substr(9).split(" "); + return { + tag: parseInt(parts[0], 10), + cryptoSuite: parts[1], + keyParams: parts[2], + sessionParams: parts.slice(3), + }; +}; + +SDPUtils.writeCryptoLine = function(parameters) { + return ( + "a=crypto:" + + parameters.tag + + " " + + parameters.cryptoSuite + + " " + + (typeof parameters.keyParams === "object" + ? SDPUtils.writeCryptoKeyParams(parameters.keyParams) + : parameters.keyParams) + + (parameters.sessionParams ? " " + parameters.sessionParams.join(" ") : "") + + "\r\n" + ); +}; + +// Parses the crypto key parameters into +// https://rawgit.com/aboba/edgertc/master/msortc-rs4.html#rtcsrtpkeyparam* +SDPUtils.parseCryptoKeyParams = function(keyParams) { + if (keyParams.indexOf("inline:") !== 0) { + return null; + } + var parts = keyParams.substr(7).split("|"); + return { + keyMethod: "inline", + keySalt: parts[0], + lifeTime: parts[1], + mkiValue: parts[2] ? parts[2].split(":")[0] : undefined, + mkiLength: parts[2] ? parts[2].split(":")[1] : undefined, + }; +}; + +SDPUtils.writeCryptoKeyParams = function(keyParams) { + return ( + keyParams.keyMethod + + ":" + + keyParams.keySalt + + (keyParams.lifeTime ? "|" + keyParams.lifeTime : "") + + (keyParams.mkiValue && keyParams.mkiLength + ? "|" + keyParams.mkiValue + ":" + keyParams.mkiLength + : "") + ); +}; + +// Extracts all SDES paramters. +SDPUtils.getCryptoParameters = function(mediaSection, sessionpart) { + var lines = SDPUtils.matchPrefix(mediaSection + sessionpart, "a=crypto:"); + return lines.map(SDPUtils.parseCryptoLine); +}; + +// Parses ICE information from SDP media section or sessionpart. +// FIXME: for consistency with other functions this should only +// get the ice-ufrag and ice-pwd lines as input. +SDPUtils.getIceParameters = function(mediaSection, sessionpart) { + var ufrag = SDPUtils.matchPrefix( + mediaSection + sessionpart, + "a=ice-ufrag:" + )[0]; + var pwd = SDPUtils.matchPrefix(mediaSection + sessionpart, "a=ice-pwd:")[0]; + if (!(ufrag && pwd)) { + return null; + } + return { + usernameFragment: ufrag.substr(12), + password: pwd.substr(10), + }; +}; + +// Serializes ICE parameters to SDP. +SDPUtils.writeIceParameters = function(params) { + return ( + "a=ice-ufrag:" + + params.usernameFragment + + "\r\n" + + "a=ice-pwd:" + + params.password + + "\r\n" + ); +}; + +// Parses the SDP media section and returns RTCRtpParameters. +SDPUtils.parseRtpParameters = function(mediaSection) { + var description = { + codecs: [], + headerExtensions: [], + fecMechanisms: [], + rtcp: [], + }; + var lines = SDPUtils.splitLines(mediaSection); + var mline = lines[0].split(" "); + for (var i = 3; i < mline.length; i++) { + // find all codecs from mline[3..] + var pt = mline[i]; + var rtpmapline = SDPUtils.matchPrefix( + mediaSection, + "a=rtpmap:" + pt + " " + )[0]; + if (rtpmapline) { + var codec = SDPUtils.parseRtpMap(rtpmapline); + var fmtps = SDPUtils.matchPrefix(mediaSection, "a=fmtp:" + pt + " "); + // Only the first a=fmtp: is considered. + codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {}; + codec.rtcpFeedback = SDPUtils.matchPrefix( + mediaSection, + "a=rtcp-fb:" + pt + " " + ).map(SDPUtils.parseRtcpFb); + description.codecs.push(codec); + // parse FEC mechanisms from rtpmap lines. + switch (codec.name.toUpperCase()) { + case "RED": + case "ULPFEC": + description.fecMechanisms.push(codec.name.toUpperCase()); + break; + default: + // only RED and ULPFEC are recognized as FEC mechanisms. + break; + } + } + } + SDPUtils.matchPrefix(mediaSection, "a=extmap:").forEach(function(line) { + description.headerExtensions.push(SDPUtils.parseExtmap(line)); + }); + // FIXME: parse rtcp. + return description; +}; + +// Generates parts of the SDP media section describing the capabilities / +// parameters. +SDPUtils.writeRtpDescription = function(kind, caps) { + var sdp = ""; + + // Build the mline. + sdp += "m=" + kind + " "; + sdp += caps.codecs.length > 0 ? "9" : "0"; // reject if no codecs. + sdp += " UDP/TLS/RTP/SAVPF "; + sdp += + caps.codecs + .map(function(codec) { + if (codec.preferredPayloadType !== undefined) { + return codec.preferredPayloadType; + } + return codec.payloadType; + }) + .join(" ") + "\r\n"; + + sdp += "c=IN IP4 0.0.0.0\r\n"; + sdp += "a=rtcp:9 IN IP4 0.0.0.0\r\n"; + + // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb. + caps.codecs.forEach(function(codec) { + sdp += SDPUtils.writeRtpMap(codec); + sdp += SDPUtils.writeFmtp(codec); + sdp += SDPUtils.writeRtcpFb(codec); + }); + var maxptime = 0; + caps.codecs.forEach(function(codec) { + if (codec.maxptime > maxptime) { + maxptime = codec.maxptime; + } + }); + if (maxptime > 0) { + sdp += "a=maxptime:" + maxptime + "\r\n"; + } + sdp += "a=rtcp-mux\r\n"; + + if (caps.headerExtensions) { + caps.headerExtensions.forEach(function(extension) { + sdp += SDPUtils.writeExtmap(extension); + }); + } + // FIXME: write fecMechanisms. + return sdp; +}; + +// Parses the SDP media section and returns an array of +// RTCRtpEncodingParameters. +SDPUtils.parseRtpEncodingParameters = function(mediaSection) { + var encodingParameters = []; + var description = SDPUtils.parseRtpParameters(mediaSection); + var hasRed = description.fecMechanisms.indexOf("RED") !== -1; + var hasUlpfec = description.fecMechanisms.indexOf("ULPFEC") !== -1; + + // filter a=ssrc:... cname:, ignore PlanB-msid + var ssrcs = SDPUtils.matchPrefix(mediaSection, "a=ssrc:") + .map(function(line) { + return SDPUtils.parseSsrcMedia(line); + }) + .filter(function(parts) { + return parts.attribute === "cname"; + }); + var primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc; + var secondarySsrc; + + var flows = SDPUtils.matchPrefix(mediaSection, "a=ssrc-group:FID").map( + function(line) { + var parts = line.substr(17).split(" "); + return parts.map(function(part) { + return parseInt(part, 10); + }); + } + ); + if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) { + secondarySsrc = flows[0][1]; + } + + description.codecs.forEach(function(codec) { + if (codec.name.toUpperCase() === "RTX" && codec.parameters.apt) { + var encParam = { + ssrc: primarySsrc, + codecPayloadType: parseInt(codec.parameters.apt, 10), + }; + if (primarySsrc && secondarySsrc) { + encParam.rtx = { ssrc: secondarySsrc }; + } + encodingParameters.push(encParam); + if (hasRed) { + encParam = JSON.parse(JSON.stringify(encParam)); + encParam.fec = { + ssrc: primarySsrc, + mechanism: hasUlpfec ? "red+ulpfec" : "red", + }; + encodingParameters.push(encParam); + } + } + }); + if (encodingParameters.length === 0 && primarySsrc) { + encodingParameters.push({ + ssrc: primarySsrc, + }); + } + + // we support both b=AS and b=TIAS but interpret AS as TIAS. + var bandwidth = SDPUtils.matchPrefix(mediaSection, "b="); + if (bandwidth.length) { + if (bandwidth[0].indexOf("b=TIAS:") === 0) { + bandwidth = parseInt(bandwidth[0].substr(7), 10); + } else if (bandwidth[0].indexOf("b=AS:") === 0) { + // use formula from JSEP to convert b=AS to TIAS value. + bandwidth = + parseInt(bandwidth[0].substr(5), 10) * 1000 * 0.95 - 50 * 40 * 8; + } else { + bandwidth = undefined; + } + encodingParameters.forEach(function(params) { + params.maxBitrate = bandwidth; + }); + } + return encodingParameters; +}; + +// parses http://draft.ortc.org/#rtcrtcpparameters* +SDPUtils.parseRtcpParameters = function(mediaSection) { + var rtcpParameters = {}; + + // Gets the first SSRC. Note tha with RTX there might be multiple + // SSRCs. + var remoteSsrc = SDPUtils.matchPrefix(mediaSection, "a=ssrc:") + .map(function(line) { + return SDPUtils.parseSsrcMedia(line); + }) + .filter(function(obj) { + return obj.attribute === "cname"; + })[0]; + if (remoteSsrc) { + rtcpParameters.cname = remoteSsrc.value; + rtcpParameters.ssrc = remoteSsrc.ssrc; + } + + // Edge uses the compound attribute instead of reducedSize + // compound is !reducedSize + var rsize = SDPUtils.matchPrefix(mediaSection, "a=rtcp-rsize"); + rtcpParameters.reducedSize = rsize.length > 0; + rtcpParameters.compound = rsize.length === 0; + + // parses the rtcp-mux attrіbute. + // Note that Edge does not support unmuxed RTCP. + var mux = SDPUtils.matchPrefix(mediaSection, "a=rtcp-mux"); + rtcpParameters.mux = mux.length > 0; + + return rtcpParameters; +}; + +// parses either a=msid: or a=ssrc:... msid lines and returns +// the id of the MediaStream and MediaStreamTrack. +SDPUtils.parseMsid = function(mediaSection) { + var parts; + var spec = SDPUtils.matchPrefix(mediaSection, "a=msid:"); + if (spec.length === 1) { + parts = spec[0].substr(7).split(" "); + return { stream: parts[0], track: parts[1] }; + } + var planB = SDPUtils.matchPrefix(mediaSection, "a=ssrc:") + .map(function(line) { + return SDPUtils.parseSsrcMedia(line); + }) + .filter(function(msidParts) { + return msidParts.attribute === "msid"; + }); + if (planB.length > 0) { + parts = planB[0].value.split(" "); + return { stream: parts[0], track: parts[1] }; + } +}; + +// SCTP +// parses draft-ietf-mmusic-sctp-sdp-26 first and falls back +// to draft-ietf-mmusic-sctp-sdp-05 +SDPUtils.parseSctpDescription = function(mediaSection) { + var mline = SDPUtils.parseMLine(mediaSection); + var maxSizeLine = SDPUtils.matchPrefix(mediaSection, "a=max-message-size:"); + var maxMessageSize; + if (maxSizeLine.length > 0) { + maxMessageSize = parseInt(maxSizeLine[0].substr(19), 10); + } + if (isNaN(maxMessageSize)) { + maxMessageSize = 65536; + } + var sctpPort = SDPUtils.matchPrefix(mediaSection, "a=sctp-port:"); + if (sctpPort.length > 0) { + return { + port: parseInt(sctpPort[0].substr(12), 10), + protocol: mline.fmt, + maxMessageSize, + }; + } + var sctpMapLines = SDPUtils.matchPrefix(mediaSection, "a=sctpmap:"); + if (sctpMapLines.length > 0) { + var parts = SDPUtils.matchPrefix(mediaSection, "a=sctpmap:")[0] + .substr(10) + .split(" "); + return { + port: parseInt(parts[0], 10), + protocol: parts[1], + maxMessageSize, + }; + } +}; + +// SCTP +// outputs the draft-ietf-mmusic-sctp-sdp-26 version that all browsers +// support by now receiving in this format, unless we originally parsed +// as the draft-ietf-mmusic-sctp-sdp-05 format (indicated by the m-line +// protocol of DTLS/SCTP -- without UDP/ or TCP/) +SDPUtils.writeSctpDescription = function(media, sctp) { + var output = []; + if (media.protocol !== "DTLS/SCTP") { + output = [ + "m=" + media.kind + " 9 " + media.protocol + " " + sctp.protocol + "\r\n", + "c=IN IP4 0.0.0.0\r\n", + "a=sctp-port:" + sctp.port + "\r\n", + ]; + } else { + output = [ + "m=" + media.kind + " 9 " + media.protocol + " " + sctp.port + "\r\n", + "c=IN IP4 0.0.0.0\r\n", + "a=sctpmap:" + sctp.port + " " + sctp.protocol + " 65535\r\n", + ]; + } + if (sctp.maxMessageSize !== undefined) { + output.push("a=max-message-size:" + sctp.maxMessageSize + "\r\n"); + } + return output.join(""); +}; + +// Generate a session ID for SDP. +// https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-20#section-5.2.1 +// recommends using a cryptographically random +ve 64-bit value +// but right now this should be acceptable and within the right range +SDPUtils.generateSessionId = function() { + return Math.floor((Math.random() * 4294967296) + 1); +}; + +// Write boilder plate for start of SDP +// sessId argument is optional - if not supplied it will +// be generated randomly +// sessVersion is optional and defaults to 2 +// sessUser is optional and defaults to 'thisisadapterortc' +SDPUtils.writeSessionBoilerplate = function(sessId, sessVer, sessUser) { + var sessionId; + var version = sessVer !== undefined ? sessVer : 2; + if (sessId) { + sessionId = sessId; + } else { + sessionId = SDPUtils.generateSessionId(); + } + var user = sessUser || "thisisadapterortc"; + // FIXME: sess-id should be an NTP timestamp. + return ( + "v=0\r\n" + + "o=" + + user + + " " + + sessionId + + " " + + version + + " IN IP4 127.0.0.1\r\n" + + "s=-\r\n" + + "t=0 0\r\n" + ); +}; + +SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) { + var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps); + + // Map ICE parameters (ufrag, pwd) to SDP. + sdp += SDPUtils.writeIceParameters( + transceiver.iceGatherer.getLocalParameters() + ); + + // Map DTLS parameters to SDP. + sdp += SDPUtils.writeDtlsParameters( + transceiver.dtlsTransport.getLocalParameters(), + type === "offer" ? "actpass" : "active" + ); + + sdp += "a=mid:" + transceiver.mid + "\r\n"; + + if (transceiver.direction) { + sdp += "a=" + transceiver.direction + "\r\n"; + } else if (transceiver.rtpSender && transceiver.rtpReceiver) { + sdp += "a=sendrecv\r\n"; + } else if (transceiver.rtpSender) { + sdp += "a=sendonly\r\n"; + } else if (transceiver.rtpReceiver) { + sdp += "a=recvonly\r\n"; + } else { + sdp += "a=inactive\r\n"; + } + + if (transceiver.rtpSender) { + // spec. + var msid = + "msid:" + stream.id + " " + transceiver.rtpSender.track.id + "\r\n"; + sdp += "a=" + msid; + + // for Chrome. + sdp += "a=ssrc:" + transceiver.sendEncodingParameters[0].ssrc + " " + msid; + if (transceiver.sendEncodingParameters[0].rtx) { + sdp += + "a=ssrc:" + transceiver.sendEncodingParameters[0].rtx.ssrc + " " + msid; + sdp += + "a=ssrc-group:FID " + + transceiver.sendEncodingParameters[0].ssrc + + " " + + transceiver.sendEncodingParameters[0].rtx.ssrc + + "\r\n"; + } + } + // FIXME: this should be written by writeRtpDescription. + sdp += + "a=ssrc:" + + transceiver.sendEncodingParameters[0].ssrc + + " cname:" + + SDPUtils.localCName + + "\r\n"; + if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) { + sdp += + "a=ssrc:" + + transceiver.sendEncodingParameters[0].rtx.ssrc + + " cname:" + + SDPUtils.localCName + + "\r\n"; + } + return sdp; +}; + +// Gets the direction from the mediaSection or the sessionpart. +SDPUtils.getDirection = function(mediaSection, sessionpart) { + // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv. + var lines = SDPUtils.splitLines(mediaSection); + for (var i = 0; i < lines.length; i++) { + switch (lines[i]) { + case "a=sendrecv": + case "a=sendonly": + case "a=recvonly": + case "a=inactive": + return lines[i].substr(2); + default: + // FIXME: What should happen here? + } + } + if (sessionpart) { + return SDPUtils.getDirection(sessionpart); + } + return "sendrecv"; +}; + +SDPUtils.getKind = function(mediaSection) { + var lines = SDPUtils.splitLines(mediaSection); + var mline = lines[0].split(" "); + return mline[0].substr(2); +}; + +SDPUtils.isRejected = function(mediaSection) { + return mediaSection.split(" ", 2)[1] === "0"; +}; + +SDPUtils.parseMLine = function(mediaSection) { + var lines = SDPUtils.splitLines(mediaSection); + var parts = lines[0].substr(2).split(" "); + return { + kind: parts[0], + port: parseInt(parts[1], 10), + protocol: parts[2], + fmt: parts.slice(3).join(" "), + }; +}; + +SDPUtils.parseOLine = function(mediaSection) { + var line = SDPUtils.matchPrefix(mediaSection, "o=")[0]; + var parts = line.substr(2).split(" "); + return { + username: parts[0], + sessionId: parts[1], + sessionVersion: parseInt(parts[2], 10), + netType: parts[3], + addressType: parts[4], + address: parts[5], + }; +}; + +// a very naive interpretation of a valid SDP. +SDPUtils.isValidSDP = function(blob) { + if (typeof blob !== "string" || blob.length === 0) { + return false; + } + var lines = SDPUtils.splitLines(blob); + for (var i = 0; i < lines.length; i++) { + if (lines[i].length < 2 || lines[i].charAt(1) !== "=") { + return false; + } + // TODO: check the modifier a bit more. + } + return true; +}; + +// Expose public methods. +if (typeof module === "object") { + module.exports = SDPUtils; +} diff --git a/dom/media/webrtc/tests/mochitests/iceTestUtils.js b/dom/media/webrtc/tests/mochitests/iceTestUtils.js new file mode 100644 index 0000000000..d4d1f5c4b4 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/iceTestUtils.js @@ -0,0 +1,302 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// This is mostly so test_peerConnection_gatherWithStun300.html and +// test_peerConnection_gatherWithStun300IPv6 can share this code. I would have +// put the ipv6 test code in the same file, but our ipv6 tester support is +// inconsistent enough that we need to be able to track the ipv6 test +// separately. + +async function findStatsRelayCandidates(pc, protocol) { + const stats = await pc.getStats(); + return [...stats.values()].filter( + v => + v.type == "local-candidate" && + v.candidateType == "relay" && + v.relayProtocol == protocol + ); +} + +// Trickles candidates if pcDst is set, and resolves the candidate list +async function trickleIce(pc, pcDst) { + const candidates = [], + addCandidatePromises = []; + while (true) { + const { candidate } = await new Promise(r => + pc.addEventListener("icecandidate", r, { once: true }) + ); + if (!candidate) { + break; + } + candidates.push(candidate); + if (pcDst) { + addCandidatePromises.push(pcDst.addIceCandidate(candidate)); + } + } + await Promise.all(addCandidatePromises); + return candidates; +} + +async function gather(pc) { + if (pc.signalingState == "stable") { + await pc.setLocalDescription( + await pc.createOffer({ offerToReceiveAudio: true }) + ); + } else if (pc.signalingState == "have-remote-offer") { + await pc.setLocalDescription(); + } + + return trickleIce(pc); +} + +async function gatherWithTimeout(pc, timeout, context) { + const throwOnTimeout = async () => { + await wait(timeout); + throw new Error( + `Gathering did not complete within ${timeout} ms with ${context}` + ); + }; + + return Promise.race([gather(pc), throwOnTimeout()]); +} + +async function iceConnected(pc) { + return new Promise((resolve, reject) => { + pc.addEventListener("iceconnectionstatechange", () => { + if (["connected", "completed"].includes(pc.iceConnectionState)) { + resolve(); + } else if (pc.iceConnectionState == "failed") { + reject(new Error(`ICE failed`)); + } + }); + }); +} + +// Set up trickle, but does not wait for it to complete. Can be used by itself +// in cases where we do not expect any new candidates, but want to still set up +// the signal handling in case new candidates _do_ show up. +async function connectNoTrickleWait(offerer, answerer, timeout, context) { + return connect(offerer, answerer, timeout, context, true); +} + +async function connect( + offerer, + answerer, + timeout, + context, + noTrickleWait = false +) { + const trickle1 = trickleIce(offerer, answerer); + const trickle2 = trickleIce(answerer, offerer); + try { + const offer = await offerer.createOffer({ offerToReceiveAudio: true }); + await offerer.setLocalDescription(offer); + await answerer.setRemoteDescription(offer); + const answer = await answerer.createAnswer(); + await Promise.all([ + offerer.setRemoteDescription(answer), + answerer.setLocalDescription(answer), + ]); + + const throwOnTimeout = async () => { + if (timeout) { + await wait(timeout); + throw new Error( + `ICE did not complete within ${timeout} ms with ${context}` + ); + } + }; + + await Promise.race([ + Promise.all([iceConnected(offerer), iceConnected(answerer)]), + throwOnTimeout(timeout, context), + ]); + } finally { + if (!noTrickleWait) { + // TODO(bug 1751509): For now, we need to let gathering finish before we + // proceed, because there are races in ICE restart wrt gathering state. + await Promise.all([trickle1, trickle2]); + } + } +} + +function isV6HostCandidate(candidate) { + const fields = candidate.candidate.split(" "); + const type = fields[7]; + const ipAddress = fields[4]; + return type == "host" && ipAddress.includes(":"); +} + +async function ipv6Supported() { + const pc = new RTCPeerConnection(); + const candidates = await gatherWithTimeout(pc, 8000); + info(`baseline candidates: ${JSON.stringify(candidates)}`); + pc.close(); + return candidates.some(isV6HostCandidate); +} + +function makeContextString(iceServers) { + const currentRedirectAddress = SpecialPowers.getCharPref( + "media.peerconnection.nat_simulator.redirect_address", + "" + ); + const currentRedirectTargets = SpecialPowers.getCharPref( + "media.peerconnection.nat_simulator.redirect_targets", + "" + ); + return `redirect rule: ${currentRedirectAddress}=>${currentRedirectTargets} iceServers: ${JSON.stringify( + iceServers + )}`; +} + +async function checkSrflx(iceServers) { + const context = makeContextString(iceServers); + info(`checkSrflx ${context}`); + const pc = new RTCPeerConnection({ + iceServers, + bundlePolicy: "max-bundle", // Avoids extra candidates + }); + const candidates = await gatherWithTimeout(pc, 8000, context); + const srflxCandidates = candidates.filter(c => c.candidate.includes("srflx")); + info(`candidates: ${JSON.stringify(srflxCandidates)}`); + // TODO(bug 1339203): Once we support rtcpMuxPolicy, set it to "require" to + // result in a single srflx candidate + is( + srflxCandidates.length, + 2, + `Should have two srflx candidates with ${context}` + ); + pc.close(); +} + +async function checkNoSrflx(iceServers) { + const context = makeContextString(iceServers); + info(`checkNoSrflx ${context}`); + const pc = new RTCPeerConnection({ + iceServers, + bundlePolicy: "max-bundle", // Avoids extra candidates + }); + const candidates = await gatherWithTimeout(pc, 8000, context); + const srflxCandidates = candidates.filter(c => c.candidate.includes("srflx")); + info(`candidates: ${JSON.stringify(srflxCandidates)}`); + is( + srflxCandidates.length, + 0, + `Should have no srflx candidates with ${context}` + ); + pc.close(); +} + +async function checkRelayUdp(iceServers) { + const context = makeContextString(iceServers); + info(`checkRelayUdp ${context}`); + const pc = new RTCPeerConnection({ + iceServers, + bundlePolicy: "max-bundle", // Avoids extra candidates + }); + const candidates = await gatherWithTimeout(pc, 8000, context); + const relayCandidates = candidates.filter(c => c.candidate.includes("relay")); + info(`candidates: ${JSON.stringify(relayCandidates)}`); + // TODO(bug 1339203): Once we support rtcpMuxPolicy, set it to "require" to + // result in a single relay candidate + is( + relayCandidates.length, + 2, + `Should have two relay candidates with ${context}` + ); + // It would be nice if RTCIceCandidate had a field telling us what the + // "related protocol" is (similar to relatedAddress and relatedPort). + // Because there is no such thing, we need to go through the stats API, + // which _does_ have that information. + is( + (await findStatsRelayCandidates(pc, "tcp")).length, + 0, + `No TCP relay candidates should be present with ${context}` + ); + pc.close(); +} + +async function checkRelayTcp(iceServers) { + const context = makeContextString(iceServers); + info(`checkRelayTcp ${context}`); + const pc = new RTCPeerConnection({ + iceServers, + bundlePolicy: "max-bundle", // Avoids extra candidates + }); + const candidates = await gatherWithTimeout(pc, 8000, context); + const relayCandidates = candidates.filter(c => c.candidate.includes("relay")); + info(`candidates: ${JSON.stringify(relayCandidates)}`); + // TODO(bug 1339203): Once we support rtcpMuxPolicy, set it to "require" to + // result in a single relay candidate + is( + relayCandidates.length, + 2, + `Should have two relay candidates with ${context}` + ); + // It would be nice if RTCIceCandidate had a field telling us what the + // "related protocol" is (similar to relatedAddress and relatedPort). + // Because there is no such thing, we need to go through the stats API, + // which _does_ have that information. + is( + (await findStatsRelayCandidates(pc, "udp")).length, + 0, + `No UDP relay candidates should be present with ${context}` + ); + pc.close(); +} + +async function checkRelayUdpTcp(iceServers) { + const context = makeContextString(iceServers); + info(`checkRelayUdpTcp ${context}`); + const pc = new RTCPeerConnection({ + iceServers, + bundlePolicy: "max-bundle", // Avoids extra candidates + }); + const candidates = await gatherWithTimeout(pc, 8000, context); + const relayCandidates = candidates.filter(c => c.candidate.includes("relay")); + info(`candidates: ${JSON.stringify(relayCandidates)}`); + // TODO(bug 1339203): Once we support rtcpMuxPolicy, set it to "require" to + // result in a single relay candidate each for UDP and TCP + is( + relayCandidates.length, + 4, + `Should have two relay candidates for each protocol with ${context}` + ); + // It would be nice if RTCIceCandidate had a field telling us what the + // "related protocol" is (similar to relatedAddress and relatedPort). + // Because there is no such thing, we need to go through the stats API, + // which _does_ have that information. + is( + (await findStatsRelayCandidates(pc, "udp")).length, + 2, + `Two UDP relay candidates should be present with ${context}` + ); + // TODO(bug 1705563): This is 1 because of bug 1705563 + is( + (await findStatsRelayCandidates(pc, "tcp")).length, + 1, + `One TCP relay candidates should be present with ${context}` + ); + pc.close(); +} + +async function checkNoRelay(iceServers) { + const context = makeContextString(iceServers); + info(`checkNoRelay ${context}`); + const pc = new RTCPeerConnection({ + iceServers, + bundlePolicy: "max-bundle", // Avoids extra candidates + }); + const candidates = await gatherWithTimeout(pc, 8000, context); + const relayCandidates = candidates.filter(c => c.candidate.includes("relay")); + info(`candidates: ${JSON.stringify(relayCandidates)}`); + is( + relayCandidates.length, + 0, + `Should have no relay candidates with ${context}` + ); + pc.close(); +} diff --git a/dom/media/webrtc/tests/mochitests/identity/identityPcTest.js b/dom/media/webrtc/tests/mochitests/identity/identityPcTest.js new file mode 100644 index 0000000000..1381873f9d --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/identity/identityPcTest.js @@ -0,0 +1,79 @@ +function identityPcTest(remoteOptions) { + var user = "someone"; + var domain1 = "test1.example.com"; + var domain2 = "test2.example.com"; + var id1 = user + "@" + domain1; + var id2 = user + "@" + domain2; + + test = new PeerConnectionTest({ + config_local: { + peerIdentity: id2, + }, + config_remote: { + peerIdentity: id1, + }, + }); + test.setMediaConstraints( + [ + { + audio: true, + video: true, + peerIdentity: id2, + }, + ], + [ + remoteOptions || { + audio: true, + video: true, + peerIdentity: id1, + }, + ] + ); + test.pcLocal.setIdentityProvider("test1.example.com", { protocol: "idp.js" }); + test.pcRemote.setIdentityProvider("test2.example.com", { + protocol: "idp.js", + }); + test.chain.append([ + function PEER_IDENTITY_IS_SET_CORRECTLY(test) { + // no need to wait to check identity in this case, + // setRemoteDescription should wait for the IdP to complete + function checkIdentity(pc, pfx, idp, name) { + return pc.peerIdentity.then(peerInfo => { + is(peerInfo.idp, idp, pfx + "IdP check"); + is(peerInfo.name, name + "@" + idp, pfx + "identity check"); + }); + } + + return Promise.all([ + checkIdentity( + test.pcLocal._pc, + "local: ", + "test2.example.com", + "someone" + ), + checkIdentity( + test.pcRemote._pc, + "remote: ", + "test1.example.com", + "someone" + ), + ]); + }, + + function REMOTE_STREAMS_ARE_RESTRICTED(test) { + var remoteStream = test.pcLocal._pc.getRemoteStreams()[0]; + for (const track of remoteStream.getTracks()) { + mustThrowWith( + `Freshly received ${track.kind} track with peerIdentity`, + "SecurityError", + () => new MediaRecorder(new MediaStream([track])).start() + ); + } + return Promise.all([ + audioIsSilence(true, remoteStream), + videoIsBlack(true, remoteStream), + ]); + }, + ]); + return test.run(); +} diff --git a/dom/media/webrtc/tests/mochitests/identity/idp-bad.js b/dom/media/webrtc/tests/mochitests/identity/idp-bad.js new file mode 100644 index 0000000000..86e1cb7a34 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/identity/idp-bad.js @@ -0,0 +1 @@ + diff --git a/dom/media/webrtc/tests/mochitests/identity/idp-min.js b/dom/media/webrtc/tests/mochitests/identity/idp-min.js new file mode 100644 index 0000000000..a4b2c55cee --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/identity/idp-min.js @@ -0,0 +1,24 @@ +(function (global) { + "use strict"; + // A minimal implementation of the interface. + // Though this isn't particularly functional. + // This is needed so that we can have a "working" IdP served + // from two different locations in the tree. + global.rtcIdentityProvider.register({ + generateAssertion(payload, origin, usernameHint) { + dump("idp: generateAssertion(" + payload + ")\n"); + return Promise.resolve({ + idp: { domain: "example.com", protocol: "idp.js" }, + assertion: "bogus", + }); + }, + + validateAssertion(assertion, origin) { + dump("idp: validateAssertion(" + assertion + ")\n"); + return Promise.resolve({ + identity: "user@example.com", + contents: "bogus", + }); + }, + }); +})(this); diff --git a/dom/media/webrtc/tests/mochitests/identity/idp-redirect-http-trick.js b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-http-trick.js new file mode 100644 index 0000000000..75390cbf4f --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-http-trick.js @@ -0,0 +1,3 @@ +(function () { + dump("ERROR\n"); +})(); diff --git a/dom/media/webrtc/tests/mochitests/identity/idp-redirect-http-trick.js^headers^ b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-http-trick.js^headers^ new file mode 100644 index 0000000000..b3a2afd90a --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-http-trick.js^headers^ @@ -0,0 +1,2 @@ +HTTP 301 Moved Permanently +Location: http://example.com/.well-known/idp-proxy/idp-redirect-https.js diff --git a/dom/media/webrtc/tests/mochitests/identity/idp-redirect-http.js b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-http.js new file mode 100644 index 0000000000..75390cbf4f --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-http.js @@ -0,0 +1,3 @@ +(function () { + dump("ERROR\n"); +})(); diff --git a/dom/media/webrtc/tests/mochitests/identity/idp-redirect-http.js^headers^ b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-http.js^headers^ new file mode 100644 index 0000000000..d2380984e7 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-http.js^headers^ @@ -0,0 +1,2 @@ +HTTP 301 Moved Permanently +Location: http://example.com/.well-known/idp-proxy/idp.js diff --git a/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-double.js b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-double.js new file mode 100644 index 0000000000..75390cbf4f --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-double.js @@ -0,0 +1,3 @@ +(function () { + dump("ERROR\n"); +})(); diff --git a/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-double.js^headers^ b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-double.js^headers^ new file mode 100644 index 0000000000..3fb8a35ae7 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-double.js^headers^ @@ -0,0 +1,2 @@ +HTTP 301 Moved Permanently +Location: https://example.com/.well-known/idp-proxy/idp-redirect-https.js diff --git a/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-odd-path.js b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-odd-path.js new file mode 100644 index 0000000000..75390cbf4f --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-odd-path.js @@ -0,0 +1,3 @@ +(function () { + dump("ERROR\n"); +})(); diff --git a/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-odd-path.js^headers^ b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-odd-path.js^headers^ new file mode 100644 index 0000000000..6e2931eda9 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-odd-path.js^headers^ @@ -0,0 +1,2 @@ +HTTP 301 Moved Permanently +Location: https://example.com/.well-known/idp-min.js diff --git a/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https.js b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https.js new file mode 100644 index 0000000000..75390cbf4f --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https.js @@ -0,0 +1,3 @@ +(function () { + dump("ERROR\n"); +})(); diff --git a/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https.js^headers^ b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https.js^headers^ new file mode 100644 index 0000000000..77d56ac442 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https.js^headers^ @@ -0,0 +1,2 @@ +HTTP 301 Moved Permanently +Location: https://example.com/.well-known/idp-proxy/idp.js diff --git a/dom/media/webrtc/tests/mochitests/identity/idp.js b/dom/media/webrtc/tests/mochitests/identity/idp.js new file mode 100644 index 0000000000..557740657f --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/identity/idp.js @@ -0,0 +1,119 @@ +(function (global) { + "use strict"; + + // rather than create a million different IdP configurations and litter the + // world with files all containing near-identical code, let's use the hash/URL + // fragment as a way of generating instructions for the IdP + var instructions = global.location.hash.replace("#", "").split(":"); + function is(target) { + return function (instruction) { + return instruction === target; + }; + } + + function IDPJS() { + this.domain = global.location.host; + var path = global.location.pathname; + this.protocol = + path.substring(path.lastIndexOf("/") + 1) + global.location.hash; + this.id = crypto.getRandomValues(new Uint8Array(10)).join("."); + } + + IDPJS.prototype = { + getLogin() { + return fetch( + "https://example.com/.well-known/idp-proxy/idp.sjs?" + this.id + ).then(response => response.status === 200); + }, + checkLogin(result) { + return this.getLogin().then(loggedIn => { + if (loggedIn) { + return result; + } + return Promise.reject({ + name: "IdpLoginError", + loginUrl: + "https://example.com/.well-known/idp-proxy/login.html#" + this.id, + }); + }); + }, + + borkResult(result) { + if (instructions.some(is("throw"))) { + throw new Error("Throwing!"); + } + if (instructions.some(is("fail"))) { + return Promise.reject(new Error("Failing!")); + } + if (instructions.some(is("login"))) { + return this.checkLogin(result); + } + if (instructions.some(is("hang"))) { + return new Promise(r => {}); + } + dump("idp: result=" + JSON.stringify(result) + "\n"); + return Promise.resolve(result); + }, + + _selectUsername(usernameHint) { + dump("_selectUsername: usernameHint(" + usernameHint + ")\n"); + var username = "someone@" + this.domain; + if (usernameHint) { + var at = usernameHint.indexOf("@"); + if (at < 0) { + username = usernameHint + "@" + this.domain; + } else if (usernameHint.substring(at + 1) === this.domain) { + username = usernameHint; + } + } + return username; + }, + + generateAssertion(payload, origin, options) { + dump( + "idp: generateAssertion(" + + payload + + ", " + + origin + + ", " + + JSON.stringify(options) + + ")\n" + ); + var idpDetails = { + domain: this.domain, + protocol: this.protocol, + }; + if (instructions.some(is("bad-assert"))) { + idpDetails = {}; + } + return this.borkResult({ + idp: idpDetails, + assertion: JSON.stringify({ + username: this._selectUsername(options.usernameHint), + contents: payload, + }), + }); + }, + + validateAssertion(assertion, origin) { + dump("idp: validateAssertion(" + assertion + ")\n"); + var assertion = JSON.parse(assertion); + if (instructions.some(is("bad-validate"))) { + assertion.contents = {}; + } + return this.borkResult({ + identity: assertion.username, + contents: assertion.contents, + }); + }, + }; + + if (!instructions.some(is("not_ready"))) { + dump("registering idp.js" + global.location.hash + "\n"); + var idp = new IDPJS(); + global.rtcIdentityProvider.register({ + generateAssertion: idp.generateAssertion.bind(idp), + validateAssertion: idp.validateAssertion.bind(idp), + }); + } +})(this); diff --git a/dom/media/webrtc/tests/mochitests/identity/idp.sjs b/dom/media/webrtc/tests/mochitests/identity/idp.sjs new file mode 100644 index 0000000000..e1a245be78 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/identity/idp.sjs @@ -0,0 +1,18 @@ +function handleRequest(request, response) { + var key = "/.well-known/idp-proxy/" + request.queryString; + dump(getState(key) + "\n"); + if (request.method === "GET") { + if (getState(key)) { + response.setStatusLine(request.httpVersion, 200, "OK"); + } else { + response.setStatusLine(request.httpVersion, 404, "Not Found"); + } + } else if (request.method === "PUT") { + setState(key, "OK"); + response.setStatusLine(request.httpVersion, 200, "OK"); + } else { + response.setStatusLine(request.httpVersion, 406, "Method Not Allowed"); + } + response.setHeader("Content-Type", "text/plain;charset=UTF-8"); + response.write("OK"); +} diff --git a/dom/media/webrtc/tests/mochitests/identity/login.html b/dom/media/webrtc/tests/mochitests/identity/login.html new file mode 100644 index 0000000000..eafba22f2d --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/identity/login.html @@ -0,0 +1,31 @@ + + + + Identity Provider Login + + + +
Logging in...
+ + diff --git a/dom/media/webrtc/tests/mochitests/identity/mochitest.ini b/dom/media/webrtc/tests/mochitests/identity/mochitest.ini new file mode 100644 index 0000000000..7a60cc6c6e --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/identity/mochitest.ini @@ -0,0 +1,47 @@ +[DEFAULT] +subsuite = media +skip-if = (os == 'linux' && !debug) +support-files = + /.well-known/idp-proxy/idp.js + identityPcTest.js + !/dom/media/webrtc/tests/mochitests/blacksilence.js + !/dom/media/webrtc/tests/mochitests/dataChannel.js + !/dom/media/webrtc/tests/mochitests/head.js + !/dom/media/webrtc/tests/mochitests/network.js + !/dom/media/webrtc/tests/mochitests/pc.js + !/dom/media/webrtc/tests/mochitests/sdpUtils.js + !/dom/media/webrtc/tests/mochitests/templates.js + !/dom/media/webrtc/tests/mochitests/turnConfig.js +tags = mtg + +[test_fingerprints.html] +scheme=https +[test_getIdentityAssertion.html] +[test_idpproxy.html] +support-files = + /.well-known/idp-proxy/idp-redirect-http.js + /.well-known/idp-proxy/idp-redirect-http.js^headers^ + /.well-known/idp-proxy/idp-redirect-http-trick.js + /.well-known/idp-proxy/idp-redirect-http-trick.js^headers^ + /.well-known/idp-proxy/idp-redirect-https.js + /.well-known/idp-proxy/idp-redirect-https.js^headers^ + /.well-known/idp-proxy/idp-redirect-https-double.js + /.well-known/idp-proxy/idp-redirect-https-double.js^headers^ + /.well-known/idp-proxy/idp-redirect-https-odd-path.js + /.well-known/idp-proxy/idp-redirect-https-odd-path.js^headers^ + /.well-known/idp-min.js + /.well-known/idp-proxy/idp-bad.js +[test_loginNeeded.html] +support-files = + /.well-known/idp-proxy/login.html + /.well-known/idp-proxy/idp.sjs +[test_peerConnection_asymmetricIsolation.html] +scheme=https +skip-if = os == 'android' +[test_peerConnection_peerIdentity.html] +scheme=https +skip-if = os == 'android' +[test_setIdentityProvider.html] +scheme=https +[test_setIdentityProviderWithErrors.html] +scheme=https diff --git a/dom/media/webrtc/tests/mochitests/identity/test_fingerprints.html b/dom/media/webrtc/tests/mochitests/identity/test_fingerprints.html new file mode 100644 index 0000000000..0a7f0a2033 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/identity/test_fingerprints.html @@ -0,0 +1,91 @@ + + + + + + + + + + diff --git a/dom/media/webrtc/tests/mochitests/identity/test_getIdentityAssertion.html b/dom/media/webrtc/tests/mochitests/identity/test_getIdentityAssertion.html new file mode 100644 index 0000000000..47e1cb1df6 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/identity/test_getIdentityAssertion.html @@ -0,0 +1,101 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/identity/test_idpproxy.html b/dom/media/webrtc/tests/mochitests/identity/test_idpproxy.html new file mode 100644 index 0000000000..065501b8a4 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/identity/test_idpproxy.html @@ -0,0 +1,178 @@ + + + + + + + + + + diff --git a/dom/media/webrtc/tests/mochitests/identity/test_loginNeeded.html b/dom/media/webrtc/tests/mochitests/identity/test_loginNeeded.html new file mode 100644 index 0000000000..550dc20d92 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/identity/test_loginNeeded.html @@ -0,0 +1,72 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/identity/test_peerConnection_asymmetricIsolation.html b/dom/media/webrtc/tests/mochitests/identity/test_peerConnection_asymmetricIsolation.html new file mode 100644 index 0000000000..65a2fc5392 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/identity/test_peerConnection_asymmetricIsolation.html @@ -0,0 +1,31 @@ + + + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/identity/test_peerConnection_peerIdentity.html b/dom/media/webrtc/tests/mochitests/identity/test_peerConnection_peerIdentity.html new file mode 100644 index 0000000000..a8116cc451 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/identity/test_peerConnection_peerIdentity.html @@ -0,0 +1,21 @@ + + + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/identity/test_setIdentityProvider.html b/dom/media/webrtc/tests/mochitests/identity/test_setIdentityProvider.html new file mode 100644 index 0000000000..ac7cba6a5e --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/identity/test_setIdentityProvider.html @@ -0,0 +1,67 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/identity/test_setIdentityProviderWithErrors.html b/dom/media/webrtc/tests/mochitests/identity/test_setIdentityProviderWithErrors.html new file mode 100644 index 0000000000..ce6832d1e6 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/identity/test_setIdentityProviderWithErrors.html @@ -0,0 +1,57 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/mediaStreamPlayback.js b/dom/media/webrtc/tests/mochitests/mediaStreamPlayback.js new file mode 100644 index 0000000000..44c1c78ea0 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/mediaStreamPlayback.js @@ -0,0 +1,241 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const ENDED_TIMEOUT_LENGTH = 30000; + +/* The time we wait depends primarily on the canplaythrough event firing + * Note: this needs to be at least 30s because the + * B2G emulator in VMs is really slow. */ +const VERIFYPLAYING_TIMEOUT_LENGTH = 60000; + +/** + * This class manages playback of a HTMLMediaElement with a MediaStream. + * When constructed by a caller, an object instance is created with + * a media element and a media stream object. + * + * @param {HTMLMediaElement} mediaElement the media element for playback + * @param {MediaStream} mediaStream the media stream used in + * the mediaElement for playback + */ +function MediaStreamPlayback(mediaElement, mediaStream) { + this.mediaElement = mediaElement; + this.mediaStream = mediaStream; +} + +MediaStreamPlayback.prototype = { + /** + * Starts media element with a media stream, runs it until a canplaythrough + * and timeupdate event fires, and calls stop() on all its tracks. + * + * @param {Boolean} isResume specifies if this media element is being resumed + * from a previous run + */ + playMedia(isResume) { + this.startMedia(isResume); + return this.verifyPlaying() + .then(() => this.stopTracksForStreamInMediaPlayback()) + .then(() => this.detachFromMediaElement()); + }, + + /** + * Stops the local media stream's tracks while it's currently in playback in + * a media element. + * + * Precondition: The media stream and element should both be actively + * being played. All the stream's tracks must be local. + */ + stopTracksForStreamInMediaPlayback() { + var elem = this.mediaElement; + return Promise.all([ + haveEvent( + elem, + "ended", + wait(ENDED_TIMEOUT_LENGTH, new Error("Timeout")) + ), + ...this.mediaStream + .getTracks() + .map(t => (t.stop(), haveNoEvent(t, "ended"))), + ]); + }, + + /** + * Starts media with a media stream, runs it until a canplaythrough and + * timeupdate event fires, and detaches from the element without stopping media. + * + * @param {Boolean} isResume specifies if this media element is being resumed + * from a previous run + */ + playMediaWithoutStoppingTracks(isResume) { + this.startMedia(isResume); + return this.verifyPlaying().then(() => this.detachFromMediaElement()); + }, + + /** + * Starts the media with the associated stream. + * + * @param {Boolean} isResume specifies if the media element playback + * is being resumed from a previous run + */ + startMedia(isResume) { + // If we're playing media element for the first time, check that time is zero. + if (!isResume) { + is( + this.mediaElement.currentTime, + 0, + "Before starting the media element, currentTime = 0" + ); + } + this.canPlayThroughFired = listenUntil( + this.mediaElement, + "canplaythrough", + () => true + ); + + // Hooks up the media stream to the media element and starts playing it + this.mediaElement.srcObject = this.mediaStream; + this.mediaElement.play(); + }, + + /** + * Verifies that media is playing. + */ + verifyPlaying() { + var lastElementTime = this.mediaElement.currentTime; + + var mediaTimeProgressed = listenUntil( + this.mediaElement, + "timeupdate", + () => this.mediaElement.currentTime > lastElementTime + ); + + return timeout( + Promise.all([this.canPlayThroughFired, mediaTimeProgressed]), + VERIFYPLAYING_TIMEOUT_LENGTH, + "verifyPlaying timed out" + ).then(() => { + is(this.mediaElement.paused, false, "Media element should be playing"); + is( + this.mediaElement.duration, + Number.POSITIVE_INFINITY, + "Duration should be infinity" + ); + + // When the media element is playing with a real-time stream, we + // constantly switch between having data to play vs. queuing up data, + // so we can only check that the ready state is one of those two values + ok( + this.mediaElement.readyState === HTMLMediaElement.HAVE_ENOUGH_DATA || + this.mediaElement.readyState === HTMLMediaElement.HAVE_CURRENT_DATA, + "Ready state shall be HAVE_ENOUGH_DATA or HAVE_CURRENT_DATA" + ); + + is(this.mediaElement.seekable.length, 0, "Seekable length shall be zero"); + is(this.mediaElement.buffered.length, 0, "Buffered length shall be zero"); + + is( + this.mediaElement.seeking, + false, + "MediaElement is not seekable with MediaStream" + ); + ok( + isNaN(this.mediaElement.startOffsetTime), + "Start offset time shall not be a number" + ); + is( + this.mediaElement.defaultPlaybackRate, + 1, + "DefaultPlaybackRate should be 1" + ); + is(this.mediaElement.playbackRate, 1, "PlaybackRate should be 1"); + is(this.mediaElement.preload, "none", 'Preload should be "none"'); + is(this.mediaElement.src, "", "No src should be defined"); + is( + this.mediaElement.currentSrc, + "", + "Current src should still be an empty string" + ); + }); + }, + + /** + * Detaches from the element without stopping the media. + * + * Precondition: The media stream and element should both be actively + * being played. + */ + detachFromMediaElement() { + this.mediaElement.pause(); + this.mediaElement.srcObject = null; + }, +}; + +// haxx to prevent SimpleTest from failing at window.onload +function addLoadEvent() {} + +/* import-globals-from /testing/mochitest/tests/SimpleTest/SimpleTest.js */ +/* import-globals-from head.js */ +const scriptsReady = Promise.all( + ["/tests/SimpleTest/SimpleTest.js", "head.js"].map(script => { + const el = document.createElement("script"); + el.src = script; + document.head.appendChild(el); + return new Promise(r => (el.onload = r)); + }) +); + +function createHTML(options) { + return scriptsReady.then(() => realCreateHTML(options)); +} + +async function runTest(testFunction) { + await Promise.all([ + scriptsReady, + SpecialPowers.pushPrefEnv({ + set: [["media.navigator.permission.fake", true]], + }), + ]); + await runTestWhenReady(async (...args) => { + await testFunction(...args); + await noGum(); + }); +} + +// noGum - Helper to detect whether active guM tracks still exist. +// +// Note it relies on the permissions system to detect active tracks, so it won't +// catch getUserMedia use while media.navigator.permission.disabled is true +// (which is common in automation), UNLESS we set +// media.navigator.permission.fake to true also, like runTest() does above. +async function noGum() { + if (!navigator.mediaDevices) { + // No mediaDevices, then gUM cannot have been called either. + return; + } + const mediaManagerService = Cc[ + "@mozilla.org/mediaManagerService;1" + ].getService(Ci.nsIMediaManagerService); + + const hasCamera = {}; + const hasMicrophone = {}; + mediaManagerService.mediaCaptureWindowState( + window, + hasCamera, + hasMicrophone, + {}, + {}, + {}, + {}, + false + ); + is( + hasCamera.value, + mediaManagerService.STATE_NOCAPTURE, + "Test must leave no active camera gUM tracks behind." + ); + is( + hasMicrophone.value, + mediaManagerService.STATE_NOCAPTURE, + "Test must leave no active microphone gUM tracks behind." + ); +} diff --git a/dom/media/webrtc/tests/mochitests/mochitest.ini b/dom/media/webrtc/tests/mochitests/mochitest.ini new file mode 100644 index 0000000000..d1a9800984 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/mochitest.ini @@ -0,0 +1,65 @@ +[DEFAULT] +tags = mtg webrtc +subsuite = media +scheme = https +support-files = + head.js + dataChannel.js + mediaStreamPlayback.js + network.js + nonTrickleIce.js + pc.js + stats.js + templates.js + test_enumerateDevices_iframe.html + test_enumerateDevices_iframe_pre_gum.html + test_getUserMedia_permission_iframe.html + NetworkPreparationChromeScript.js + blacksilence.js + turnConfig.js + sdpUtils.js + addTurnsSelfsignedCert.js + parser_rtp.js + peerconnection_audio_forced_sample_rate.js + iceTestUtils.js + simulcast.js + helpers_from_wpt/sdp.js + !/dom/canvas/test/captureStream_common.js + !/dom/canvas/test/webgl-mochitest/webgl-util.js + !/dom/media/test/manifest.js + !/dom/media/test/seek.webm + !/dom/media/test/gizmo.mp4 + !/docshell/test/navigation/blank.html +prefs = + focusmanager.testmode=true # emulate focus + privacy.partition.network_state=false + network.proxy.allow_hijacking_localhost=true + media.devices.enumerate.legacy.enabled=false + +[test_1488832.html] +skip-if = + os == 'linux' # Bug 1714410 +[test_1717318.html] +[test_a_noOp.html] +scheme=http +[test_enumerateDevices.html] +[test_enumerateDevices_getUserMediaFake.html] +[test_enumerateDevices_legacy.html] +[test_enumerateDevices_navigation.html] +skip-if = true # Disabled because it is a racy test and causes timeouts, see bug 1650932 +[test_fingerprinting_resistance.html] +skip-if = + os == "linux" && asan # Bug 1646309 - low frequency intermittent +[test_forceSampleRate.html] +scheme=http +[test_groupId.html] +[test_multi_mics.html] +skip-if = os == 'android' +[test_ondevicechange.html] +run-sequentially = sets prefs that may disrupt other tests +[test_setSinkId.html] +skip-if = + os != 'linux' # the only platform with real devices +[test_setSinkId_default_addTrack.html] +[test_setSinkId_preMutedElement.html] +[test_unfocused_pref.html] diff --git a/dom/media/webrtc/tests/mochitests/mochitest_datachannel.ini b/dom/media/webrtc/tests/mochitests/mochitest_datachannel.ini new file mode 100644 index 0000000000..881421af0e --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/mochitest_datachannel.ini @@ -0,0 +1,52 @@ +[DEFAULT] +tags = mtg webrtc +subsuite = media +scheme = https +support-files = + head.js + dataChannel.js + mediaStreamPlayback.js + network.js + nonTrickleIce.js + pc.js + stats.js + templates.js + test_enumerateDevices_iframe.html + test_getUserMedia_permission_iframe.html + NetworkPreparationChromeScript.js + blacksilence.js + turnConfig.js + sdpUtils.js + addTurnsSelfsignedCert.js + parser_rtp.js + peerconnection_audio_forced_sample_rate.js + iceTestUtils.js + simulcast.js + helpers_from_wpt/sdp.js + !/dom/canvas/test/captureStream_common.js + !/dom/canvas/test/webgl-mochitest/webgl-util.js + !/dom/media/test/manifest.js + !/dom/media/test/seek.webm + !/dom/media/test/gizmo.mp4 + !/docshell/test/navigation/blank.html +prefs = + focusmanager.testmode=true # emulate focus + privacy.partition.network_state=false + network.proxy.allow_hijacking_localhost=true + +[test_dataChannel_basicAudio.html] +[test_dataChannel_basicAudioVideo.html] +[test_dataChannel_basicAudioVideoCombined.html] +[test_dataChannel_basicAudioVideoNoBundle.html] +[test_dataChannel_basicDataOnly.html] +[test_dataChannel_basicVideo.html] +[test_dataChannel_bug1013809.html] +[test_dataChannel_dataOnlyBufferedAmountLow.html] +scheme=http +[test_dataChannel_dtlsVersions.html] +[test_dataChannel_hostnameObfuscation.html] +scheme=http +[test_dataChannel_noOffer.html] +scheme=http +[test_dataChannel_stats.html] +scheme=http diff --git a/dom/media/webrtc/tests/mochitests/mochitest_getusermedia.ini b/dom/media/webrtc/tests/mochitests/mochitest_getusermedia.ini new file mode 100644 index 0000000000..02c8272b5b --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/mochitest_getusermedia.ini @@ -0,0 +1,105 @@ +[DEFAULT] +tags = mtg webrtc +subsuite = media +scheme = https +support-files = + head.js + dataChannel.js + mediaStreamPlayback.js + network.js + nonTrickleIce.js + pc.js + stats.js + templates.js + test_enumerateDevices_iframe.html + test_getUserMedia_permission_iframe.html + NetworkPreparationChromeScript.js + blacksilence.js + turnConfig.js + sdpUtils.js + addTurnsSelfsignedCert.js + parser_rtp.js + peerconnection_audio_forced_sample_rate.js + iceTestUtils.js + simulcast.js + helpers_from_wpt/sdp.js + !/dom/canvas/test/captureStream_common.js + !/dom/canvas/test/webgl-mochitest/webgl-util.js + !/dom/media/test/manifest.js + !/dom/media/test/seek.webm + !/dom/media/test/gizmo.mp4 + !/docshell/test/navigation/blank.html +prefs = + focusmanager.testmode=true # emulate focus + privacy.partition.network_state=false + network.proxy.allow_hijacking_localhost=true + media.devices.enumerate.legacy.enabled=false + +[test_defaultAudioConstraints.html] +skip-if = os == 'mac' + os == 'win' + toolkit == 'android' # Bug 1404995, no loopback devices on some platforms +[test_getUserMedia_GC_MediaStream.html] +[test_getUserMedia_active_autoplay.html] +[test_getUserMedia_addTrackRemoveTrack.html] +[test_getUserMedia_addtrack_removetrack_events.html] +[test_getUserMedia_audioCapture.html] +skip-if = toolkit == 'android' + (os == "win" && processor == "aarch64") # android(Bug 1189784, timeouts on 4.3 emulator), android(Bug 1264333), aarch64 due to 1538359 +[test_getUserMedia_audioConstraints.html] +skip-if = os == 'mac' + os == 'win' + toolkit == 'android' # Bug 1404995, no loopback devices on some platforms +[test_getUserMedia_audioConstraints_concurrentIframes.html] +skip-if = os == 'mac' + os == 'win' + toolkit == 'android' + (os == 'linux' && debug) # Bug 1404995, no loopback devices on some platforms # Bug 1481101 + os == "linux" && !debug && !fission # bug 1645930, lower frequency intermittent +[test_getUserMedia_audioConstraints_concurrentStreams.html] +skip-if = os == 'mac' + os == 'win' + toolkit == 'android' # Bug 1404995, no loopback devices on some platforms +[test_getUserMedia_basicAudio.html] +[test_getUserMedia_basicAudio_loopback.html] +skip-if = os == 'mac' + os == 'win' + toolkit == 'android' # Bug 1404995, no loopback devices on some platforms +[test_getUserMedia_basicScreenshare.html] +skip-if = + toolkit == 'android' # no screenshare on android + apple_silicon # bug 1707742 +[test_getUserMedia_basicTabshare.html] +skip-if = + toolkit == 'android' # no windowshare on android +[test_getUserMedia_basicVideo.html] +[test_getUserMedia_basicVideoAudio.html] +[test_getUserMedia_basicVideo_playAfterLoadedmetadata.html] +[test_getUserMedia_basicWindowshare.html] +skip-if = toolkit == 'android' # no windowshare on android +[test_getUserMedia_bug1223696.html] +[test_getUserMedia_callbacks.html] +[test_getUserMedia_constraints.html] +[test_getUserMedia_cubebDisabled.html] +[test_getUserMedia_cubebDisabledFakeStreams.html] +[test_getUserMedia_getTrackById.html] +[test_getUserMedia_gumWithinGum.html] +[test_getUserMedia_loadedmetadata.html] +[test_getUserMedia_mediaElementCapture_audio.html] +[test_getUserMedia_mediaElementCapture_tracks.html] +[test_getUserMedia_mediaElementCapture_video.html] +[test_getUserMedia_mediaStreamClone.html] +[test_getUserMedia_mediaStreamConstructors.html] +[test_getUserMedia_mediaStreamTrackClone.html] +[test_getUserMedia_nonDefaultRate.html] +[test_getUserMedia_peerIdentity.html] +[test_getUserMedia_permission.html] +[test_getUserMedia_playAudioTwice.html] +[test_getUserMedia_playVideoAudioTwice.html] +[test_getUserMedia_playVideoTwice.html] +[test_getUserMedia_scarySources.html] +skip-if = toolkit == 'android' # no screenshare or windowshare on android +[test_getUserMedia_spinEventLoop.html] +[test_getUserMedia_trackCloneCleanup.html] +[test_getUserMedia_trackEnded.html] + diff --git a/dom/media/webrtc/tests/mochitests/mochitest_peerconnection.ini b/dom/media/webrtc/tests/mochitests/mochitest_peerconnection.ini new file mode 100644 index 0000000000..37fb551435 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/mochitest_peerconnection.ini @@ -0,0 +1,311 @@ +[DEFAULT] +tags = mtg webrtc +subsuite = media +scheme = https +support-files = + head.js + dataChannel.js + mediaStreamPlayback.js + network.js + nonTrickleIce.js + pc.js + stats.js + templates.js + test_enumerateDevices_iframe.html + test_getUserMedia_permission_iframe.html + NetworkPreparationChromeScript.js + blacksilence.js + turnConfig.js + sdpUtils.js + addTurnsSelfsignedCert.js + parser_rtp.js + peerconnection_audio_forced_sample_rate.js + iceTestUtils.js + simulcast.js + helpers_from_wpt/sdp.js + !/dom/canvas/test/captureStream_common.js + !/dom/canvas/test/webgl-mochitest/webgl-util.js + !/dom/media/test/manifest.js + !/dom/media/test/seek.webm + !/dom/media/test/gizmo.mp4 + !/docshell/test/navigation/blank.html +prefs = + focusmanager.testmode=true # emulate focus + privacy.partition.network_state=false + network.proxy.allow_hijacking_localhost=true + media.devices.enumerate.legacy.enabled=false + +[test_peerConnection_addAudioTrackToExistingVideoStream.html] +[test_peerConnection_addDataChannel.html] +[test_peerConnection_addDataChannelNoBundle.html] +[test_peerConnection_addSecondAudioStream.html] +[test_peerConnection_addSecondAudioStreamNoBundle.html] +[test_peerConnection_addSecondVideoStream.html] +[test_peerConnection_addSecondVideoStreamNoBundle.html] +[test_peerConnection_addtrack_removetrack_events.html] +[test_peerConnection_answererAddSecondAudioStream.html] +[test_peerConnection_audioChannels.html] +[test_peerConnection_audioCodecs.html] +[test_peerConnection_audioContributingSources.html] +[test_peerConnection_audioRenegotiationInactiveAnswer.html] +[test_peerConnection_audioSynchronizationSources.html] +[test_peerConnection_audioSynchronizationSourcesUnidirectional.html] +[test_peerConnection_basicAudio.html] +[test_peerConnection_basicAudioDynamicPtMissingRtpmap.html] +[test_peerConnection_basicAudioNATRelay.html] +skip-if = + toolkit == 'android' # websockets don't work on android (bug 1266217) +scheme=http +[test_peerConnection_basicAudioNATRelayTCP.html] +skip-if = + toolkit == 'android' # websockets don't work on android (bug 1266217) + (os == 'win' && os_version == '6.1') # WinError 10048 +scheme=http +[test_peerConnection_basicAudioNATRelayTCPWithStun300.html] +skip-if = + toolkit == 'android' # websockets don't work on android (bug 1266217) + (os == 'win' && os_version == '6.1') # WinError 10048 +scheme=http +[test_peerConnection_basicAudioNATRelayTLS.html] +skip-if = + toolkit == 'android' # websockets don't work on android (bug 1266217) + (os == 'win' && os_version == '6.1') # WinError 10048 +scheme=http +[test_peerConnection_basicAudioNATRelayWithStun300.html] +skip-if = + toolkit == 'android' # websockets don't work on android (bug 1266217) + (os == 'win' && os_version == '6.1') # WinError 10048 +scheme=http +[test_peerConnection_basicAudioNATSrflx.html] +skip-if = + toolkit == 'android' # websockets don't work on android (bug 1266217) + (os == 'win' && os_version == '6.1') # WinError 10048 +scheme=http +[test_peerConnection_basicAudioNoisyUDPBlock.html] +skip-if = + toolkit == 'android' # websockets don't work on android (bug 1266217) + (os == 'win' && os_version == '6.1') # WinError 10048 +scheme=http +[test_peerConnection_basicAudioPcmaPcmuOnly.html] +[test_peerConnection_basicAudioRelayPolicy.html] +skip-if = + toolkit == 'android' # websockets don't work on android (bug 1266217) + (os == 'win' && os_version == '6.1') # WinError 10048 +scheme=http +[test_peerConnection_basicAudioRequireEOC.html] +[test_peerConnection_basicAudioVerifyRtpHeaderExtensions.html] +[test_peerConnection_basicAudioVideo.html] +[test_peerConnection_basicAudioVideoCombined.html] +[test_peerConnection_basicAudioVideoNoBundle.html] +[test_peerConnection_basicAudioVideoNoBundleNoRtcpMux.html] +[test_peerConnection_basicAudioVideoNoRtcpMux.html] +[test_peerConnection_basicAudioVideoTransceivers.html] +[test_peerConnection_basicAudioVideoVerifyExtmap.html] +[test_peerConnection_basicAudioVideoVerifyExtmapSendonly.html] +[test_peerConnection_basicAudioVideoVerifyTooLongMidFails.html] +[test_peerConnection_basicAudio_forced_higher_rate.html] +[test_peerConnection_basicAudio_forced_lower_rate.html] +[test_peerConnection_basicH264Video.html] +skip-if = + toolkit == 'android' && is_emulator # Bug 1355786, No h264 support on android emulator +[test_peerConnection_basicScreenshare.html] +skip-if = toolkit == 'android' # no screenshare on android +[test_peerConnection_basicVideo.html] +[test_peerConnection_basicVideoVerifyRtpHeaderExtensions.html] +[test_peerConnection_basicWindowshare.html] +skip-if = toolkit == 'android' # no screenshare on android +[test_peerConnection_bug1013809.html] +[test_peerConnection_bug1042791.html] +skip-if = (toolkit == 'android' && is_emulator) # Bug 1355786, No h264 support on android emulator +[test_peerConnection_bug1227781.html] +scheme=http +[test_peerConnection_bug1512281.html] +fail-if = 1 +[test_peerConnection_bug1773067.html] +[test_peerConnection_bug822674.html] +scheme=http +[test_peerConnection_bug825703.html] +scheme=http +[test_peerConnection_bug827843.html] +[test_peerConnection_bug834153.html] +scheme=http +[test_peerConnection_callbacks.html] +[test_peerConnection_captureStream_canvas_2d.html] +scheme=http +[test_peerConnection_captureStream_canvas_2d_noSSRC.html] +scheme=http +[test_peerConnection_captureStream_canvas_webgl.html] +scheme=http +[test_peerConnection_capturedVideo.html] +tags=capturestream +skip-if = toolkit == 'android' # android(Bug 1189784, timeouts on 4.3 emulator), Bug 1264340 +[test_peerConnection_certificates.html] +[test_peerConnection_checkPacketDumpHook.html] +[test_peerConnection_close.html] +scheme=http +[test_peerConnection_closeDuringIce.html] +[test_peerConnection_codecNegotiationFailure.html] +[test_peerConnection_constructedStream.html] +[test_peerConnection_disabledVideoPreNegotiation.html] +[test_peerConnection_encodingsNegotiation.html] +[test_peerConnection_errorCallbacks.html] +scheme=http +[test_peerConnection_extmapRenegotiation.html] +[test_peerConnection_forwarding_basicAudioVideoCombined.html] +skip-if = toolkit == 'android' # Bug 1189784 +[test_peerConnection_gatherWithSetConfiguration.html] +skip-if = + toolkit == 'android' # websockets don't work on android (bug 1266217) + (os == 'win' && os_version == '6.1') # WinError 10048 +scheme=http +[test_peerConnection_gatherWithStun300.html] +skip-if = + toolkit == 'android' # websockets don't work on android (bug 1266217) + (os == 'win' && os_version == '6.1') # WinError 10048 +scheme=http +[test_peerConnection_gatherWithStun300IPv6.html] +skip-if = + toolkit == 'android' # websockets don't work on android (bug 1266217) + os == 'mac' # no ipv6 support on OS X testers (bug 1710706) + os == 'win' # no ipv6 support on windows testers (bug 1710706) +scheme=http +[test_peerConnection_glean.html] +[test_peerConnection_iceFailure.html] +skip-if = true # (Bug 1180388 for win, mac and linux), android(Bug 1189784), Bug 1180388 +scheme=http +[test_peerConnection_insertDTMF.html] +[test_peerConnection_localReofferRollback.html] +[test_peerConnection_localRollback.html] +[test_peerConnection_maxFsConstraint.html] +[test_peerConnection_multiple_captureStream_canvas_2d.html] +scheme=http +[test_peerConnection_noTrickleAnswer.html] +[test_peerConnection_noTrickleOffer.html] +[test_peerConnection_noTrickleOfferAnswer.html] +[test_peerConnection_nonDefaultRate.html] +[test_peerConnection_offerRequiresReceiveAudio.html] +[test_peerConnection_offerRequiresReceiveVideo.html] +[test_peerConnection_offerRequiresReceiveVideoAudio.html] +[test_peerConnection_portRestrictions.html] +[test_peerConnection_promiseSendOnly.html] +[test_peerConnection_recordReceiveTrack.html] +[test_peerConnection_relayOnly.html] +disabled=bug 1612063 # test is racy +[test_peerConnection_remoteReofferRollback.html] +[test_peerConnection_remoteRollback.html] +[test_peerConnection_removeAudioTrack.html] +[test_peerConnection_removeThenAddAudioTrack.html] +[test_peerConnection_removeThenAddAudioTrackNoBundle.html] +[test_peerConnection_removeThenAddVideoTrack.html] +[test_peerConnection_removeThenAddVideoTrackNoBundle.html] +[test_peerConnection_removeVideoTrack.html] +[test_peerConnection_renderAfterRenegotiation.html] +scheme=http +[test_peerConnection_replaceNullTrackThenRenegotiateAudio.html] +[test_peerConnection_replaceNullTrackThenRenegotiateVideo.html] +[test_peerConnection_replaceTrack.html] +[test_peerConnection_replaceTrack_camera.html] +skip-if = toolkit == 'android' # Bug 1614460 +[test_peerConnection_replaceTrack_disabled.html] +skip-if = + toolkit == 'android' # Bug 1614460 +[test_peerConnection_replaceTrack_microphone.html] +[test_peerConnection_replaceVideoThenRenegotiate.html] +[test_peerConnection_restartIce.html] +[test_peerConnection_restartIceBadAnswer.html] +[test_peerConnection_restartIceLocalAndRemoteRollback.html] +[test_peerConnection_restartIceLocalAndRemoteRollbackNoSubsequentRestart.html] +[test_peerConnection_restartIceLocalRollback.html] +[test_peerConnection_restartIceLocalRollbackNoSubsequentRestart.html] +[test_peerConnection_restartIceNoBundle.html] +[test_peerConnection_restartIceNoBundleNoRtcpMux.html] +[test_peerConnection_restartIceNoRtcpMux.html] +[test_peerConnection_restrictBandwidthTargetBitrate.html] +[test_peerConnection_restrictBandwidthWithTias.html] +[test_peerConnection_rtcp_rsize.html] +[test_peerConnection_scaleResolution.html] +[test_peerConnection_scaleResolution_oldSetParameters.html] +[test_peerConnection_sender_and_receiver_stats.html] +[test_peerConnection_setLocalAnswerInHaveLocalOffer.html] +[test_peerConnection_setLocalAnswerInStable.html] +[test_peerConnection_setLocalOfferInHaveRemoteOffer.html] +[test_peerConnection_setParameters.html] +[test_peerConnection_setParameters_maxFramerate.html] +[test_peerConnection_setParameters_maxFramerate_oldSetParameters.html] +[test_peerConnection_setParameters_oldSetParameters.html] +[test_peerConnection_setParameters_scaleResolutionDownBy.html] +skip-if = (os == 'win' && processor == 'aarch64') # aarch64 due to bug 1537567 +[test_peerConnection_setParameters_scaleResolutionDownBy_oldSetParameters.html] +skip-if = (os == 'win' && processor == 'aarch64') # aarch64 due to bug 1537567 +[test_peerConnection_setRemoteAnswerInHaveRemoteOffer.html] +[test_peerConnection_setRemoteAnswerInStable.html] +[test_peerConnection_setRemoteOfferInHaveLocalOffer.html] +[test_peerConnection_simulcastAnswer.html] +skip-if = toolkit == 'android' # no simulcast support on android +[test_peerConnection_simulcastAnswer_lowResFirst.html] +skip-if = toolkit == 'android' # no simulcast support on android +[test_peerConnection_simulcastAnswer_lowResFirst_oldSetParameters.html] +skip-if = toolkit == 'android' # no simulcast support on android +[test_peerConnection_simulcastAnswer_oldSetParameters.html] +skip-if = toolkit == 'android' # no simulcast support on android +[test_peerConnection_simulcastOddResolution.html] +skip-if = toolkit == 'android' # no simulcast support on android +[test_peerConnection_simulcastOddResolution_oldSetParameters.html] +skip-if = toolkit == 'android' # no simulcast support on android +[test_peerConnection_simulcastOffer.html] +skip-if = toolkit == 'android' # no simulcast support on android +[test_peerConnection_simulcastOffer_lowResFirst.html] +skip-if = toolkit == 'android' # no simulcast support on android +[test_peerConnection_simulcastOffer_lowResFirst_oldSetParameters.html] +skip-if = toolkit == 'android' # no simulcast support on android +[test_peerConnection_simulcastOffer_oldSetParameters.html] +skip-if = toolkit == 'android' # no simulcast support on android +[test_peerConnection_stats.html] +[test_peerConnection_stats_jitter.html] +skip-if = tsan # Bug 1672590, TSan is just too slow to pass this test +[test_peerConnection_stats_oneway.html] +[test_peerConnection_stats_relayProtocol.html] +skip-if = + toolkit == 'android' # android(Bug 1189784, timeouts on 4.3 emulator, Bug 1373858, Bug 1521117) + socketprocess_e10s + (os == 'win' && os_version == '6.1') # WinError 10048 +scheme=http +[test_peerConnection_stereoFmtpPref.html] +[test_peerConnection_syncSetDescription.html] +[test_peerConnection_telephoneEventFirst.html] +[test_peerConnection_threeUnbundledConnections.html] +[test_peerConnection_throwInCallbacks.html] +[test_peerConnection_toJSON.html] +scheme=http +[test_peerConnection_trackDisabling.html] +skip-if = toolkit == 'android' # Bug 1614460 +[test_peerConnection_trackDisabling_clones.html] +[test_peerConnection_trackless_sender_stats.html] +[test_peerConnection_twoAudioStreams.html] +[test_peerConnection_twoAudioTracksInOneStream.html] +[test_peerConnection_twoAudioVideoStreams.html] +[test_peerConnection_twoAudioVideoStreamsCombined.html] +skip-if = (toolkit == 'android') || (os == 'linux' && asan) # android(Bug 1189784), Bug 1480942 for Linux asan +[test_peerConnection_twoAudioVideoStreamsCombinedNoBundle.html] +skip-if = + (toolkit == 'android') # Bug 1189784 + (os == 'linux' && asan) # Bug 1480942 + (os == 'win' && processor == 'aarch64') # Bug 1777081 +[test_peerConnection_twoVideoStreams.html] +[test_peerConnection_twoVideoTracksInOneStream.html] +[test_peerConnection_verifyAudioAfterRenegotiation.html] +skip-if = + os == "android" && processor == "x86_64" && !debug # Bug 1783287 +[test_peerConnection_verifyDescriptions.html] +[test_peerConnection_verifyVideoAfterRenegotiation.html] +[test_peerConnection_videoCodecs.html] +skip-if = + toolkit == 'android' # android(Bug 1614460) + win10_2004 && !debug # Bug 1777082 +[test_peerConnection_videoRenegotiationInactiveAnswer.html] +[test_peerConnection_webAudio.html] +tags = webaudio webrtc +scheme=http +[test_selftest.html] +# Bug 1227781: Crash with bogus TURN server. +scheme=http diff --git a/dom/media/webrtc/tests/mochitests/network.js b/dom/media/webrtc/tests/mochitests/network.js new file mode 100644 index 0000000000..223721b111 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/network.js @@ -0,0 +1,16 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * A stub function for preparing the network if needed + * + */ +async function startNetworkAndTest() {} + +/** + * A stub function to shutdown the network if needed + */ +async function networkTestFinished() {} diff --git a/dom/media/webrtc/tests/mochitests/nonTrickleIce.js b/dom/media/webrtc/tests/mochitests/nonTrickleIce.js new file mode 100644 index 0000000000..9361944791 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/nonTrickleIce.js @@ -0,0 +1,97 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function removeTrickleOption(desc) { + var sdp = desc.sdp.replace(/\r\na=ice-options:trickle\r\n/, "\r\n"); + return new RTCSessionDescription({ type: desc.type, sdp }); +} + +function makeOffererNonTrickle(chain) { + chain.replace("PC_LOCAL_SETUP_ICE_HANDLER", [ + function PC_LOCAL_SETUP_NOTRICKLE_ICE_HANDLER(test) { + // We need to install this callback before calling setLocalDescription + // otherwise we might miss callbacks + test.pcLocal.setupIceCandidateHandler(test, () => {}); + // We ignore ICE candidates because we want the full offer + }, + ]); + chain.replace("PC_REMOTE_GET_OFFER", [ + function PC_REMOTE_GET_FULL_OFFER(test) { + return test.pcLocal.endOfTrickleIce.then(() => { + test._local_offer = removeTrickleOption(test.pcLocal.localDescription); + test._offer_constraints = test.pcLocal.constraints; + test._offer_options = test.pcLocal.offerOptions; + }); + }, + ]); + chain.insertAfter("PC_REMOTE_SANE_REMOTE_SDP", [ + function PC_REMOTE_REQUIRE_REMOTE_SDP_CANDIDATES(test) { + info( + "test.pcLocal.localDescription.sdp: " + + JSON.stringify(test.pcLocal.localDescription.sdp) + ); + info("test._local_offer.sdp" + JSON.stringify(test._local_offer.sdp)); + is( + test.pcRemote._pc.canTrickleIceCandidates, + false, + "Remote thinks that trickle isn't supported" + ); + ok(!test.localRequiresTrickleIce, "Local does NOT require trickle"); + ok( + test._local_offer.sdp.includes("a=candidate"), + "offer has ICE candidates" + ); + ok( + test._local_offer.sdp.includes("a=end-of-candidates"), + "offer has end-of-candidates" + ); + }, + ]); + chain.remove("PC_REMOTE_CHECK_CAN_TRICKLE_SYNC"); +} + +function makeAnswererNonTrickle(chain) { + chain.replace("PC_REMOTE_SETUP_ICE_HANDLER", [ + function PC_REMOTE_SETUP_NOTRICKLE_ICE_HANDLER(test) { + // We need to install this callback before calling setLocalDescription + // otherwise we might miss callbacks + test.pcRemote.setupIceCandidateHandler(test, () => {}); + // We ignore ICE candidates because we want the full offer + }, + ]); + chain.replace("PC_LOCAL_GET_ANSWER", [ + function PC_LOCAL_GET_FULL_ANSWER(test) { + return test.pcRemote.endOfTrickleIce.then(() => { + test._remote_answer = removeTrickleOption( + test.pcRemote.localDescription + ); + test._answer_constraints = test.pcRemote.constraints; + }); + }, + ]); + chain.insertAfter("PC_LOCAL_SANE_REMOTE_SDP", [ + function PC_LOCAL_REQUIRE_REMOTE_SDP_CANDIDATES(test) { + info( + "test.pcRemote.localDescription.sdp: " + + JSON.stringify(test.pcRemote.localDescription.sdp) + ); + info("test._remote_answer.sdp" + JSON.stringify(test._remote_answer.sdp)); + is( + test.pcLocal._pc.canTrickleIceCandidates, + false, + "Local thinks that trickle isn't supported" + ); + ok(!test.remoteRequiresTrickleIce, "Remote does NOT require trickle"); + ok( + test._remote_answer.sdp.includes("a=candidate"), + "answer has ICE candidates" + ); + ok( + test._remote_answer.sdp.includes("a=end-of-candidates"), + "answer has end-of-candidates" + ); + }, + ]); + chain.remove("PC_LOCAL_CHECK_CAN_TRICKLE_SYNC"); +} diff --git a/dom/media/webrtc/tests/mochitests/parser_rtp.js b/dom/media/webrtc/tests/mochitests/parser_rtp.js new file mode 100644 index 0000000000..2275c1f787 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/parser_rtp.js @@ -0,0 +1,131 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* + * Parses an RTP packet + * @param buffer an ArrayBuffer that contains the packet + * @return { type: "rtp", header: {...}, payload: a DataView } + */ +var ParseRtpPacket = buffer => { + // DataView.getFooInt returns big endian numbers by default + let view = new DataView(buffer); + + // Standard Header Fields + // https://tools.ietf.org/html/rfc3550#section-5.1 + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // |V=2|P|X| CC |M| PT | sequence number | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | timestamp | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | synchronization source (SSRC) identifier | + // +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + // | contributing source (CSRC) identifiers | + // | .... | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + let header = {}; + let offset = 0; + // Note that incrementing the offset happens as close to reading the data as + // possible. This simplifies ensuring that the number of read bytes and the + // offset increment match. Data may be manipulated between when the offset is + // incremented and before the next read. + let byte = view.getUint8(offset); + offset++; + // Version 2 Bit + header.version = (0xc0 & byte) >> 6; + // Padding 1 Bit + header.padding = (0x30 & byte) >> 5; + // Extension 1 Bit + header.extensionsPresent = (0x10 & byte) >> 4 == 1; + // CSRC count 4 Bit + header.csrcCount = 0xf & byte; + + byte = view.getUint8(offset); + offset++; + // Marker 1 Bit + header.marker = (0x80 & byte) >> 7; + // Payload Type 7 Bit + header.payloadType = 0x7f & byte; + // Sequence Number 16 Bit + header.sequenceNumber = view.getUint16(offset); + offset += 2; + // Timestamp 32 Bit + header.timestamp = view.getUint32(offset); + offset += 4; + // SSRC 32 Bit + header.ssrc = view.getUint32(offset); + offset += 4; + + // CSRC 32 Bit + header.csrcs = []; + for (let c = 0; c < header.csrcCount; c++) { + header.csrcs.push(view.getUint32(offset)); + offset += 4; + } + + // Extensions + header.extensions = []; + header.extensionPaddingBytes = 0; + header.extensionsTotalLength = 0; + if (header.extensionsPresent) { + // https://tools.ietf.org/html/rfc3550#section-5.3.1 + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | defined by profile | length | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | header extension | + // | .... | + let addExtension = (id, len) => + header.extensions.push({ + id, + data: new DataView(buffer, offset, len), + }); + let extensionId = view.getUint16(offset); + offset += 2; + // len is in 32 bit units, not bytes + header.extensionsTotalLength = view.getUint16(offset) * 4; + offset += 2; + // Check for https://tools.ietf.org/html/rfc5285 + if (extensionId != 0xbede) { + // No rfc5285 + addExtension(extensionId, header.extensionsTotalLength); + offset += header.extensionsTotalLength; + } else { + let expectedEnd = offset + header.extensionsTotalLength; + while (offset < expectedEnd) { + // We only support "one-byte" extension headers ATM + // https://tools.ietf.org/html/rfc5285#section-4.2 + // 0 + // 0 1 2 3 4 5 6 7 + // +-+-+-+-+-+-+-+-+ + // | ID | len | + // +-+-+-+-+-+-+-+-+ + byte = view.getUint8(offset); + offset++; + // Check for padding which can occur between extensions or at the end + if (byte == 0) { + header.extensionPaddingBytes++; + continue; + } + let id = (byte & 0xf0) >> 4; + // Check for the FORBIDDEN id (15), dun dun dun + if (id == 15) { + // Ignore bytes until until the end of extensions + offset = expectedEnd; + break; + } + // the length of the extention is len + 1 + let len = (byte & 0x0f) + 1; + addExtension(id, len); + offset += len; + } + } + } + return { type: "rtp", header, payload: new DataView(buffer, offset) }; +}; diff --git a/dom/media/webrtc/tests/mochitests/pc.js b/dom/media/webrtc/tests/mochitests/pc.js new file mode 100644 index 0000000000..36a923fbed --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/pc.js @@ -0,0 +1,2495 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const LOOPBACK_ADDR = "127.0.0."; + +const iceStateTransitions = { + new: ["checking", "closed"], //Note: 'failed' might need to added here + // even though it is not in the standard + checking: ["new", "connected", "failed", "closed"], //Note: do we need to + // allow 'completed' in + // here as well? + connected: ["new", "completed", "disconnected", "closed"], + completed: ["new", "disconnected", "closed"], + disconnected: ["new", "connected", "completed", "failed", "closed"], + failed: ["new", "disconnected", "closed"], + closed: [], +}; + +const signalingStateTransitions = { + stable: ["have-local-offer", "have-remote-offer", "closed"], + "have-local-offer": [ + "have-remote-pranswer", + "stable", + "closed", + "have-local-offer", + ], + "have-remote-pranswer": ["stable", "closed", "have-remote-pranswer"], + "have-remote-offer": [ + "have-local-pranswer", + "stable", + "closed", + "have-remote-offer", + ], + "have-local-pranswer": ["stable", "closed", "have-local-pranswer"], + closed: [], +}; + +var makeDefaultCommands = () => { + return [].concat( + commandsPeerConnectionInitial, + commandsGetUserMedia, + commandsPeerConnectionOfferAnswer + ); +}; + +/** + * This class handles tests for peer connections. + * + * @constructor + * @param {object} [options={}] + * Optional options for the peer connection test + * @param {object} [options.commands=commandsPeerConnection] + * Commands to run for the test + * @param {bool} [options.is_local=true] + * true if this test should run the tests for the "local" side. + * @param {bool} [options.is_remote=true] + * true if this test should run the tests for the "remote" side. + * @param {object} [options.config_local=undefined] + * Configuration for the local peer connection instance + * @param {object} [options.config_remote=undefined] + * Configuration for the remote peer connection instance. If not defined + * the configuration from the local instance will be used + */ +function PeerConnectionTest(options) { + // If no options are specified make it an empty object + options = options || {}; + options.commands = options.commands || makeDefaultCommands(); + options.is_local = "is_local" in options ? options.is_local : true; + options.is_remote = "is_remote" in options ? options.is_remote : true; + + options.h264 = "h264" in options ? options.h264 : false; + options.bundle = "bundle" in options ? options.bundle : true; + options.rtcpmux = "rtcpmux" in options ? options.rtcpmux : true; + options.opus = "opus" in options ? options.opus : true; + options.ssrc = "ssrc" in options ? options.ssrc : true; + + options.config_local = options.config_local || {}; + options.config_remote = options.config_remote || {}; + + if (!options.bundle) { + // Make sure neither end tries to use bundle-only! + options.config_local.bundlePolicy = "max-compat"; + options.config_remote.bundlePolicy = "max-compat"; + } + + if (iceServersArray.length) { + if (!options.turn_disabled_local && !options.config_local.iceServers) { + options.config_local.iceServers = iceServersArray; + } + if (!options.turn_disabled_remote && !options.config_remote.iceServers) { + options.config_remote.iceServers = iceServersArray; + } + } else if (typeof turnServers !== "undefined") { + if (!options.turn_disabled_local && turnServers.local) { + if (!options.config_local.hasOwnProperty("iceServers")) { + options.config_local.iceServers = turnServers.local.iceServers; + } + } + if (!options.turn_disabled_remote && turnServers.remote) { + if (!options.config_remote.hasOwnProperty("iceServers")) { + options.config_remote.iceServers = turnServers.remote.iceServers; + } + } + } + + if (options.is_local) { + this.pcLocal = new PeerConnectionWrapper("pcLocal", options.config_local); + } else { + this.pcLocal = null; + } + + if (options.is_remote) { + this.pcRemote = new PeerConnectionWrapper( + "pcRemote", + options.config_remote || options.config_local + ); + } else { + this.pcRemote = null; + } + + // Create command chain instance and assign default commands + this.chain = new CommandChain(this, options.commands); + + this.testOptions = options; +} + +/** TODO: consider removing this dependency on timeouts */ +function timerGuard(p, time, message) { + return Promise.race([ + p, + wait(time).then(() => { + throw new Error("timeout after " + time / 1000 + "s: " + message); + }), + ]); +} + +/** + * Closes the peer connection if it is active + */ +PeerConnectionTest.prototype.closePC = function () { + info("Closing peer connections"); + + var closeIt = pc => { + if (!pc || pc.signalingState === "closed") { + return Promise.resolve(); + } + + var promise = Promise.all([ + Promise.all( + pc._pc + .getReceivers() + .filter(receiver => receiver.track.readyState == "live") + .map(receiver => { + info( + "Waiting for track " + + receiver.track.id + + " (" + + receiver.track.kind + + ") to end." + ); + return haveEvent(receiver.track, "ended", wait(50000)).then( + event => { + is( + event.target, + receiver.track, + "Event target should be the correct track" + ); + info(pc + " ended fired for track " + receiver.track.id); + }, + e => + e + ? Promise.reject(e) + : ok( + false, + "ended never fired for track " + receiver.track.id + ) + ); + }) + ), + ]); + pc.close(); + return promise; + }; + + return timerGuard( + Promise.all([closeIt(this.pcLocal), closeIt(this.pcRemote)]), + 60000, + "failed to close peer connection" + ); +}; + +/** + * Close the open data channels, followed by the underlying peer connection + */ +PeerConnectionTest.prototype.close = function () { + var allChannels = (this.pcLocal || this.pcRemote).dataChannels; + return timerGuard( + Promise.all(allChannels.map((channel, i) => this.closeDataChannels(i))), + 120000, + "failed to close data channels" + ).then(() => this.closePC()); +}; + +/** + * Close the specified data channels + * + * @param {Number} index + * Index of the data channels to close on both sides + */ +PeerConnectionTest.prototype.closeDataChannels = function (index) { + info("closeDataChannels called with index: " + index); + var localChannel = null; + if (this.pcLocal) { + localChannel = this.pcLocal.dataChannels[index]; + } + var remoteChannel = null; + if (this.pcRemote) { + remoteChannel = this.pcRemote.dataChannels[index]; + } + + // We need to setup all the close listeners before calling close + var setupClosePromise = channel => { + if (!channel) { + return Promise.resolve(); + } + return new Promise(resolve => { + channel.onclose = () => { + is( + channel.readyState, + "closed", + name + " channel " + index + " closed" + ); + resolve(); + }; + }); + }; + + // make sure to setup close listeners before triggering any actions + var allClosed = Promise.all([ + setupClosePromise(localChannel), + setupClosePromise(remoteChannel), + ]); + var complete = timerGuard( + allClosed, + 120000, + "failed to close data channel pair" + ); + + // triggering close on one side should suffice + if (remoteChannel) { + remoteChannel.close(); + } else if (localChannel) { + localChannel.close(); + } + + return complete; +}; + +/** + * Send data (message or blob) to the other peer + * + * @param {String|Blob} data + * Data to send to the other peer. For Blobs the MIME type will be lost. + * @param {Object} [options={ }] + * Options to specify the data channels to be used + * @param {DataChannelWrapper} [options.sourceChannel=pcLocal.dataChannels[length - 1]] + * Data channel to use for sending the message + * @param {DataChannelWrapper} [options.targetChannel=pcRemote.dataChannels[length - 1]] + * Data channel to use for receiving the message + */ +PeerConnectionTest.prototype.send = async function (data, options) { + options = options || {}; + const source = + options.sourceChannel || + this.pcLocal.dataChannels[this.pcLocal.dataChannels.length - 1]; + const target = + options.targetChannel || + this.pcRemote.dataChannels[this.pcRemote.dataChannels.length - 1]; + source.bufferedAmountLowThreshold = options.bufferedAmountLowThreshold || 0; + + const getSizeInBytes = d => { + if (d instanceof Blob) { + return d.size; + } else if (d instanceof ArrayBuffer) { + return d.byteLength; + } else if (d instanceof String || typeof d === "string") { + return new TextEncoder().encode(d).length; + } else { + ok(false); + } + }; + + const expectedSizeInBytes = getSizeInBytes(data); + const bufferedAmount = source.bufferedAmount; + + source.send(data); + is( + source.bufferedAmount, + expectedSizeInBytes + bufferedAmount, + `Buffered amount should be ${expectedSizeInBytes}` + ); + + await new Promise(resolve => (source.onbufferedamountlow = resolve)); + + return new Promise(resolve => { + // Register event handler for the target channel + target.onmessage = e => { + is( + getSizeInBytes(e.data), + expectedSizeInBytes, + `Expected to receive the same number of bytes as we sent (${expectedSizeInBytes})` + ); + resolve({ channel: target, data: e.data }); + }; + }); +}; + +/** + * Create a data channel + * + * @param {Dict} options + * Options for the data channel (see nsIPeerConnection) + */ +PeerConnectionTest.prototype.createDataChannel = function (options) { + var remotePromise; + if (!options.negotiated) { + this.pcRemote.expectDataChannel("pcRemote expected data channel"); + remotePromise = this.pcRemote.nextDataChannel; + } + + // Create the datachannel + var localChannel = this.pcLocal.createDataChannel(options); + var localPromise = localChannel.opened; + + if (options.negotiated) { + remotePromise = localPromise.then(localChannel => { + // externally negotiated - we need to open from both ends + options.id = options.id || channel.id; // allow for no id on options + var remoteChannel = this.pcRemote.createDataChannel(options); + return remoteChannel.opened; + }); + } + + // pcRemote.observedNegotiationNeeded might be undefined if + // !options.negotiated, which means we just wait on pcLocal + return Promise.all([ + this.pcLocal.observedNegotiationNeeded, + this.pcRemote.observedNegotiationNeeded, + ]).then(() => { + return Promise.all([localPromise, remotePromise]).then(result => { + return { local: result[0], remote: result[1] }; + }); + }); +}; + +/** + * Creates an answer for the specified peer connection instance + * and automatically handles the failure case. + * + * @param {PeerConnectionWrapper} peer + * The peer connection wrapper to run the command on + */ +PeerConnectionTest.prototype.createAnswer = function (peer) { + return peer.createAnswer().then(answer => { + // make a copy so this does not get updated with ICE candidates + this.originalAnswer = new RTCSessionDescription( + JSON.parse(JSON.stringify(answer)) + ); + return answer; + }); +}; + +/** + * Creates an offer for the specified peer connection instance + * and automatically handles the failure case. + * + * @param {PeerConnectionWrapper} peer + * The peer connection wrapper to run the command on + */ +PeerConnectionTest.prototype.createOffer = function (peer) { + return peer.createOffer().then(offer => { + // make a copy so this does not get updated with ICE candidates + this.originalOffer = new RTCSessionDescription( + JSON.parse(JSON.stringify(offer)) + ); + return offer; + }); +}; + +/** + * Sets the local description for the specified peer connection instance + * and automatically handles the failure case. + * + * @param {PeerConnectionWrapper} peer + The peer connection wrapper to run the command on + * @param {RTCSessionDescriptionInit} desc + * Session description for the local description request + */ +PeerConnectionTest.prototype.setLocalDescription = function ( + peer, + desc, + stateExpected +) { + var eventFired = new Promise(resolve => { + peer.onsignalingstatechange = e => { + info(peer + ": 'signalingstatechange' event received"); + var state = e.target.signalingState; + if (stateExpected === state) { + peer.setLocalDescStableEventDate = new Date(); + resolve(); + } else { + ok( + false, + "This event has either already fired or there has been a " + + "mismatch between event received " + + state + + " and event expected " + + stateExpected + ); + } + }; + }); + + var stateChanged = peer.setLocalDescription(desc).then(() => { + peer.setLocalDescDate = new Date(); + }); + + peer.endOfTrickleSdp = peer.endOfTrickleIce + .then(() => { + return peer._pc.localDescription; + }) + .catch(e => ok(false, "Sending EOC message failed: " + e)); + + return Promise.all([eventFired, stateChanged]); +}; + +/** + * Sets the media constraints for both peer connection instances. + * + * @param {object} constraintsLocal + * Media constrains for the local peer connection instance + * @param constraintsRemote + */ +PeerConnectionTest.prototype.setMediaConstraints = function ( + constraintsLocal, + constraintsRemote +) { + if (this.pcLocal) { + this.pcLocal.constraints = constraintsLocal; + } + if (this.pcRemote) { + this.pcRemote.constraints = constraintsRemote; + } +}; + +/** + * Sets the media options used on a createOffer call in the test. + * + * @param {object} options the media constraints to use on createOffer + */ +PeerConnectionTest.prototype.setOfferOptions = function (options) { + if (this.pcLocal) { + this.pcLocal.offerOptions = options; + } +}; + +/** + * Sets the remote description for the specified peer connection instance + * and automatically handles the failure case. + * + * @param {PeerConnectionWrapper} peer + The peer connection wrapper to run the command on + * @param {RTCSessionDescriptionInit} desc + * Session description for the remote description request + */ +PeerConnectionTest.prototype.setRemoteDescription = function ( + peer, + desc, + stateExpected +) { + var eventFired = new Promise(resolve => { + peer.onsignalingstatechange = e => { + info(peer + ": 'signalingstatechange' event received"); + var state = e.target.signalingState; + if (stateExpected === state) { + peer.setRemoteDescStableEventDate = new Date(); + resolve(); + } else { + ok( + false, + "This event has either already fired or there has been a " + + "mismatch between event received " + + state + + " and event expected " + + stateExpected + ); + } + }; + }); + + var stateChanged = peer.setRemoteDescription(desc).then(() => { + peer.setRemoteDescDate = new Date(); + peer.checkMediaTracks(); + }); + + return Promise.all([eventFired, stateChanged]); +}; + +/** + * Adds and removes steps to/from the execution chain based on the configured + * testOptions. + */ +PeerConnectionTest.prototype.updateChainSteps = function () { + if (this.testOptions.h264) { + this.chain.insertAfterEach("PC_LOCAL_CREATE_OFFER", [ + PC_LOCAL_REMOVE_ALL_BUT_H264_FROM_OFFER, + ]); + } + if (!this.testOptions.bundle) { + this.chain.insertAfterEach("PC_LOCAL_CREATE_OFFER", [ + PC_LOCAL_REMOVE_BUNDLE_FROM_OFFER, + ]); + } + if (!this.testOptions.rtcpmux) { + this.chain.insertAfterEach("PC_LOCAL_CREATE_OFFER", [ + PC_LOCAL_REMOVE_RTCPMUX_FROM_OFFER, + ]); + } + if (!this.testOptions.ssrc) { + this.chain.insertAfterEach("PC_LOCAL_CREATE_OFFER", [ + PC_LOCAL_REMOVE_SSRC_FROM_OFFER, + ]); + this.chain.insertAfterEach("PC_REMOTE_CREATE_ANSWER", [ + PC_REMOTE_REMOVE_SSRC_FROM_ANSWER, + ]); + } + if (!this.testOptions.is_local) { + this.chain.filterOut(/^PC_LOCAL/); + } + if (!this.testOptions.is_remote) { + this.chain.filterOut(/^PC_REMOTE/); + } +}; + +/** + * Start running the tests as assigned to the command chain. + */ +PeerConnectionTest.prototype.run = async function () { + /* We have to modify the chain here to allow tests which modify the default + * test chain instantiating a PeerConnectionTest() */ + this.updateChainSteps(); + try { + await this.chain.execute(); + await this.close(); + } catch (e) { + const stack = + typeof e.stack === "string" + ? ` ${e.stack.split("\n").join(" ... ")}` + : ""; + ok(false, `Error in test execution: ${e} (${stack})`); + } +}; + +/** + * Routes ice candidates from one PCW to the other PCW + */ +PeerConnectionTest.prototype.iceCandidateHandler = function ( + caller, + candidate +) { + info("Received: " + JSON.stringify(candidate) + " from " + caller); + + var target = null; + if (caller.includes("pcLocal")) { + if (this.pcRemote) { + target = this.pcRemote; + } + } else if (caller.includes("pcRemote")) { + if (this.pcLocal) { + target = this.pcLocal; + } + } else { + ok(false, "received event from unknown caller: " + caller); + return; + } + + if (target) { + target.storeOrAddIceCandidate(candidate); + } else { + info("sending ice candidate to signaling server"); + send_message({ type: "ice_candidate", ice_candidate: candidate }); + } +}; + +/** + * Installs a polling function for the socket.io client to read + * all messages from the chat room into a message queue. + */ +PeerConnectionTest.prototype.setupSignalingClient = function () { + this.signalingMessageQueue = []; + this.signalingCallbacks = {}; + this.signalingLoopRun = true; + + var queueMessage = message => { + info("Received signaling message: " + JSON.stringify(message)); + var fired = false; + Object.keys(this.signalingCallbacks).forEach(name => { + if (name === message.type) { + info("Invoking callback for message type: " + name); + this.signalingCallbacks[name](message); + fired = true; + } + }); + if (!fired) { + this.signalingMessageQueue.push(message); + info( + "signalingMessageQueue.length: " + this.signalingMessageQueue.length + ); + } + if (this.signalingLoopRun) { + wait_for_message().then(queueMessage); + } else { + info("Exiting signaling message event loop"); + } + }; + wait_for_message().then(queueMessage); +}; + +/** + * Sets a flag to stop reading further messages from the chat room. + */ +PeerConnectionTest.prototype.signalingMessagesFinished = function () { + this.signalingLoopRun = false; +}; + +/** + * Register a callback function to deliver messages from the chat room + * directly instead of storing them in the message queue. + * + * @param {string} messageType + * For which message types should the callback get invoked. + * + * @param {function} onMessage + * The function which gets invoked if a message of the messageType + * has been received from the chat room. + */ +PeerConnectionTest.prototype.registerSignalingCallback = function ( + messageType, + onMessage +) { + this.signalingCallbacks[messageType] = onMessage; +}; + +/** + * Searches the message queue for the first message of a given type + * and invokes the given callback function, or registers the callback + * function for future messages if the queue contains no such message. + * + * @param {string} messageType + * The type of message to search and register for. + */ +PeerConnectionTest.prototype.getSignalingMessage = function (messageType) { + var i = this.signalingMessageQueue.findIndex(m => m.type === messageType); + if (i >= 0) { + info( + "invoking callback on message " + + i + + " from message queue, for message type:" + + messageType + ); + return Promise.resolve(this.signalingMessageQueue.splice(i, 1)[0]); + } + return new Promise(resolve => + this.registerSignalingCallback(messageType, resolve) + ); +}; + +/** + * This class acts as a wrapper around a DataChannel instance. + * + * @param dataChannel + * @param peerConnectionWrapper + * @constructor + */ +function DataChannelWrapper(dataChannel, peerConnectionWrapper) { + this._channel = dataChannel; + this._pc = peerConnectionWrapper; + + info("Creating " + this); + + /** + * Setup appropriate callbacks + */ + createOneShotEventWrapper(this, this._channel, "close"); + createOneShotEventWrapper(this, this._channel, "error"); + createOneShotEventWrapper(this, this._channel, "message"); + createOneShotEventWrapper(this, this._channel, "bufferedamountlow"); + + this.opened = timerGuard( + new Promise(resolve => { + this._channel.onopen = () => { + this._channel.onopen = unexpectedEvent(this, "onopen"); + is(this.readyState, "open", "data channel is 'open' after 'onopen'"); + resolve(this); + }; + }), + 180000, + "channel didn't open in time" + ); +} + +DataChannelWrapper.prototype = { + /** + * Returns the binary type of the channel + * + * @returns {String} The binary type + */ + get binaryType() { + return this._channel.binaryType; + }, + + /** + * Sets the binary type of the channel + * + * @param {String} type + * The new binary type of the channel + */ + set binaryType(type) { + this._channel.binaryType = type; + }, + + /** + * Returns the label of the underlying data channel + * + * @returns {String} The label + */ + get label() { + return this._channel.label; + }, + + /** + * Returns the protocol of the underlying data channel + * + * @returns {String} The protocol + */ + get protocol() { + return this._channel.protocol; + }, + + /** + * Returns the id of the underlying data channel + * + * @returns {number} The stream id + */ + get id() { + return this._channel.id; + }, + + /** + * Returns the reliable state of the underlying data channel + * + * @returns {bool} The stream's reliable state + */ + get reliable() { + return this._channel.reliable; + }, + + /** + * Returns the ordered attribute of the data channel + * + * @returns {bool} The ordered attribute + */ + get ordered() { + return this._channel.ordered; + }, + + /** + * Returns the maxPacketLifeTime attribute of the data channel + * + * @returns {number} The maxPacketLifeTime attribute + */ + get maxPacketLifeTime() { + return this._channel.maxPacketLifeTime; + }, + + /** + * Returns the maxRetransmits attribute of the data channel + * + * @returns {number} The maxRetransmits attribute + */ + get maxRetransmits() { + return this._channel.maxRetransmits; + }, + + /** + * Returns the readyState bit of the data channel + * + * @returns {String} The state of the channel + */ + get readyState() { + return this._channel.readyState; + }, + + get bufferedAmount() { + return this._channel.bufferedAmount; + }, + + /** + * Sets the bufferlowthreshold of the channel + * + * @param {integer} amoutn + * The new threshold for the chanel + */ + set bufferedAmountLowThreshold(amount) { + this._channel.bufferedAmountLowThreshold = amount; + }, + + /** + * Close the data channel + */ + close() { + info(this + ": Closing channel"); + this._channel.close(); + }, + + /** + * Send data through the data channel + * + * @param {String|Object} data + * Data which has to be sent through the data channel + */ + send(data) { + info(this + ": Sending data '" + data + "'"); + this._channel.send(data); + }, + + /** + * Returns the string representation of the class + * + * @returns {String} The string representation + */ + toString() { + return ( + "DataChannelWrapper (" + this._pc.label + "_" + this._channel.label + ")" + ); + }, +}; + +/** + * This class acts as a wrapper around a PeerConnection instance. + * + * @constructor + * @param {string} label + * Description for the peer connection instance + * @param {object} configuration + * Configuration for the peer connection instance + */ +function PeerConnectionWrapper(label, configuration) { + this.configuration = configuration; + if (configuration && configuration.label_suffix) { + label = label + "_" + configuration.label_suffix; + } + this.label = label; + + this.constraints = []; + this.offerOptions = {}; + + this.dataChannels = []; + + this._local_ice_candidates = []; + this._remote_ice_candidates = []; + this.localRequiresTrickleIce = false; + this.remoteRequiresTrickleIce = false; + this.localMediaElements = []; + this.remoteMediaElements = []; + this.audioElementsOnly = false; + + this._sendStreams = []; + + this.expectedLocalTrackInfo = []; + this.remoteStreamsByTrackId = new Map(); + + this.disableRtpCountChecking = false; + + this.iceConnectedResolve; + this.iceConnectedReject; + this.iceConnected = new Promise((resolve, reject) => { + this.iceConnectedResolve = resolve; + this.iceConnectedReject = reject; + }); + this.iceCheckingRestartExpected = false; + this.iceCheckingIceRollbackExpected = false; + + info("Creating " + this); + this._pc = new RTCPeerConnection(this.configuration); + + /** + * Setup callback handlers + */ + // This allows test to register their own callbacks for ICE connection state changes + this.ice_connection_callbacks = {}; + + this._pc.oniceconnectionstatechange = e => { + isnot( + typeof this._pc.iceConnectionState, + "undefined", + "iceConnectionState should not be undefined" + ); + var iceState = this._pc.iceConnectionState; + info( + this + ": oniceconnectionstatechange fired, new state is: " + iceState + ); + Object.keys(this.ice_connection_callbacks).forEach(name => { + this.ice_connection_callbacks[name](); + }); + if (iceState === "connected") { + this.iceConnectedResolve(); + } else if (iceState === "failed") { + this.iceConnectedReject(new Error("ICE failed")); + } + }; + + this._pc.onicegatheringstatechange = e => { + isnot( + typeof this._pc.iceGatheringState, + "undefined", + "iceGetheringState should not be undefined" + ); + var gatheringState = this._pc.iceGatheringState; + info( + this + + ": onicegatheringstatechange fired, new state is: " + + gatheringState + ); + }; + + createOneShotEventWrapper(this, this._pc, "datachannel"); + this._pc.addEventListener("datachannel", e => { + var wrapper = new DataChannelWrapper(e.channel, this); + this.dataChannels.push(wrapper); + }); + + createOneShotEventWrapper(this, this._pc, "signalingstatechange"); + createOneShotEventWrapper(this, this._pc, "negotiationneeded"); +} + +PeerConnectionWrapper.prototype = { + /** + * Returns the senders + * + * @returns {sequence} the senders + */ + getSenders() { + return this._pc.getSenders(); + }, + + /** + * Returns the getters + * + * @returns {sequence} the receivers + */ + getReceivers() { + return this._pc.getReceivers(); + }, + + /** + * Returns the local description. + * + * @returns {object} The local description + */ + get localDescription() { + return this._pc.localDescription; + }, + + /** + * Returns the remote description. + * + * @returns {object} The remote description + */ + get remoteDescription() { + return this._pc.remoteDescription; + }, + + /** + * Returns the signaling state. + * + * @returns {object} The local description + */ + get signalingState() { + return this._pc.signalingState; + }, + /** + * Returns the ICE connection state. + * + * @returns {object} The local description + */ + get iceConnectionState() { + return this._pc.iceConnectionState; + }, + + setIdentityProvider(provider, options) { + this._pc.setIdentityProvider(provider, options); + }, + + elementPrefix: direction => { + return [this.label, direction].join("_"); + }, + + getMediaElementForTrack(track, direction) { + var prefix = this.elementPrefix(direction); + return getMediaElementForTrack(track, prefix); + }, + + createMediaElementForTrack(track, direction) { + var prefix = this.elementPrefix(direction); + return createMediaElementForTrack(track, prefix); + }, + + ensureMediaElement(track, direction) { + var prefix = this.elementPrefix(direction); + var element = this.getMediaElementForTrack(track, direction); + if (!element) { + element = this.createMediaElementForTrack(track, direction); + if (direction == "local") { + this.localMediaElements.push(element); + } else if (direction == "remote") { + this.remoteMediaElements.push(element); + } + } + + // We do this regardless, because sometimes we end up with a new stream with + // an old id (ie; the rollback tests cause the same stream to be added + // twice) + element.srcObject = new MediaStream([track]); + element.play(); + }, + + addSendStream(stream) { + // The PeerConnection will not necessarily know about this stream + // automatically, because replaceTrack is not told about any streams the + // new track might be associated with. Only content really knows. + this._sendStreams.push(stream); + }, + + getStreamForSendTrack(track) { + return this._sendStreams.find(str => str.getTrackById(track.id)); + }, + + getStreamForRecvTrack(track) { + return this._pc.getRemoteStreams().find(s => !!s.getTrackById(track.id)); + }, + + /** + * Attaches a local track to this RTCPeerConnection using + * RTCPeerConnection.addTrack(). + * + * Also creates a media element playing a MediaStream containing all + * tracks that have been added to `stream` using `attachLocalTrack()`. + * + * @param {MediaStreamTrack} track + * MediaStreamTrack to handle + * @param {MediaStream} stream + * MediaStream to use as container for `track` on remote side + */ + attachLocalTrack(track, stream) { + info("Got a local " + track.kind + " track"); + + this.expectNegotiationNeeded(); + var sender = this._pc.addTrack(track, stream); + is(sender.track, track, "addTrack returns sender"); + is( + this._pc.getSenders().pop(), + sender, + "Sender should be the last element in getSenders()" + ); + + ok(track.id, "track has id"); + ok(track.kind, "track has kind"); + ok(stream.id, "stream has id"); + this.expectedLocalTrackInfo.push({ track, sender, streamId: stream.id }); + this.addSendStream(stream); + + // This will create one media element per track, which might not be how + // we set up things with the RTCPeerConnection. It's the only way + // we can ensure all sent tracks are flowing however. + this.ensureMediaElement(track, "local"); + + return this.observedNegotiationNeeded; + }, + + /** + * Callback when we get local media. Also an appropriate HTML media element + * will be created and added to the content node. + * + * @param {MediaStream} stream + * Media stream to handle + */ + attachLocalStream(stream, useAddTransceiver) { + info("Got local media stream: (" + stream.id + ")"); + + this.expectNegotiationNeeded(); + if (useAddTransceiver) { + info("Using addTransceiver (on PC)."); + stream.getTracks().forEach(track => { + var transceiver = this._pc.addTransceiver(track, { streams: [stream] }); + is(transceiver.sender.track, track, "addTransceiver returns sender"); + }); + } + // In order to test both the addStream and addTrack APIs, we do half one + // way, half the other, at random. + else if (Math.random() < 0.5) { + info("Using addStream."); + this._pc.addStream(stream); + ok( + this._pc + .getSenders() + .find(sender => sender.track == stream.getTracks()[0]), + "addStream returns sender" + ); + } else { + info("Using addTrack (on PC)."); + stream.getTracks().forEach(track => { + var sender = this._pc.addTrack(track, stream); + is(sender.track, track, "addTrack returns sender"); + }); + } + + this.addSendStream(stream); + + stream.getTracks().forEach(track => { + ok(track.id, "track has id"); + ok(track.kind, "track has kind"); + const sender = this._pc.getSenders().find(s => s.track == track); + ok(sender, "track has a sender"); + this.expectedLocalTrackInfo.push({ track, sender, streamId: stream.id }); + this.ensureMediaElement(track, "local"); + }); + + return this.observedNegotiationNeeded; + }, + + removeSender(index) { + var sender = this._pc.getSenders()[index]; + this.expectedLocalTrackInfo = this.expectedLocalTrackInfo.filter( + i => i.sender != sender + ); + this.expectNegotiationNeeded(); + this._pc.removeTrack(sender); + return this.observedNegotiationNeeded; + }, + + senderReplaceTrack(sender, withTrack, stream) { + const info = this.expectedLocalTrackInfo.find(i => i.sender == sender); + if (!info) { + return; // replaceTrack on a null track, probably + } + info.track = withTrack; + this.addSendStream(stream); + this.ensureMediaElement(withTrack, "local"); + return sender.replaceTrack(withTrack); + }, + + async getUserMedia(constraints) { + SpecialPowers.wrap(document).notifyUserGestureActivation(); + var stream = await getUserMedia(constraints); + if (constraints.audio) { + stream.getAudioTracks().forEach(track => { + info( + this + + " gUM local stream " + + stream.id + + " with audio track " + + track.id + ); + }); + } + if (constraints.video) { + stream.getVideoTracks().forEach(track => { + info( + this + + " gUM local stream " + + stream.id + + " with video track " + + track.id + ); + }); + } + return stream; + }, + + /** + * Requests all the media streams as specified in the constrains property. + * + * @param {array} constraintsList + * Array of constraints for GUM calls + */ + getAllUserMedia(constraintsList) { + if (constraintsList.length === 0) { + info("Skipping GUM: no UserMedia requested"); + return Promise.resolve(); + } + + info("Get " + constraintsList.length + " local streams"); + return Promise.all( + constraintsList.map(constraints => this.getUserMedia(constraints)) + ); + }, + + async getAllUserMediaAndAddStreams(constraintsList) { + var streams = await this.getAllUserMedia(constraintsList); + if (!streams) { + return; + } + return Promise.all(streams.map(stream => this.attachLocalStream(stream))); + }, + + async getAllUserMediaAndAddTransceivers(constraintsList) { + var streams = await this.getAllUserMedia(constraintsList); + if (!streams) { + return; + } + return Promise.all( + streams.map(stream => this.attachLocalStream(stream, true)) + ); + }, + + /** + * Create a new data channel instance. Also creates a promise called + * `this.nextDataChannel` that resolves when the next data channel arrives. + */ + expectDataChannel(message) { + this.nextDataChannel = new Promise(resolve => { + this.ondatachannel = e => { + ok(e.channel, message); + is( + e.channel.readyState, + "open", + "data channel in 'open' after 'ondatachannel'" + ); + resolve(e.channel); + }; + }); + }, + + /** + * Create a new data channel instance + * + * @param {Object} options + * Options which get forwarded to nsIPeerConnection.createDataChannel + * @returns {DataChannelWrapper} The created data channel + */ + createDataChannel(options) { + var label = "channel_" + this.dataChannels.length; + info(this + ": Create data channel '" + label); + + if (!this.dataChannels.length) { + this.expectNegotiationNeeded(); + } + var channel = this._pc.createDataChannel(label, options); + is(channel.readyState, "connecting", "initial readyState is 'connecting'"); + var wrapper = new DataChannelWrapper(channel, this); + this.dataChannels.push(wrapper); + return wrapper; + }, + + /** + * Creates an offer and automatically handles the failure case. + */ + createOffer() { + return this._pc.createOffer(this.offerOptions).then(offer => { + info("Got offer: " + JSON.stringify(offer)); + // note: this might get updated through ICE gathering + this._latest_offer = offer; + return offer; + }); + }, + + /** + * Creates an answer and automatically handles the failure case. + */ + createAnswer() { + return this._pc.createAnswer().then(answer => { + info(this + ": Got answer: " + JSON.stringify(answer)); + this._last_answer = answer; + return answer; + }); + }, + + /** + * Sets the local description and automatically handles the failure case. + * + * @param {object} desc + * RTCSessionDescriptionInit for the local description request + */ + setLocalDescription(desc) { + this.observedNegotiationNeeded = undefined; + return this._pc.setLocalDescription(desc).then(() => { + info(this + ": Successfully set the local description"); + }); + }, + + /** + * Tries to set the local description and expect failure. Automatically + * causes the test case to fail if the call succeeds. + * + * @param {object} desc + * RTCSessionDescriptionInit for the local description request + * @returns {Promise} + * A promise that resolves to the expected error + */ + setLocalDescriptionAndFail(desc) { + return this._pc + .setLocalDescription(desc) + .then( + generateErrorCallback("setLocalDescription should have failed."), + err => { + info(this + ": As expected, failed to set the local description"); + return err; + } + ); + }, + + /** + * Sets the remote description and automatically handles the failure case. + * + * @param {object} desc + * RTCSessionDescriptionInit for the remote description request + */ + setRemoteDescription(desc) { + this.observedNegotiationNeeded = undefined; + // This has to be done before calling sRD, otherwise a candidate in flight + // could end up in the PC's operations queue before sRD resolves. + if (desc.type == "rollback") { + this.holdIceCandidates = new Promise( + r => (this.releaseIceCandidates = r) + ); + } + return this._pc.setRemoteDescription(desc).then(() => { + info(this + ": Successfully set remote description"); + if (desc.type != "rollback") { + this.releaseIceCandidates(); + } + }); + }, + + /** + * Tries to set the remote description and expect failure. Automatically + * causes the test case to fail if the call succeeds. + * + * @param {object} desc + * RTCSessionDescriptionInit for the remote description request + * @returns {Promise} + * a promise that resolve to the returned error + */ + setRemoteDescriptionAndFail(desc) { + return this._pc + .setRemoteDescription(desc) + .then( + generateErrorCallback("setRemoteDescription should have failed."), + err => { + info(this + ": As expected, failed to set the remote description"); + return err; + } + ); + }, + + /** + * Registers a callback for the signaling state change and + * appends the new state to an array for logging it later. + */ + logSignalingState() { + this.signalingStateLog = [this._pc.signalingState]; + this._pc.addEventListener("signalingstatechange", e => { + var newstate = this._pc.signalingState; + var oldstate = this.signalingStateLog[this.signalingStateLog.length - 1]; + if (Object.keys(signalingStateTransitions).includes(oldstate)) { + ok( + signalingStateTransitions[oldstate].includes(newstate), + this + + ": legal signaling state transition from " + + oldstate + + " to " + + newstate + ); + } else { + ok( + false, + this + + ": old signaling state " + + oldstate + + " missing in signaling transition array" + ); + } + this.signalingStateLog.push(newstate); + }); + }, + + isTrackOnPC(track) { + return !!this.getStreamForRecvTrack(track); + }, + + allExpectedTracksAreObserved(expected, observed) { + return Object.keys(expected).every(trackId => observed[trackId]); + }, + + setupStreamEventHandlers(stream) { + const myTrackIds = new Set(stream.getTracks().map(t => t.id)); + + stream.addEventListener("addtrack", ({ track }) => { + ok( + !myTrackIds.has(track.id), + "Duplicate addtrack callback: " + + `stream id=${stream.id} track id=${track.id}` + ); + myTrackIds.add(track.id); + // addtrack events happen before track events, so the track callback hasn't + // heard about this yet. + let streams = this.remoteStreamsByTrackId.get(track.id); + ok( + !streams || !streams.has(stream.id), + `In addtrack for stream id=${stream.id}` + + `there should not have been a track event for track id=${track.id} ` + + " containing this stream yet." + ); + ok( + stream.getTracks().includes(track), + "In addtrack, stream id=" + + `${stream.id} should already contain track id=${track.id}` + ); + }); + + stream.addEventListener("removetrack", ({ track }) => { + ok( + myTrackIds.has(track.id), + "Duplicate removetrack callback: " + + `stream id=${stream.id} track id=${track.id}` + ); + myTrackIds.delete(track.id); + // Also remove the association from remoteStreamsByTrackId + const streams = this.remoteStreamsByTrackId.get(track.id); + ok( + streams, + `In removetrack for stream id=${stream.id}, track id=` + + `${track.id} should have had a track callback for the stream.` + ); + streams.delete(stream.id); + ok( + !stream.getTracks().includes(track), + "In removetrack, stream id=" + + `${stream.id} should not contain track id=${track.id}` + ); + }); + }, + + setupTrackEventHandler() { + this._pc.addEventListener("track", ({ track, streams }) => { + info(`${this}: 'ontrack' event fired for ${track.id}`); + ok(this.isTrackOnPC(track), `Found track ${track.id}`); + + let gratuitousEvent = true; + let streamsContainingTrack = this.remoteStreamsByTrackId.get(track.id); + if (!streamsContainingTrack) { + gratuitousEvent = false; // Told us about a new track + this.remoteStreamsByTrackId.set(track.id, new Set()); + streamsContainingTrack = this.remoteStreamsByTrackId.get(track.id); + } + + for (const stream of streams) { + ok( + stream.getTracks().includes(track), + `In track event, track id=${track.id}` + + ` should already be in stream id=${stream.id}` + ); + + if (!streamsContainingTrack.has(stream.id)) { + gratuitousEvent = false; // Told us about a new stream + streamsContainingTrack.add(stream.id); + this.setupStreamEventHandlers(stream); + } + } + + ok(!gratuitousEvent, "track event told us something new"); + + // So far, we've verified consistency between the current state of the + // streams, addtrack/removetrack events on the streams, and track events + // on the peerconnection. We have also verified that we have not gotten + // any gratuitous events. We have not done anything to verify that the + // current state of affairs matches what we were expecting it to. + + this.ensureMediaElement(track, "remote"); + }); + }, + + /** + * Either adds a given ICE candidate right away or stores it to be added + * later, depending on the state of the PeerConnection. + * + * @param {object} candidate + * The RTCIceCandidate to be added or stored + */ + storeOrAddIceCandidate(candidate) { + this._remote_ice_candidates.push(candidate); + if (this.signalingState === "closed") { + info("Received ICE candidate for closed PeerConnection - discarding"); + return; + } + this.holdIceCandidates + .then(() => { + info(this + ": adding ICE candidate " + JSON.stringify(candidate)); + return this._pc.addIceCandidate(candidate); + }) + .then(() => ok(true, this + " successfully added an ICE candidate")) + .catch(e => + // The onicecandidate callback runs independent of the test steps + // and therefore errors thrown from in there don't get caught by the + // race of the Promises around our test steps. + // Note: as long as we are queuing ICE candidates until the success + // of sRD() this should never ever happen. + ok(false, this + " adding ICE candidate failed with: " + e.message) + ); + }, + + /** + * Registers a callback for the ICE connection state change and + * appends the new state to an array for logging it later. + */ + logIceConnectionState() { + this.iceConnectionLog = [this._pc.iceConnectionState]; + this.ice_connection_callbacks.logIceStatus = () => { + var newstate = this._pc.iceConnectionState; + var oldstate = this.iceConnectionLog[this.iceConnectionLog.length - 1]; + if (Object.keys(iceStateTransitions).includes(oldstate)) { + if (this.iceCheckingRestartExpected) { + is( + newstate, + "checking", + "iceconnectionstate event '" + + newstate + + "' matches expected state 'checking'" + ); + this.iceCheckingRestartExpected = false; + } else if (this.iceCheckingIceRollbackExpected) { + is( + newstate, + "connected", + "iceconnectionstate event '" + + newstate + + "' matches expected state 'connected'" + ); + this.iceCheckingIceRollbackExpected = false; + } else { + ok( + iceStateTransitions[oldstate].includes(newstate), + this + + ": legal ICE state transition from " + + oldstate + + " to " + + newstate + ); + } + } else { + ok( + false, + this + + ": old ICE state " + + oldstate + + " missing in ICE transition array" + ); + } + this.iceConnectionLog.push(newstate); + }; + }, + + /** + * Resets the ICE connected Promise and allows ICE connection state monitoring + * to go backwards to 'checking'. + */ + expectIceChecking() { + this.iceCheckingRestartExpected = true; + this.iceConnected = new Promise((resolve, reject) => { + this.iceConnectedResolve = resolve; + this.iceConnectedReject = reject; + }); + }, + + /** + * Waits for ICE to either connect or fail. + * + * @returns {Promise} + * resolves when connected, rejects on failure + */ + waitForIceConnected() { + return this.iceConnected; + }, + + /** + * Setup a onicecandidate handler + * + * @param {object} test + * A PeerConnectionTest object to which the ice candidates gets + * forwarded. + */ + setupIceCandidateHandler(test, candidateHandler) { + candidateHandler = candidateHandler || test.iceCandidateHandler.bind(test); + + var resolveEndOfTrickle; + this.endOfTrickleIce = new Promise(r => (resolveEndOfTrickle = r)); + this.holdIceCandidates = new Promise(r => (this.releaseIceCandidates = r)); + + this._pc.onicecandidate = anEvent => { + if (!anEvent.candidate) { + this._pc.onicecandidate = () => + ok( + false, + this.label + " received ICE candidate after end of trickle" + ); + info(this.label + ": received end of trickle ICE event"); + ok( + this._pc.iceGatheringState === "complete", + "ICE gathering state has reached complete" + ); + resolveEndOfTrickle(this.label); + return; + } + + info( + this.label + ": iceCandidate = " + JSON.stringify(anEvent.candidate) + ); + ok(anEvent.candidate.sdpMid.length, "SDP mid not empty"); + ok( + anEvent.candidate.usernameFragment.length, + "usernameFragment not empty" + ); + + ok( + typeof anEvent.candidate.sdpMLineIndex === "number", + "SDP MLine Index needs to exist" + ); + this._local_ice_candidates.push(anEvent.candidate); + candidateHandler(this.label, anEvent.candidate); + }; + }, + + checkLocalMediaTracks() { + info( + `${this}: Checking local tracks ${JSON.stringify( + this.expectedLocalTrackInfo + )}` + ); + const sendersWithTrack = this._pc.getSenders().filter(({ track }) => track); + is( + sendersWithTrack.length, + this.expectedLocalTrackInfo.length, + "The number of senders with a track should be equal to the number of " + + "expected local tracks." + ); + + // expectedLocalTrackInfo is in the same order that the tracks were added, and + // so should the output of getSenders. + this.expectedLocalTrackInfo.forEach((info, i) => { + const sender = sendersWithTrack[i]; + is(sender, info.sender, `Sender ${i} should match`); + is(sender.track, info.track, `Track ${i} should match`); + }); + }, + + /** + * Checks that we are getting the media tracks we expect. + */ + checkMediaTracks() { + this.checkLocalMediaTracks(); + }, + + checkLocalMsids() { + const sdp = this.localDescription.sdp; + const msections = sdputils.getMSections(sdp); + const expectedStreamIdCounts = new Map(); + for (const { track, sender, streamId } of this.expectedLocalTrackInfo) { + const transceiver = this._pc + .getTransceivers() + .find(t => t.sender == sender); + ok(transceiver, "There should be a transceiver for each sender"); + if (transceiver.mid) { + const midFinder = new RegExp(`^a=mid:${transceiver.mid}$`, "m"); + const msection = msections.find(m => m.match(midFinder)); + ok( + msection, + `There should be a media section for mid = ${transceiver.mid}` + ); + ok( + msection.startsWith(`m=${track.kind}`), + `Media section should be of type ${track.kind}` + ); + const msidFinder = new RegExp(`^a=msid:${streamId} \\S+$`, "m"); + ok( + msection.match(msidFinder), + `Should find a=msid:${streamId} in media section` + + " (with any track id for now)" + ); + const count = expectedStreamIdCounts.get(streamId) || 0; + expectedStreamIdCounts.set(streamId, count + 1); + } + } + + // Check for any unexpected msids. + const allMsids = sdp.match(new RegExp("^a=msid:\\S+", "mg")); + if (!allMsids) { + return; + } + const allStreamIds = allMsids.map(msidAttr => + msidAttr.replace("a=msid:", "") + ); + allStreamIds.forEach(id => { + const count = expectedStreamIdCounts.get(id); + ok(count, `Unexpected stream id ${id} found in local description.`); + if (count) { + expectedStreamIdCounts.set(id, count - 1); + } + }); + }, + + /** + * Check that media flow is present for the given media element by checking + * that it reaches ready state HAVE_ENOUGH_DATA and progresses time further + * than the start of the check. + * + * This ensures, that the stream being played is producing + * data and, in case it contains a video track, that at least one video frame + * has been displayed. + * + * @param {HTMLMediaElement} track + * The media element to check + * @returns {Promise} + * A promise that resolves when media data is flowing. + */ + waitForMediaElementFlow(element) { + info("Checking data flow for element: " + element.id); + is( + element.ended, + !element.srcObject.active, + "Element ended should be the inverse of the MediaStream's active state" + ); + if (element.ended) { + is( + element.readyState, + element.HAVE_CURRENT_DATA, + "Element " + element.id + " is ended and should have had data" + ); + return Promise.resolve(); + } + + const haveEnoughData = ( + element.readyState == element.HAVE_ENOUGH_DATA + ? Promise.resolve() + : haveEvent( + element, + "canplay", + wait(60000, new Error("Timeout for element " + element.id)) + ) + ).then(_ => info("Element " + element.id + " has enough data.")); + + const startTime = element.currentTime; + const timeProgressed = timeout( + listenUntil(element, "timeupdate", _ => element.currentTime > startTime), + 60000, + "Element " + element.id + " should progress currentTime" + ).then(); + + return Promise.all([haveEnoughData, timeProgressed]); + }, + + /** + * Wait for RTP packet flow for the given MediaStreamTrack. + * + * @param {object} track + * A MediaStreamTrack to wait for data flow on. + * @returns {Promise} + * Returns a promise which yields a StatsReport object with RTP stats. + */ + async _waitForRtpFlow(target, rtpType) { + const { track } = target; + info(`_waitForRtpFlow(${track.id}, ${rtpType})`); + const packets = `packets${rtpType == "outbound-rtp" ? "Sent" : "Received"}`; + + const retryInterval = 500; // Time between stats checks + const timeout = 30000; // Timeout in ms + const retries = timeout / retryInterval; + + for (let i = 0; i < retries; i++) { + info(`Checking ${rtpType} for ${track.kind} track ${track.id} try ${i}`); + for (const rtp of (await target.getStats()).values()) { + if (rtp.type != rtpType) { + continue; + } + if (rtp.kind != track.kind) { + continue; + } + + const numPackets = rtp[packets]; + info(`Track ${track.id} has ${numPackets} ${packets}.`); + if (!numPackets) { + continue; + } + + ok(true, `RTP flowing for ${track.kind} track ${track.id}`); + return; + } + await wait(retryInterval); + } + throw new Error( + `Checking stats for track ${track.id} timed out after ${timeout} ms` + ); + }, + + /** + * Wait for inbound RTP packet flow for the given MediaStreamTrack. + * + * @param {object} receiver + * An RTCRtpReceiver to wait for data flow on. + * @returns {Promise} + * Returns a promise that resolves once data is flowing. + */ + async waitForInboundRtpFlow(receiver) { + return this._waitForRtpFlow(receiver, "inbound-rtp"); + }, + + /** + * Wait for outbound RTP packet flow for the given MediaStreamTrack. + * + * @param {object} sender + * An RTCRtpSender to wait for data flow on. + * @returns {Promise} + * Returns a promise that resolves once data is flowing. + */ + async waitForOutboundRtpFlow(sender) { + return this._waitForRtpFlow(sender, "outbound-rtp"); + }, + + getExpectedActiveReceivers() { + return this._pc + .getTransceivers() + .filter( + t => + !t.stopped && + t.currentDirection && + t.currentDirection != "inactive" && + t.currentDirection != "sendonly" + ) + .filter(({ receiver }) => receiver.track) + .map(({ mid, currentDirection, receiver }) => { + info( + `Found transceiver that should be receiving RTP: mid=${mid}` + + ` currentDirection=${currentDirection}` + + ` kind=${receiver.track.kind} track-id=${receiver.track.id}` + ); + return receiver; + }); + }, + + getExpectedSenders() { + return this._pc.getSenders().filter(({ track }) => track); + }, + + /** + * Wait for presence of video flow on all media elements and rtp flow on + * all sending and receiving track involved in this test. + * + * @returns {Promise} + * A promise that resolves when media flows for all elements and tracks + */ + waitForMediaFlow() { + const receivers = this.getExpectedActiveReceivers(); + return Promise.all([ + ...this.localMediaElements.map(el => this.waitForMediaElementFlow(el)), + ...this.remoteMediaElements + .filter(({ srcObject }) => + receivers.some(({ track }) => + srcObject.getTracks().some(t => t == track) + ) + ) + .map(el => this.waitForMediaElementFlow(el)), + ...receivers.map(receiver => this.waitForInboundRtpFlow(receiver)), + ...this.getExpectedSenders().map(sender => + this.waitForOutboundRtpFlow(sender) + ), + ]); + }, + + /** + * Check that correct audio (typically a flat tone) is flowing to this + * PeerConnection for each transceiver that should be receiving. Uses + * WebAudio AnalyserNodes to compare input and output audio data in the + * frequency domain. + * + * @param {object} from + * A PeerConnectionWrapper whose audio RTPSender we use as source for + * the audio flow check. + * @returns {Promise} + * A promise that resolves when we're receiving the tone/s from |from|. + */ + async checkReceivingToneFrom( + audiocontext, + from, + cancel = wait(60000, new Error("Tone not detected")) + ) { + let localTransceivers = this._pc + .getTransceivers() + .filter(t => t.mid) + .filter(t => t.receiver.track.kind == "audio") + .sort((t1, t2) => t1.mid < t2.mid); + let remoteTransceivers = from._pc + .getTransceivers() + .filter(t => t.mid) + .filter(t => t.receiver.track.kind == "audio") + .sort((t1, t2) => t1.mid < t2.mid); + + is( + localTransceivers.length, + remoteTransceivers.length, + "Same number of associated audio transceivers on remote and local." + ); + + for (let i = 0; i < localTransceivers.length; i++) { + is( + localTransceivers[i].mid, + remoteTransceivers[i].mid, + "Transceivers at index " + i + " have the same mid." + ); + + if (!remoteTransceivers[i].sender.track) { + continue; + } + + if ( + remoteTransceivers[i].currentDirection == "recvonly" || + remoteTransceivers[i].currentDirection == "inactive" + ) { + continue; + } + + let sendTrack = remoteTransceivers[i].sender.track; + let inputElem = from.getMediaElementForTrack(sendTrack, "local"); + ok( + inputElem, + "Remote wrapper should have a media element for track id " + + sendTrack.id + ); + let inputAudioStream = from.getStreamForSendTrack(sendTrack); + ok( + inputAudioStream, + "Remote wrapper should have a stream for track id " + sendTrack.id + ); + let inputAnalyser = new AudioStreamAnalyser( + audiocontext, + inputAudioStream + ); + + let recvTrack = localTransceivers[i].receiver.track; + let outputAudioStream = this.getStreamForRecvTrack(recvTrack); + ok( + outputAudioStream, + "Local wrapper should have a stream for track id " + recvTrack.id + ); + let outputAnalyser = new AudioStreamAnalyser( + audiocontext, + outputAudioStream + ); + + let error = null; + cancel.then(e => (error = e)); + + let indexOfMax = data => + data.reduce((max, val, i) => (val >= data[max] ? i : max), 0); + + await outputAnalyser.waitForAnalysisSuccess(() => { + if (error) { + throw error; + } + + let inputData = inputAnalyser.getByteFrequencyData(); + let outputData = outputAnalyser.getByteFrequencyData(); + + let inputMax = indexOfMax(inputData); + let outputMax = indexOfMax(outputData); + info( + `Comparing maxima; input[${inputMax}] = ${inputData[inputMax]},` + + ` output[${outputMax}] = ${outputData[outputMax]}` + ); + if (!inputData[inputMax] || !outputData[outputMax]) { + return false; + } + + // When the input and output maxima are within reasonable distance (2% of + // total length, which means ~10 for length 512) from each other, we can + // be sure that the input tone has made it through the peer connection. + info(`input data length: ${inputData.length}`); + return Math.abs(inputMax - outputMax) < inputData.length * 0.02; + }); + } + }, + + /** + * Check that stats are present by checking for known stats. + */ + async getStats(selector) { + const stats = await this._pc.getStats(selector); + const dict = {}; + for (const [k, v] of stats.entries()) { + dict[k] = v; + } + info(`${this}: Got stats: ${JSON.stringify(dict)}`); + return stats; + }, + + /** + * Checks that we are getting the media streams we expect. + * + * @param {object} stats + * The stats to check from this PeerConnectionWrapper + */ + checkStats(stats) { + const isRemote = ({ type }) => + ["remote-outbound-rtp", "remote-inbound-rtp"].includes(type); + var counters = {}; + for (let [key, res] of stats) { + info("Checking stats for " + key + " : " + res); + // validate stats + ok(res.id == key, "Coherent stats id"); + const now = performance.timeOrigin + performance.now(); + const minimum = performance.timeOrigin; + const type = isRemote(res) ? "rtcp" : "rtp"; + ok( + res.timestamp >= minimum, + `Valid ${type} timestamp ${res.timestamp} >= ${minimum} ( + ${res.timestamp - minimum} ms)` + ); + ok( + res.timestamp <= now, + `Valid ${type} timestamp ${res.timestamp} <= ${now} ( + ${res.timestamp - now} ms)` + ); + if (isRemote(res)) { + continue; + } + counters[res.type] = (counters[res.type] || 0) + 1; + + switch (res.type) { + case "inbound-rtp": + case "outbound-rtp": + { + // Inbound tracks won't have an ssrc if RTP is not flowing. + // (eg; negotiated inactive) + ok( + res.ssrc || res.type == "inbound-rtp", + "Outbound RTP stats has an ssrc." + ); + + if (res.ssrc) { + // ssrc is a 32 bit number returned as an unsigned long + ok(!/[^0-9]/.test(`${res.ssrc}`), "SSRC is numeric"); + ok(parseInt(res.ssrc) < Math.pow(2, 32), "SSRC is within limits"); + } + + if (res.type == "outbound-rtp") { + ok(res.packetsSent !== undefined, "Rtp packetsSent"); + // We assume minimum payload to be 1 byte (guess from RFC 3550) + ok(res.bytesSent >= res.packetsSent, "Rtp bytesSent"); + } else { + ok(res.packetsReceived !== undefined, "Rtp packetsReceived"); + ok(res.bytesReceived >= res.packetsReceived, "Rtp bytesReceived"); + } + if (res.remoteId) { + var rem = stats.get(res.remoteId); + ok(isRemote(rem), "Remote is rtcp"); + ok(rem.localId == res.id, "Remote backlink match"); + if (res.type == "outbound-rtp") { + ok(rem.type == "remote-inbound-rtp", "Rtcp is inbound"); + if (rem.packetsLost) { + ok( + rem.packetsLost >= 0, + "Rtcp packetsLost " + rem.packetsLost + " >= 0" + ); + ok( + rem.packetsLost < 1000, + "Rtcp packetsLost " + rem.packetsLost + " < 1000" + ); + } + if (!this.disableRtpCountChecking) { + // no guarantee which one is newer! + // Note: this must change when we add a timestamp field to remote RTCP reports + // and make rem.timestamp be the reception time + if (res.timestamp < rem.timestamp) { + info( + "REVERSED timestamps: rec:" + + rem.packetsReceived + + " time:" + + rem.timestamp + + " sent:" + + res.packetsSent + + " time:" + + res.timestamp + ); + } + } + if (rem.jitter) { + ok(rem.jitter >= 0, "Rtcp jitter " + rem.jitter + " >= 0"); + ok(rem.jitter < 5, "Rtcp jitter " + rem.jitter + " < 5 sec"); + } + if (rem.roundTripTime) { + ok( + rem.roundTripTime >= 0, + "Rtcp rtt " + rem.roundTripTime + " >= 0" + ); + ok( + rem.roundTripTime < 60, + "Rtcp rtt " + rem.roundTripTime + " < 1 min" + ); + } + } else { + ok(rem.type == "remote-outbound-rtp", "Rtcp is outbound"); + ok(rem.packetsSent !== undefined, "Rtcp packetsSent"); + ok(rem.bytesSent !== undefined, "Rtcp bytesSent"); + } + ok(rem.ssrc == res.ssrc, "Remote ssrc match"); + } else { + info("No rtcp info received yet"); + } + } + break; + } + } + + var nin = this._pc.getTransceivers().filter(t => { + return ( + !t.stopped && + t.currentDirection != "inactive" && + t.currentDirection != "sendonly" + ); + }).length; + const nout = Object.keys(this.expectedLocalTrackInfo).length; + var ndata = this.dataChannels.length; + + // TODO(Bug 957145): Restore stronger inbound-rtp test once Bug 948249 is fixed + //is((counters["inbound-rtp"] || 0), nin, "Have " + nin + " inbound-rtp stat(s)"); + ok( + (counters["inbound-rtp"] || 0) >= nin, + "Have at least " + nin + " inbound-rtp stat(s) *" + ); + + is( + counters["outbound-rtp"] || 0, + nout, + "Have " + nout + " outbound-rtp stat(s)" + ); + + var numLocalCandidates = counters["local-candidate"] || 0; + var numRemoteCandidates = counters["remote-candidate"] || 0; + // If there are no tracks, there will be no stats either. + if (nin + nout + ndata > 0) { + ok(numLocalCandidates, "Have local-candidate stat(s)"); + ok(numRemoteCandidates, "Have remote-candidate stat(s)"); + } else { + is(numLocalCandidates, 0, "Have no local-candidate stats"); + is(numRemoteCandidates, 0, "Have no remote-candidate stats"); + } + }, + + /** + * Compares the Ice server configured for this PeerConnectionWrapper + * with the ICE candidates received in the RTCP stats. + * + * @param {object} stats + * The stats to be verified for relayed vs. direct connection. + */ + checkStatsIceConnectionType(stats, expectedLocalCandidateType) { + let lId; + let rId; + for (let stat of stats.values()) { + if (stat.type == "candidate-pair" && stat.selected) { + lId = stat.localCandidateId; + rId = stat.remoteCandidateId; + break; + } + } + isnot( + lId, + undefined, + "Got local candidate ID " + lId + " for selected pair" + ); + isnot( + rId, + undefined, + "Got remote candidate ID " + rId + " for selected pair" + ); + let lCand = stats.get(lId); + let rCand = stats.get(rId); + if (!lCand || !rCand) { + ok( + false, + "failed to find candidatepair IDs or stats for local: " + + lId + + " remote: " + + rId + ); + return; + } + + info( + "checkStatsIceConnectionType verifying: local=" + + JSON.stringify(lCand) + + " remote=" + + JSON.stringify(rCand) + ); + expectedLocalCandidateType = expectedLocalCandidateType || "host"; + var candidateType = lCand.candidateType; + if (lCand.relayProtocol === "tcp" && candidateType === "relay") { + candidateType = "relay-tcp"; + } + + if (lCand.relayProtocol === "tls" && candidateType === "relay") { + candidateType = "relay-tls"; + } + + if (expectedLocalCandidateType === "srflx" && candidateType === "prflx") { + // Be forgiving of prflx when expecting srflx, since that can happen due + // to timing. + candidateType = "srflx"; + } + + is( + candidateType, + expectedLocalCandidateType, + "Local candidate type is what we expected for selected pair" + ); + }, + + /** + * Compares amount of established ICE connection according to ICE candidate + * pairs in the stats reporting with the expected amount of connection based + * on the constraints. + * + * @param {object} stats + * The stats to check for ICE candidate pairs + * @param {object} testOptions + * The test options object from the PeerConnectionTest + */ + checkStatsIceConnections(stats, testOptions) { + var numIceConnections = 0; + stats.forEach(stat => { + if (stat.type === "candidate-pair" && stat.selected) { + numIceConnections += 1; + } + }); + info("ICE connections according to stats: " + numIceConnections); + isnot( + numIceConnections, + 0, + "Number of ICE connections according to stats is not zero" + ); + if (testOptions.bundle) { + if (testOptions.rtcpmux) { + is(numIceConnections, 1, "stats reports exactly 1 ICE connection"); + } else { + is( + numIceConnections, + 2, + "stats report exactly 2 ICE connections for media and RTCP" + ); + } + } else { + var numAudioTransceivers = this._pc + .getTransceivers() + .filter(transceiver => { + return ( + !transceiver.stopped && transceiver.receiver.track.kind == "audio" + ); + }).length; + + var numVideoTransceivers = this._pc + .getTransceivers() + .filter(transceiver => { + return ( + !transceiver.stopped && transceiver.receiver.track.kind == "video" + ); + }).length; + + var numExpectedTransports = numAudioTransceivers + numVideoTransceivers; + if (!testOptions.rtcpmux) { + numExpectedTransports *= 2; + } + + if (this.dataChannels.length) { + ++numExpectedTransports; + } + + info( + "expected audio + video + data transports: " + numExpectedTransports + ); + is( + numIceConnections, + numExpectedTransports, + "stats ICE connections matches expected A/V transports" + ); + } + }, + + expectNegotiationNeeded() { + if (!this.observedNegotiationNeeded) { + this.observedNegotiationNeeded = new Promise(resolve => { + this.onnegotiationneeded = resolve; + }); + } + }, + + /** + * Property-matching function for finding a certain stat in passed-in stats + * + * @param {object} stats + * The stats to check from this PeerConnectionWrapper + * @param {object} props + * The properties to look for + * @returns {boolean} Whether an entry containing all match-props was found. + */ + hasStat(stats, props) { + for (let res of stats.values()) { + var match = true; + for (let prop in props) { + if (res[prop] !== props[prop]) { + match = false; + break; + } + } + if (match) { + return true; + } + } + return false; + }, + + /** + * Closes the connection + */ + close() { + this._pc.close(); + this.localMediaElements.forEach(e => e.pause()); + info(this + ": Closed connection."); + }, + + /** + * Returns the string representation of the class + * + * @returns {String} The string representation + */ + toString() { + return "PeerConnectionWrapper (" + this.label + ")"; + }, +}; + +// haxx to prevent SimpleTest from failing at window.onload +function addLoadEvent() {} + +function loadScript(...scripts) { + return Promise.all( + scripts.map(script => { + var el = document.createElement("script"); + if (typeof scriptRelativePath === "string" && script.charAt(0) !== "/") { + script = scriptRelativePath + script; + } + el.src = script; + document.head.appendChild(el); + return new Promise(r => { + el.onload = r; + el.onerror = r; + }); + }) + ); +} + +// Ensure SimpleTest.js is loaded before other scripts. +/* import-globals-from /testing/mochitest/tests/SimpleTest/SimpleTest.js */ +/* import-globals-from head.js */ +/* import-globals-from templates.js */ +/* import-globals-from turnConfig.js */ +/* import-globals-from dataChannel.js */ +/* import-globals-from network.js */ +/* import-globals-from sdpUtils.js */ + +var scriptsReady = loadScript("/tests/SimpleTest/SimpleTest.js").then(() => { + return loadScript( + "head.js", + "templates.js", + "turnConfig.js", + "dataChannel.js", + "network.js", + "sdpUtils.js" + ); +}); + +function createHTML(options) { + return scriptsReady.then(() => realCreateHTML(options)); +} + +var iceServerWebsocket; +var iceServersArray = []; + +var addTurnsSelfsignedCerts = () => { + var gUrl = SimpleTest.getTestFileURL("addTurnsSelfsignedCert.js"); + var gScript = SpecialPowers.loadChromeScript(gUrl); + var certs = []; + // If the ICE server is running TURNS, and includes a "cert" attribute in + // its JSON, we set up an override that will forgive things like + // self-signed for it. + iceServersArray.forEach(iceServer => { + if (iceServer.hasOwnProperty("cert")) { + iceServer.urls.forEach(url => { + if (url.startsWith("turns:")) { + // Assumes no port or params! + certs.push({ cert: iceServer.cert, hostname: url.substr(6) }); + } + }); + } + }); + + return new Promise((resolve, reject) => { + gScript.addMessageListener("certs-added", () => { + resolve(); + }); + + gScript.sendAsyncMessage("add-turns-certs", certs); + }); +}; + +var setupIceServerConfig = useIceServer => { + // We disable ICE support for HTTP proxy when using a TURN server, because + // mochitest uses a fake HTTP proxy to serve content, which will eat our STUN + // packets for TURN TCP. + var enableHttpProxy = enable => + SpecialPowers.pushPrefEnv({ + set: [["media.peerconnection.disable_http_proxy", !enable]], + }); + + var spawnIceServer = () => + new Promise((resolve, reject) => { + iceServerWebsocket = new WebSocket("ws://localhost:8191/"); + iceServerWebsocket.onopen = event => { + info("websocket/process bridge open, starting ICE Server..."); + iceServerWebsocket.send("iceserver"); + }; + + iceServerWebsocket.onmessage = event => { + // The first message will contain the iceServers configuration, subsequent + // messages are just logging. + info("ICE Server: " + event.data); + resolve(event.data); + }; + + iceServerWebsocket.onerror = () => { + reject("ICE Server error: Is the ICE server websocket up?"); + }; + + iceServerWebsocket.onclose = () => { + info("ICE Server websocket closed"); + reject("ICE Server gone before getting configuration"); + }; + }); + + if (!useIceServer) { + info("Skipping ICE Server for this test"); + return enableHttpProxy(true); + } + + return enableHttpProxy(false) + .then(spawnIceServer) + .then(iceServersStr => { + iceServersArray = JSON.parse(iceServersStr); + }) + .then(addTurnsSelfsignedCerts); +}; + +async function runNetworkTest(testFunction, fixtureOptions = {}) { + let { AppConstants } = SpecialPowers.ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" + ); + + await scriptsReady; + await runTestWhenReady(async options => { + await startNetworkAndTest(); + await setupIceServerConfig(fixtureOptions.useIceServer); + await testFunction(options); + await networkTestFinished(); + }); +} diff --git a/dom/media/webrtc/tests/mochitests/peerconnection_audio_forced_sample_rate.js b/dom/media/webrtc/tests/mochitests/peerconnection_audio_forced_sample_rate.js new file mode 100644 index 0000000000..d0c647be0d --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/peerconnection_audio_forced_sample_rate.js @@ -0,0 +1,32 @@ +// This function takes a sample-rate, and tests that audio flows correctly when +// the sampling-rate at which the MTG runs is not one of the sampling-rates that +// the MediaPipeline can work with. +// It is in a separate file because we have an MTG per document, and we want to +// test multiple sample-rates, so we include it in multiple HTML mochitest +// files. +async function test_peerconnection_audio_forced_sample_rate(forcedSampleRate) { + await scriptsReady; + await pushPrefs(["media.cubeb.force_sample_rate", forcedSampleRate]); + await runNetworkTest(function (options) { + const test = new PeerConnectionTest(options); + const ac = new AudioContext(); + test.setMediaConstraints([{ audio: true }], []); + test.chain.replace("PC_LOCAL_GUM", [ + function PC_LOCAL_WEBAUDIO_SOURCE(test) { + const oscillator = ac.createOscillator(); + oscillator.type = "sine"; + oscillator.frequency.value = 700; + oscillator.start(); + const dest = ac.createMediaStreamDestination(); + oscillator.connect(dest); + test.pcLocal.attachLocalStream(dest.stream); + }, + ]); + test.chain.append([ + function CHECK_REMOTE_AUDIO_FLOW(test) { + return test.pcRemote.checkReceivingToneFrom(ac, test.pcLocal); + }, + ]); + return test.run(); + }); +} diff --git a/dom/media/webrtc/tests/mochitests/sdpUtils.js b/dom/media/webrtc/tests/mochitests/sdpUtils.js new file mode 100644 index 0000000000..51cae10dba --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/sdpUtils.js @@ -0,0 +1,398 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var sdputils = { + // Finds the codec id / payload type given a codec format + // (e.g., "VP8", "VP9/90000"). `offset` tells us which one to use in case of + // multiple matches. + findCodecId(sdp, format, offset = 0) { + let regex = new RegExp("rtpmap:([0-9]+) " + format, "gi"); + let match; + for (let i = 0; i <= offset; ++i) { + match = regex.exec(sdp); + if (!match) { + throw new Error( + "Couldn't find offset " + + i + + " of codec " + + format + + " while looking for offset " + + offset + + " in sdp:\n" + + sdp + ); + } + } + // match[0] is the full matched string + // match[1] is the first parenthesis group + return match[1]; + }, + + // Returns a list of all payload types, excluding rtx, in an sdp. + getPayloadTypes(sdp) { + const regex = /^a=rtpmap:([0-9]+) (?:(?!rtx).)*$/gim; + const pts = []; + for (const [line, pt] of sdp.matchAll(regex)) { + pts.push(pt); + } + return pts; + }, + + // Finds all the extmap ids in the given sdp. Note that this does NOT + // consider m-sections, so a more generic version would need to + // look at each m-section separately. + findExtmapIds(sdp) { + var sdpExtmapIds = []; + extmapRegEx = /^a=extmap:([0-9+])/gm; + // must call exec on the regex to get each match in the string + while ((searchResults = extmapRegEx.exec(sdp)) !== null) { + // returned array has the matched text as the first item, + // and then one item for each capturing parenthesis that + // matched containing the text that was captured. + sdpExtmapIds.push(searchResults[1]); + } + return sdpExtmapIds; + }, + + findExtmapIdsUrnsDirections(sdp) { + var sdpExtmap = []; + extmapRegEx = /^a=extmap:([0-9+])([A-Za-z/]*) ([A-Za-z0-9_:\-\/\.]+)/gm; + // must call exec on the regex to get each match in the string + while ((searchResults = extmapRegEx.exec(sdp)) !== null) { + // returned array has the matched text as the first item, + // and then one item for each capturing parenthesis that + // matched containing the text that was captured. + var idUrn = []; + idUrn.push(searchResults[1]); + idUrn.push(searchResults[3]); + idUrn.push(searchResults[2].slice(1)); + sdpExtmap.push(idUrn); + } + return sdpExtmap; + }, + + verify_unique_extmap_ids(sdp) { + const sdpExtmapIds = sdputils.findExtmapIdsUrnsDirections(sdp); + + return sdpExtmapIds.reduce(function (result, item, index) { + const [id, urn, dir] = item; + ok( + !(id in result) || (result[id][0] === urn && result[id][1] === dir), + "ID " + id + " is unique ID for " + urn + " and direction " + dir + ); + result[id] = [urn, dir]; + return result; + }, {}); + }, + + getMSections(sdp) { + return sdp + .split(new RegExp("^m=", "gm")) + .slice(1) + .map(s => "m=" + s); + }, + + getAudioMSections(sdp) { + return this.getMSections(sdp).filter(section => + section.startsWith("m=audio") + ); + }, + + getVideoMSections(sdp) { + return this.getMSections(sdp).filter(section => + section.startsWith("m=video") + ); + }, + + checkSdpAfterEndOfTrickle(description, testOptions, label) { + info("EOC-SDP: " + JSON.stringify(description)); + + const checkForTransportAttributes = msection => { + info("Checking msection: " + msection); + ok( + msection.includes("a=end-of-candidates"), + label + ": SDP contains end-of-candidates" + ); + + if (!msection.startsWith("m=application")) { + if (testOptions.rtcpmux) { + ok( + msection.includes("a=rtcp-mux"), + label + ": SDP contains rtcp-mux" + ); + } else { + ok(msection.includes("a=rtcp:"), label + ": SDP contains rtcp port"); + } + } + }; + + const hasOwnTransport = msection => { + const port0Check = new RegExp(/^m=\S+ 0 /).exec(msection); + if (port0Check) { + return false; + } + const midMatch = new RegExp(/\r\na=mid:(\S+)/).exec(msection); + if (!midMatch) { + return true; + } + const mid = midMatch[1]; + const bundleGroupMatch = new RegExp( + "\\r\\na=group:BUNDLE \\S.* " + mid + "\\s+" + ).exec(description.sdp); + return bundleGroupMatch == null; + }; + + const msectionsWithOwnTransports = this.getMSections( + description.sdp + ).filter(hasOwnTransport); + + ok( + msectionsWithOwnTransports.length, + "SDP should contain at least one msection with a transport" + ); + msectionsWithOwnTransports.forEach(checkForTransportAttributes); + + if (testOptions.ssrc) { + ok(description.sdp.includes("a=ssrc"), label + ": SDP contains a=ssrc"); + } else { + ok( + !description.sdp.includes("a=ssrc"), + label + ": SDP does not contain a=ssrc" + ); + } + }, + + // Note, we don't bother removing the fmtp lines, which makes a good test + // for some SDP parsing issues. + removeCodec(sdp, codec) { + var updated_sdp = sdp.replace( + new RegExp("a=rtpmap:" + codec + ".*\\/90000\\r\\n", ""), + "" + ); + updated_sdp = updated_sdp.replace( + new RegExp("(RTP\\/SAVPF.*)( " + codec + ")(.*\\r\\n)", ""), + "$1$3" + ); + updated_sdp = updated_sdp.replace( + new RegExp("a=rtcp-fb:" + codec + " nack\\r\\n", ""), + "" + ); + updated_sdp = updated_sdp.replace( + new RegExp("a=rtcp-fb:" + codec + " nack pli\\r\\n", ""), + "" + ); + updated_sdp = updated_sdp.replace( + new RegExp("a=rtcp-fb:" + codec + " ccm fir\\r\\n", ""), + "" + ); + return updated_sdp; + }, + + removeAllButPayloadType(sdp, pt) { + return sdp.replace( + new RegExp("m=(\\w+ \\w+) UDP/TLS/RTP/SAVPF .*" + pt + ".*\\r\\n", "gi"), + "m=$1 UDP/TLS/RTP/SAVPF " + pt + "\r\n" + ); + }, + + removeRtpMapForPayloadType(sdp, pt) { + return sdp.replace(new RegExp("a=rtpmap:" + pt + ".*\\r\\n", "gi"), ""); + }, + + removeRtcpMux(sdp) { + return sdp.replace(/a=rtcp-mux\r\n/g, ""); + }, + + removeSSRCs(sdp) { + return sdp.replace(/a=ssrc.*\r\n/g, ""); + }, + + removeBundle(sdp) { + return sdp.replace(/a=group:BUNDLE .*\r\n/g, ""); + }, + + reduceAudioMLineToPcmuPcma(sdp) { + return sdp.replace( + /m=audio .*\r\n/g, + "m=audio 9 UDP/TLS/RTP/SAVPF 0 8\r\n" + ); + }, + + setAllMsectionsInactive(sdp) { + return sdp + .replace(/\r\na=sendrecv/g, "\r\na=inactive") + .replace(/\r\na=sendonly/g, "\r\na=inactive") + .replace(/\r\na=recvonly/g, "\r\na=inactive"); + }, + + removeAllRtpMaps(sdp) { + return sdp.replace(/a=rtpmap:.*\r\n/g, ""); + }, + + reduceAudioMLineToDynamicPtAndOpus(sdp) { + return sdp.replace( + /m=audio .*\r\n/g, + "m=audio 9 UDP/TLS/RTP/SAVPF 101 109\r\n" + ); + }, + + addTiasBps(sdp, bps) { + return sdp.replace(/c=IN (.*)\r\n/g, "c=IN $1\r\nb=TIAS:" + bps + "\r\n"); + }, + + removeSimulcastProperties(sdp) { + return sdp + .replace(/a=simulcast:.*\r\n/g, "") + .replace(/a=rid:.*\r\n/g, "") + .replace( + /a=extmap:[^\s]* urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id.*\r\n/g, + "" + ) + .replace( + /a=extmap:[^\s]* urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id.*\r\n/g, + "" + ); + }, + + transferSimulcastProperties(offer_sdp, answer_sdp) { + if (!offer_sdp.includes("a=simulcast:")) { + return answer_sdp; + } + ok( + offer_sdp.includes("a=simulcast:send "), + "Offer contains simulcast attribute" + ); + var o_simul = offer_sdp.match(/simulcast:send (.*)([\n$])*/i); + var new_answer_sdp = answer_sdp + "a=simulcast:recv " + o_simul[1] + "\r\n"; + ok(offer_sdp.includes("a=rid:"), "Offer contains RID attribute"); + var o_rids = offer_sdp.match(/a=rid:(.*)/gi); + o_rids.forEach(o_rid => { + new_answer_sdp = new_answer_sdp + o_rid.replace(/send/, "recv") + "\r\n"; + }); + var extmap_id = offer_sdp.match( + "a=extmap:([0-9+])/sendonly urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id" + ); + ok(extmap_id != null, "Offer contains RID RTP header extension"); + new_answer_sdp = + new_answer_sdp + + "a=extmap:" + + extmap_id[1] + + "/recvonly urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\n"; + var extmap_id = offer_sdp.match( + "a=extmap:([0-9+])/sendonly urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id" + ); + if (extmap_id != null) { + new_answer_sdp = + new_answer_sdp + + "a=extmap:" + + extmap_id[1] + + "/recvonly urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\n"; + } + + return new_answer_sdp; + }, + + verifySdp( + desc, + expectedType, + offerConstraintsList, + offerOptions, + testOptions + ) { + info("Examining this SessionDescription: " + JSON.stringify(desc)); + info("offerConstraintsList: " + JSON.stringify(offerConstraintsList)); + info("offerOptions: " + JSON.stringify(offerOptions)); + ok(desc, "SessionDescription is not null"); + is(desc.type, expectedType, "SessionDescription type is " + expectedType); + ok(desc.sdp.length > 10, "SessionDescription body length is plausible"); + ok(desc.sdp.includes("a=ice-ufrag"), "ICE username is present in SDP"); + ok(desc.sdp.includes("a=ice-pwd"), "ICE password is present in SDP"); + ok(desc.sdp.includes("a=fingerprint"), "ICE fingerprint is present in SDP"); + //TODO: update this for loopback support bug 1027350 + ok( + !desc.sdp.includes(LOOPBACK_ADDR), + "loopback interface is absent from SDP" + ); + var requiresTrickleIce = !desc.sdp.includes("a=candidate"); + if (requiresTrickleIce) { + info("No ICE candidate in SDP -> requiring trickle ICE"); + } else { + info("at least one ICE candidate is present in SDP"); + } + + //TODO: how can we check for absence/presence of m=application? + + var audioTracks = + sdputils.countTracksInConstraint("audio", offerConstraintsList) || + (offerOptions && offerOptions.offerToReceiveAudio ? 1 : 0); + + info("expected audio tracks: " + audioTracks); + if (audioTracks == 0) { + ok(!desc.sdp.includes("m=audio"), "audio m-line is absent from SDP"); + } else { + ok(desc.sdp.includes("m=audio"), "audio m-line is present in SDP"); + is( + testOptions.opus, + desc.sdp.includes("a=rtpmap:109 opus/48000/2"), + "OPUS codec is present in SDP" + ); + //TODO: ideally the rtcp-mux should be for the m=audio, and not just + // anywhere in the SDP (JS SDP parser bug 1045429) + is( + testOptions.rtcpmux, + desc.sdp.includes("a=rtcp-mux"), + "RTCP Mux is offered in SDP" + ); + } + + var videoTracks = + sdputils.countTracksInConstraint("video", offerConstraintsList) || + (offerOptions && offerOptions.offerToReceiveVideo ? 1 : 0); + + info("expected video tracks: " + videoTracks); + if (videoTracks == 0) { + ok(!desc.sdp.includes("m=video"), "video m-line is absent from SDP"); + } else { + ok(desc.sdp.includes("m=video"), "video m-line is present in SDP"); + if (testOptions.h264) { + ok( + desc.sdp.includes("a=rtpmap:126 H264/90000") || + desc.sdp.includes("a=rtpmap:97 H264/90000"), + "H.264 codec is present in SDP" + ); + } else { + ok( + desc.sdp.includes("a=rtpmap:120 VP8/90000") || + desc.sdp.includes("a=rtpmap:121 VP9/90000"), + "VP8 or VP9 codec is present in SDP" + ); + } + is( + testOptions.rtcpmux, + desc.sdp.includes("a=rtcp-mux"), + "RTCP Mux is offered in SDP" + ); + is( + testOptions.ssrc, + desc.sdp.includes("a=ssrc"), + "a=ssrc signaled in SDP" + ); + } + + return requiresTrickleIce; + }, + + /** + * Counts the amount of audio tracks in a given media constraint. + * + * @param constraints + * The contraint to be examined. + */ + countTracksInConstraint(type, constraints) { + if (!Array.isArray(constraints)) { + return 0; + } + return constraints.reduce((sum, c) => sum + (c[type] ? 1 : 0), 0); + }, +}; diff --git a/dom/media/webrtc/tests/mochitests/simulcast.js b/dom/media/webrtc/tests/mochitests/simulcast.js new file mode 100644 index 0000000000..0af36478c4 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/simulcast.js @@ -0,0 +1,232 @@ +"use strict"; +/* Helper functions to munge SDP and split the sending track into + * separate tracks on the receiving end. This can be done in a number + * of ways, the one used here uses the fact that the MID and RID header + * extensions which are used for packet routing share the same wire + * format. The receiver interprets the rids from the sender as mids + * which allows receiving the different spatial resolutions on separate + * m-lines and tracks. + */ + +// Borrowed from wpt, with some dependencies removed. + +const ridExtensions = [ + "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id", + "urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id", +]; + +function ridToMid(description, rids) { + const sections = SDPUtils.splitSections(description.sdp); + const dtls = SDPUtils.getDtlsParameters(sections[1], sections[0]); + const ice = SDPUtils.getIceParameters(sections[1], sections[0]); + const rtpParameters = SDPUtils.parseRtpParameters(sections[1]); + const setupValue = description.sdp.match(/a=setup:(.*)/)[1]; + const directionValue = + description.sdp.match(/a=sendrecv|a=sendonly|a=recvonly|a=inactive/) || + "a=sendrecv"; + const mline = SDPUtils.parseMLine(sections[1]); + + // Skip mid extension; we are replacing it with the rid extmap + rtpParameters.headerExtensions = rtpParameters.headerExtensions.filter( + ext => ext.uri != "urn:ietf:params:rtp-hdrext:sdes:mid" + ); + + for (const ext of rtpParameters.headerExtensions) { + if (ext.uri == "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id") { + ext.uri = "urn:ietf:params:rtp-hdrext:sdes:mid"; + } + } + + // Filter rtx as we have no way to (re)interpret rrid. + // Not doing this makes probing use RTX, it's not understood and ramp-up is slower. + rtpParameters.codecs = rtpParameters.codecs.filter( + c => c.name.toUpperCase() !== "RTX" + ); + + if (!rids) { + rids = Array.from(description.sdp.matchAll(/a=rid:(.*) send/g)).map( + r => r[1] + ); + } + + let sdp = + SDPUtils.writeSessionBoilerplate() + + SDPUtils.writeDtlsParameters(dtls, setupValue) + + SDPUtils.writeIceParameters(ice) + + "a=group:BUNDLE " + + rids.join(" ") + + "\r\n"; + const baseRtpDescription = SDPUtils.writeRtpDescription( + mline.kind, + rtpParameters + ); + for (const rid of rids) { + sdp += + baseRtpDescription + + "a=mid:" + + rid + + "\r\n" + + "a=msid:rid-" + + rid + + " rid-" + + rid + + "\r\n"; + sdp += directionValue + "\r\n"; + } + return sdp; +} + +function midToRid(description, localDescription, rids) { + const sections = SDPUtils.splitSections(description.sdp); + const dtls = SDPUtils.getDtlsParameters(sections[1], sections[0]); + const ice = SDPUtils.getIceParameters(sections[1], sections[0]); + const rtpParameters = SDPUtils.parseRtpParameters(sections[1]); + const setupValue = description.sdp.match(/a=setup:(.*)/)[1]; + const directionValue = + description.sdp.match(/a=sendrecv|a=sendonly|a=recvonly|a=inactive/) || + "a=sendrecv"; + const mline = SDPUtils.parseMLine(sections[1]); + + // Skip rid extensions; we are replacing them with the mid extmap + rtpParameters.headerExtensions = rtpParameters.headerExtensions.filter( + ext => !ridExtensions.includes(ext.uri) + ); + + for (const ext of rtpParameters.headerExtensions) { + if (ext.uri == "urn:ietf:params:rtp-hdrext:sdes:mid") { + ext.uri = "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id"; + } + } + + const localMid = localDescription + ? SDPUtils.getMid(SDPUtils.splitSections(localDescription.sdp)[1]) + : "0"; + + if (!rids) { + rids = []; + for (let i = 1; i < sections.length; i++) { + rids.push(SDPUtils.getMid(sections[i])); + } + } + + let sdp = + SDPUtils.writeSessionBoilerplate() + + SDPUtils.writeDtlsParameters(dtls, setupValue) + + SDPUtils.writeIceParameters(ice) + + "a=group:BUNDLE " + + localMid + + "\r\n"; + sdp += SDPUtils.writeRtpDescription(mline.kind, rtpParameters); + // Although we are converting mids to rids, we still need a mid. + // The first one will be consistent with trickle ICE candidates. + sdp += "a=mid:" + localMid + "\r\n"; + sdp += directionValue + "\r\n"; + + for (const rid of rids) { + const stringrid = String(rid); // allow integers + const choices = stringrid.split(","); + choices.forEach(choice => { + sdp += "a=rid:" + choice + " recv\r\n"; + }); + } + if (rids.length) { + sdp += "a=simulcast:recv " + rids.join(";") + "\r\n"; + } + + return sdp; +} + +async function doOfferToSendSimulcast(offerer, answerer) { + await offerer.setLocalDescription(); + + // Is this a renegotiation? If so, we cannot remove (or reorder!) any mids, + // even if some rids have been removed or reordered. + let mids = []; + if (answerer.localDescription) { + // Renegotiation. Mids must be the same as before, because renegotiation + // can never remove or reorder mids, nor can it expand the simulcast + // envelope. + mids = [...answerer.localDescription.sdp.matchAll(/a=mid:(.*)/g)].map( + e => e[1] + ); + } else { + // First negotiation; the mids will be exactly the same as the rids + const simulcastAttr = offerer.localDescription.sdp.match( + /a=simulcast:send (.*)/ + ); + if (simulcastAttr) { + mids = simulcastAttr[1].split(";"); + } + } + + const nonSimulcastOffer = ridToMid(offerer.localDescription, mids); + await answerer.setRemoteDescription({ + type: "offer", + sdp: nonSimulcastOffer, + }); +} + +async function doAnswerToRecvSimulcast(offerer, answerer, rids) { + await answerer.setLocalDescription(); + const simulcastAnswer = midToRid( + answerer.localDescription, + offerer.localDescription, + rids + ); + await offerer.setRemoteDescription({ type: "answer", sdp: simulcastAnswer }); +} + +async function doOfferToRecvSimulcast(offerer, answerer, rids) { + await offerer.setLocalDescription(); + const simulcastOffer = midToRid( + offerer.localDescription, + answerer.localDescription, + rids + ); + await answerer.setRemoteDescription({ type: "offer", sdp: simulcastOffer }); +} + +async function doAnswerToSendSimulcast(offerer, answerer) { + await answerer.setLocalDescription(); + + // See which mids the offerer had; it will barf if we remove or reorder them + const mids = [...offerer.localDescription.sdp.matchAll(/a=mid:(.*)/g)].map( + e => e[1] + ); + + const nonSimulcastAnswer = ridToMid(answerer.localDescription, mids); + await offerer.setRemoteDescription({ + type: "answer", + sdp: nonSimulcastAnswer, + }); +} + +async function doOfferToSendSimulcastAndAnswer(offerer, answerer, rids) { + await doOfferToSendSimulcast(offerer, answerer); + await doAnswerToRecvSimulcast(offerer, answerer, rids); +} + +async function doOfferToRecvSimulcastAndAnswer(offerer, answerer, rids) { + await doOfferToRecvSimulcast(offerer, answerer, rids); + await doAnswerToSendSimulcast(offerer, answerer); +} + +// This would be useful for cases other than simulcast, but we do not use it +// anywhere else right now, nor do we have a place for wpt-friendly helpers at +// the moment. +function createPlaybackElement(track) { + const elem = document.createElement(track.kind); + elem.autoplay = true; + elem.srcObject = new MediaStream([track]); + elem.id = track.id; + return elem; +} + +async function getPlaybackWithLoadedMetadata(track) { + const elem = createPlaybackElement(track); + return new Promise(resolve => { + elem.addEventListener("loadedmetadata", () => { + resolve(elem); + }); + }); +} diff --git a/dom/media/webrtc/tests/mochitests/stats.js b/dom/media/webrtc/tests/mochitests/stats.js new file mode 100644 index 0000000000..475f8eeca9 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/stats.js @@ -0,0 +1,1596 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const statsExpectedByType = { + "inbound-rtp": { + expected: [ + "trackIdentifier", + "id", + "timestamp", + "type", + "ssrc", + "mediaType", + "kind", + "codecId", + "packetsReceived", + "packetsLost", + "packetsDiscarded", + "bytesReceived", + "jitter", + "lastPacketReceivedTimestamp", + "headerBytesReceived", + // Always missing from libwebrtc stats + // "estimatedPlayoutTimestamp", + "jitterBufferDelay", + "jitterBufferEmittedCount", + ], + optional: ["remoteId", "nackCount", "qpSum"], + localVideoOnly: [ + "firCount", + "pliCount", + "framesDecoded", + "framesDropped", + "discardedPackets", + "framesPerSecond", + "frameWidth", + "frameHeight", + "framesReceived", + "totalDecodeTime", + "totalInterFrameDelay", + "totalProcessingDelay", + "totalSquaredInterFrameDelay", + ], + localAudioOnly: [ + "totalSamplesReceived", + // libwebrtc doesn't seem to do FEC for video + "fecPacketsReceived", + "fecPacketsDiscarded", + "concealedSamples", + "silentConcealedSamples", + "concealmentEvents", + "insertedSamplesForDeceleration", + "removedSamplesForAcceleration", + "audioLevel", + "totalAudioEnergy", + "totalSamplesDuration", + ], + unimplemented: [ + "mediaTrackId", + "transportId", + "associateStatsId", + "sliCount", + "packetsRepaired", + "fractionLost", + "burstPacketsLost", + "burstLossCount", + "burstDiscardCount", + "gapDiscardRate", + "gapLossRate", + ], + deprecated: ["mozRtt", "isRemote"], + }, + "outbound-rtp": { + expected: [ + "id", + "timestamp", + "type", + "ssrc", + "mediaType", + "kind", + "codecId", + "packetsSent", + "bytesSent", + "remoteId", + "headerBytesSent", + "retransmittedPacketsSent", + "retransmittedBytesSent", + ], + optional: ["nackCount", "qpSum"], + localAudioOnly: [], + localVideoOnly: [ + "framesEncoded", + "firCount", + "pliCount", + "frameWidth", + "frameHeight", + "framesSent", + "hugeFramesSent", + "totalEncodeTime", + "totalEncodedBytesTarget", + ], + unimplemented: ["mediaTrackId", "transportId", "sliCount", "targetBitrate"], + deprecated: ["isRemote"], + }, + "remote-inbound-rtp": { + expected: [ + "id", + "timestamp", + "type", + "ssrc", + "mediaType", + "kind", + "codecId", + "packetsLost", + "jitter", + "localId", + "totalRoundTripTime", + "fractionLost", + "roundTripTimeMeasurements", + ], + optional: ["roundTripTime", "nackCount", "packetsReceived"], + unimplemented: [ + "mediaTrackId", + "transportId", + "packetsDiscarded", + "associateStatsId", + "sliCount", + "packetsRepaired", + "burstPacketsLost", + "burstLossCount", + "burstDiscardCount", + "gapDiscardRate", + "gapLossRate", + ], + deprecated: ["mozRtt", "isRemote"], + }, + "remote-outbound-rtp": { + expected: [ + "id", + "timestamp", + "type", + "ssrc", + "mediaType", + "kind", + "codecId", + "packetsSent", + "bytesSent", + "localId", + "remoteTimestamp", + ], + optional: ["nackCount"], + unimplemented: ["mediaTrackId", "transportId", "sliCount", "targetBitrate"], + deprecated: ["isRemote"], + }, + "media-source": { + expected: ["id", "timestamp", "type", "trackIdentifier", "kind"], + unimplemented: [ + "audioLevel", + "totalAudioEnergy", + "totalSamplesDuration", + "echoReturnLoss", + "echoReturnLossEnhancement", + "droppedSamplesDuration", + "droppedSamplesEvents", + "totalCaptureDelay", + "totalSamplesCaptured", + "width", + "height", + "frames", + "framesPerSecond", + ], + optional: [], + deprecated: [], + }, + csrc: { skip: true }, + codec: { + expected: [ + "timestamp", + "type", + "id", + "payloadType", + "transportId", + "mimeType", + "clockRate", + "sdpFmtpLine", + ], + optional: ["codecType", "channels"], + unimplemented: [], + deprecated: [], + }, + "peer-connection": { skip: true }, + "data-channel": { skip: true }, + track: { skip: true }, + transport: { skip: true }, + "candidate-pair": { + expected: [ + "id", + "timestamp", + "type", + "transportId", + "localCandidateId", + "remoteCandidateId", + "state", + "priority", + "nominated", + "writable", + "readable", + "bytesSent", + "bytesReceived", + "lastPacketSentTimestamp", + "lastPacketReceivedTimestamp", + ], + optional: ["selected"], + unimplemented: [ + "totalRoundTripTime", + "currentRoundTripTime", + "availableOutgoingBitrate", + "availableIncomingBitrate", + "requestsReceived", + "requestsSent", + "responsesReceived", + "responsesSent", + "retransmissionsReceived", + "retransmissionsSent", + "consentRequestsSent", + ], + deprecated: [], + }, + "local-candidate": { + expected: [ + "id", + "timestamp", + "type", + "address", + "protocol", + "port", + "candidateType", + "priority", + ], + optional: ["relayProtocol", "proxied"], + unimplemented: ["networkType", "url", "transportId"], + deprecated: [ + "candidateId", + "portNumber", + "ipAddress", + "componentId", + "mozLocalTransport", + "transport", + ], + }, + "remote-candidate": { + expected: [ + "id", + "timestamp", + "type", + "address", + "protocol", + "port", + "candidateType", + "priority", + ], + optional: ["relayProtocol", "proxied"], + unimplemented: ["networkType", "url", "transportId"], + deprecated: [ + "candidateId", + "portNumber", + "ipAddress", + "componentId", + "mozLocalTransport", + "transport", + ], + }, + certificate: { skip: true }, +}; + +["in", "out"].forEach(pre => { + let s = statsExpectedByType[pre + "bound-rtp"]; + s.optional = [...s.optional, ...s.localVideoOnly, ...s.localAudioOnly]; +}); + +// +// Checks that the fields in a report conform to the expectations in +// statExpectedByType +// +function checkExpectedFields(report) { + report.forEach(stat => { + let expectations = statsExpectedByType[stat.type]; + ok(expectations, "Stats type " + stat.type + " was expected"); + // If the type is not expected or if it is flagged for skipping continue to + // the next + if (!expectations || expectations.skip) { + return; + } + // Check that all required fields exist + expectations.expected.forEach(field => { + ok( + field in stat, + "Expected stat field " + stat.type + "." + field + " exists" + ); + }); + // Check that each field is either expected or optional + let allowed = [...expectations.expected, ...expectations.optional]; + Object.keys(stat).forEach(field => { + ok( + allowed.includes(field), + "Stat field " + + stat.type + + "." + + field + + ` is allowed. ${JSON.stringify(stat)}` + ); + }); + + // + // Ensure that unimplemented fields are not implemented + // note: if a field is implemented it should be moved to expected or + // optional. + // + expectations.unimplemented.forEach(field => { + ok( + !Object.keys(stat).includes(field), + "Unimplemented field " + stat.type + "." + field + " does not exist." + ); + }); + + // + // Ensure that all deprecated fields are not present + // + expectations.deprecated.forEach(field => { + ok( + !Object.keys(stat).includes(field), + "Deprecated field " + stat.type + "." + field + " does not exist." + ); + }); + }); +} + +function pedanticChecks(report) { + // Check that report is only-maplike + [...report.keys()].forEach(key => + is( + report[key], + undefined, + `Report is not dictionary like, it lacks a property for key ${key}` + ) + ); + report.forEach((statObj, mapKey) => { + info(`"${mapKey} = ${JSON.stringify(statObj, null, 2)}`); + }); + // eslint-disable-next-line complexity + report.forEach((statObj, mapKey) => { + let tested = {}; + // Record what fields get tested. + // To access a field foo without marking it as tested use stat.inner.foo + let stat = new Proxy(statObj, { + get(stat, key) { + if (key == "inner") { + return stat; + } + tested[key] = true; + return stat[key]; + }, + }); + + let expectations = statsExpectedByType[stat.type]; + + if (expectations.skip) { + return; + } + + // All stats share the following attributes inherited from RTCStats + is(stat.id, mapKey, stat.type + ".id is the same as the report key."); + + // timestamp + ok(stat.timestamp >= 0, stat.type + ".timestamp is not less than 0"); + // If the timebase for the timestamp is not properly set the timestamp + // will appear relative to the year 1970; Bug 1495446 + const date = new Date(stat.timestamp); + ok( + date.getFullYear() > 1970, + `${stat.type}.timestamp is relative to current time, date=${date}` + ); + // + // RTCStreamStats attributes with common behavior + // + // inbound-rtp, outbound-rtp, remote-inbound-rtp, remote-outbound-rtp + // inherit from RTCStreamStats + if ( + [ + "inbound-rtp", + "outbound-rtp", + "remote-inbound-rtp", + "remote-outbound-rtp", + ].includes(stat.type) + ) { + const isRemote = stat.type.startsWith("remote-"); + // + // Common RTCStreamStats fields + // + + // SSRC + ok(stat.ssrc, stat.type + ".ssrc has a value"); + + // kind + ok( + ["audio", "video"].includes(stat.kind), + stat.type + ".kind is 'audio' or 'video'" + ); + + // mediaType, renamed to kind but remains for backward compability. + ok( + ["audio", "video"].includes(stat.mediaType), + stat.type + ".mediaType is 'audio' or 'video'" + ); + + ok(stat.kind == stat.mediaType, "kind equals legacy mediaType"); + + // codecId + ok(stat.codecId, `${stat.type}.codecId has a value`); + ok(report.has(stat.codecId), `codecId ${stat.codecId} exists in report`); + is( + report.get(stat.codecId).type, + "codec", + `codecId ${stat.codecId} in report is codec type` + ); + is( + report.get(stat.codecId).mimeType.slice(0, 5), + stat.kind, + `codecId ${stat.codecId} in report is for a mimeType of the same ` + + `media type as the referencing rtp stream stat` + ); + + if (isRemote) { + // local id + if (stat.localId) { + ok( + report.has(stat.localId), + `localId ${stat.localId} exists in report.` + ); + is( + report.get(stat.localId).ssrc, + stat.ssrc, + "remote ssrc and local ssrc match." + ); + is( + report.get(stat.localId).remoteId, + stat.id, + "local object has remote object as it's own remote object." + ); + } + } else { + // remote id + if (stat.remoteId) { + ok( + report.has(stat.remoteId), + `remoteId ${stat.remoteId} exists in report.` + ); + is( + report.get(stat.remoteId).ssrc, + stat.ssrc, + "remote ssrc and local ssrc match." + ); + is( + report.get(stat.remoteId).localId, + stat.id, + "remote object has local object as it's own local object." + ); + } + } + + // nackCount + if (stat.nackCount) { + ok( + stat.nackCount >= 0, + `${stat.type}.nackCount is sane (${stat.kind}).` + ); + } + + if (!isRemote && stat.inner.kind == "video") { + // firCount + ok( + stat.firCount >= 0 && stat.firCount < 100, + `${stat.type}.firCount is a sane number for a short ` + + `${stat.kind} test. value=${stat.firCount}` + ); + + // pliCount + ok( + stat.pliCount >= 0 && stat.pliCount < 200, + `${stat.type}.pliCount is a sane number for a short ` + + `${stat.kind} test. value=${stat.pliCount}` + ); + + // qpSum + if (stat.qpSum !== undefined) { + ok( + stat.qpSum > 0, + `${stat.type}.qpSum is at least 0 ` + + `${stat.kind} test. value=${stat.qpSum}` + ); + } + } else { + is( + stat.qpSum, + undefined, + `${stat.type}.qpSum does not exist when stat.kind != video` + ); + } + } + + if (stat.type == "inbound-rtp") { + // + // Required fields + // + + // trackIdentifier + is(typeof stat.trackIdentifier, "string"); + isnot(stat.trackIdentifier, ""); + + // packetsReceived + ok( + stat.packetsReceived >= 0 && stat.packetsReceived < 10 ** 5, + `${stat.type}.packetsReceived is a sane number for a short ` + + `${stat.kind} test. value=${stat.packetsReceived}` + ); + + // packetsDiscarded + ok( + stat.packetsDiscarded >= 0 && stat.packetsDiscarded < 100, + `${stat.type}.packetsDiscarded is sane number for a short test. ` + + `value=${stat.packetsDiscarded}` + ); + // bytesReceived + ok( + stat.bytesReceived >= 0 && stat.bytesReceived < 10 ** 9, // Not a magic number, just a guess + `${stat.type}.bytesReceived is a sane number for a short ` + + `${stat.kind} test. value=${stat.bytesReceived}` + ); + + // packetsLost + ok( + stat.packetsLost < 100, + `${stat.type}.packetsLost is a sane number for a short ` + + `${stat.kind} test. value=${stat.packetsLost}` + ); + + // This should be much lower for audio, TODO: Bug 1330575 + let expectedJitter = stat.kind == "video" ? 0.5 : 1; + // jitter + ok( + stat.jitter < expectedJitter, + `${stat.type}.jitter is sane number for a ${stat.kind} ` + + `local only test. value=${stat.jitter}` + ); + + // lastPacketReceivedTimestamp + ok( + stat.lastPacketReceivedTimestamp !== undefined, + `${stat.type}.lastPacketReceivedTimestamp has a value` + ); + + // headerBytesReceived + ok( + stat.headerBytesReceived >= 0 && stat.headerBytesReceived < 50000, + `${stat.type}.headerBytesReceived is sane for a short test. ` + + `value=${stat.headerBytesReceived}` + ); + + // Always missing from libwebrtc stats + // estimatedPlayoutTimestamp + // ok( + // stat.estimatedPlayoutTimestamp !== undefined, + // `${stat.type}.estimatedPlayoutTimestamp has a value` + // ); + + // jitterBufferEmittedCount + let expectedJitterBufferEmmitedCount = stat.kind == "video" ? 7 : 1000; + ok( + stat.jitterBufferEmittedCount > expectedJitterBufferEmmitedCount, + `${stat.type}.jitterBufferEmittedCount is a sane number for a short ` + + `${stat.kind} test. value=${stat.jitterBufferEmittedCount}` + ); + + // jitterBufferDelay + let avgJitterBufferDelay = + stat.jitterBufferDelay / stat.jitterBufferEmittedCount; + ok( + avgJitterBufferDelay > 0.01 && avgJitterBufferDelay < 10, + `${stat.type}.jitterBufferDelay is a sane number for a short ` + + `${stat.kind} test. value=${stat.jitterBufferDelay}/${stat.jitterBufferEmittedCount}=${avgJitterBufferDelay}` + ); + + // + // Optional fields + // + + // + // Local audio only stats + // + if (stat.inner.kind != "audio") { + expectations.localAudioOnly.forEach(field => { + ok( + stat[field] === undefined, + `${stat.type} does not have field ${field}` + + ` when kind is not 'audio'` + ); + }); + } else { + expectations.localAudioOnly.forEach(field => { + ok( + stat.inner[field] !== undefined, + stat.type + " has field " + field + " when kind is video" + ); + }); + // totalSamplesReceived + ok( + stat.totalSamplesReceived > 1000, + `${stat.type}.totalSamplesReceived is a sane number for a short ` + + `${stat.kind} test. value=${stat.totalSamplesReceived}` + ); + + // fecPacketsReceived + ok( + stat.fecPacketsReceived >= 0 && stat.fecPacketsReceived < 10 ** 5, + `${stat.type}.fecPacketsReceived is a sane number for a short ` + + `${stat.kind} test. value=${stat.fecPacketsReceived}` + ); + + // fecPacketsDiscarded + ok( + stat.fecPacketsDiscarded >= 0 && stat.fecPacketsDiscarded < 100, + `${stat.type}.fecPacketsDiscarded is sane number for a short test. ` + + `value=${stat.fecPacketsDiscarded}` + ); + // concealedSamples + ok( + stat.concealedSamples >= 0 && + stat.concealedSamples <= stat.totalSamplesReceived, + `${stat.type}.concealedSamples is a sane number for a short ` + + `${stat.kind} test. value=${stat.concealedSamples}` + ); + + // silentConcealedSamples + ok( + stat.silentConcealedSamples >= 0 && + stat.silentConcealedSamples <= stat.concealedSamples, + `${stat.type}.silentConcealedSamples is a sane number for a short ` + + `${stat.kind} test. value=${stat.silentConcealedSamples}` + ); + + // concealmentEvents + ok( + stat.concealmentEvents >= 0 && + stat.concealmentEvents <= stat.packetsReceived, + `${stat.type}.concealmentEvents is a sane number for a short ` + + `${stat.kind} test. value=${stat.concealmentEvents}` + ); + + // insertedSamplesForDeceleration + ok( + stat.insertedSamplesForDeceleration >= 0 && + stat.insertedSamplesForDeceleration <= stat.totalSamplesReceived, + `${stat.type}.insertedSamplesForDeceleration is a sane number for a short ` + + `${stat.kind} test. value=${stat.insertedSamplesForDeceleration}` + ); + + // removedSamplesForAcceleration + ok( + stat.removedSamplesForAcceleration >= 0 && + stat.removedSamplesForAcceleration <= stat.totalSamplesReceived, + `${stat.type}.removedSamplesForAcceleration is a sane number for a short ` + + `${stat.kind} test. value=${stat.removedSamplesForAcceleration}` + ); + + // audioLevel + ok( + stat.audioLevel >= 0 && stat.audioLevel <= 128, + `${stat.type}.bytesReceived is a sane number for a short ` + + `${stat.kind} test. value=${stat.audioLevel}` + ); + + // totalAudioEnergy + ok( + stat.totalAudioEnergy >= 0 && stat.totalAudioEnergy <= 128, + `${stat.type}.totalAudioEnergy is a sane number for a short ` + + `${stat.kind} test. value=${stat.totalAudioEnergy}` + ); + + // totalSamplesDuration + ok( + stat.totalSamplesDuration >= 0 && stat.totalSamplesDuration <= 300, + `${stat.type}.totalSamplesDuration is a sane number for a short ` + + `${stat.kind} test. value=${stat.totalSamplesDuration}` + ); + } + + // + // Local video only stats + // + if (stat.inner.kind != "video") { + expectations.localVideoOnly.forEach(field => { + ok( + stat[field] === undefined, + `${stat.type} does not have field ${field}` + + ` when kind is not 'video'` + ); + }); + } else { + expectations.localVideoOnly.forEach(field => { + ok( + stat.inner[field] !== undefined, + stat.type + " has field " + field + " when kind is video" + ); + }); + // discardedPackets + ok( + stat.discardedPackets < 100, + `${stat.type}.discardedPackets is a sane number for a short test. ` + + `value=${stat.discardedPackets}` + ); + // framesPerSecond + ok( + stat.framesPerSecond > 0 && stat.framesPerSecond < 70, + `${stat.type}.framesPerSecond is a sane number for a short ` + + `${stat.kind} test. value=${stat.framesPerSecond}` + ); + + // framesDecoded + ok( + stat.framesDecoded > 0 && stat.framesDecoded < 1000000, + `${stat.type}.framesDecoded is a sane number for a short ` + + `${stat.kind} test. value=${stat.framesDecoded}` + ); + + // framesDropped + ok( + stat.framesDropped >= 0 && stat.framesDropped < 100, + `${stat.type}.framesDropped is a sane number for a short ` + + `${stat.kind} test. value=${stat.framesDropped}` + ); + + // frameWidth + ok( + stat.frameWidth > 0 && stat.frameWidth < 100000, + `${stat.type}.frameWidth is a sane number for a short ` + + `${stat.kind} test. value=${stat.framesSent}` + ); + + // frameHeight + ok( + stat.frameHeight > 0 && stat.frameHeight < 100000, + `${stat.type}.frameHeight is a sane number for a short ` + + `${stat.kind} test. value=${stat.frameHeight}` + ); + + // totalDecodeTime + ok( + stat.totalDecodeTime >= 0 && stat.totalDecodeTime < 300, + `${stat.type}.totalDecodeTime is sane for a short test. ` + + `value=${stat.totalDecodeTime}` + ); + + // totalProcessingDelay + ok( + stat.totalProcessingDelay < 100, + `${stat.type}.totalProcessingDelay is sane number for a short test ` + + `local only test. value=${stat.totalProcessingDelay}` + ); + + // totalInterFrameDelay + ok( + stat.totalInterFrameDelay >= 0 && stat.totalInterFrameDelay < 100, + `${stat.type}.totalInterFrameDelay is sane for a short test. ` + + `value=${stat.totalInterFrameDelay}` + ); + + // totalSquaredInterFrameDelay + ok( + stat.totalSquaredInterFrameDelay >= 0 && + stat.totalSquaredInterFrameDelay < 100, + `${stat.type}.totalSquaredInterFrameDelay is sane for a short test. ` + + `value=${stat.totalSquaredInterFrameDelay}` + ); + + // framesReceived + ok( + stat.framesReceived >= 0 && stat.framesReceived < 100000, + `${stat.type}.framesReceived is a sane number for a short ` + + `${stat.kind} test. value=${stat.framesReceived}` + ); + } + } else if (stat.type == "remote-inbound-rtp") { + // roundTripTime + ok( + stat.roundTripTime >= 0, + `${stat.type}.roundTripTime is sane with` + + `value of: ${stat.roundTripTime} (${stat.kind})` + ); + // + // Required fields + // + + // packetsLost + ok( + stat.packetsLost < 100, + `${stat.type}.packetsLost is a sane number for a short ` + + `${stat.kind} test. value=${stat.packetsLost}` + ); + + // jitter + ok( + stat.jitter >= 0, + `${stat.type}.jitter is sane number (${stat.kind}). ` + + `value=${stat.jitter}` + ); + + // + // Optional fields + // + + // packetsReceived + if (stat.packetsReceived) { + ok( + stat.packetsReceived >= 0 && stat.packetsReceived < 10 ** 5, + `${stat.type}.packetsReceived is a sane number for a short ` + + `${stat.kind} test. value=${stat.packetsReceived}` + ); + } + + // totalRoundTripTime + ok( + stat.totalRoundTripTime < 50000, + `${stat.type}.totalRoundTripTime is a sane number for a short ` + + `${stat.kind} test. value=${stat.totalRoundTripTime}` + ); + + // fractionLost + ok( + stat.fractionLost < 0.2, + `${stat.type}.fractionLost is a sane number for a short ` + + `${stat.kind} test. value=${stat.fractionLost}` + ); + + // roundTripTimeMeasurements + ok( + stat.roundTripTimeMeasurements >= 1 && + stat.roundTripTimeMeasurements < 500, + `${stat.type}.roundTripTimeMeasurements is a sane number for a short ` + + `${stat.kind} test. value=${stat.roundTripTimeMeasurements}` + ); + } else if (stat.type == "outbound-rtp") { + // + // Required fields + // + + // packetsSent + ok( + stat.packetsSent > 0 && stat.packetsSent < 10000, + `${stat.type}.packetsSent is a sane number for a short ` + + `${stat.kind} test. value=${stat.packetsSent}` + ); + + // bytesSent + const audio1Min = 16000 * 60; // 128kbps + const video1Min = 250000 * 60; // 2Mbps + ok( + stat.bytesSent > 0 && + stat.bytesSent < (stat.kind == "video" ? video1Min : audio1Min), + `${stat.type}.bytesSent is a sane number for a short ` + + `${stat.kind} test. value=${stat.bytesSent}` + ); + + // headerBytesSent + ok( + stat.headerBytesSent > 0 && + stat.headerBytesSent < (stat.kind == "video" ? video1Min : audio1Min), + `${stat.type}.headerBytesSent is a sane number for a short ` + + `${stat.kind} test. value=${stat.headerBytesSent}` + ); + + // retransmittedPacketsSent + ok( + stat.retransmittedPacketsSent >= 0 && + stat.retransmittedPacketsSent < + (stat.kind == "video" ? video1Min : audio1Min), + `${stat.type}.retransmittedPacketsSent is a sane number for a short ` + + `${stat.kind} test. value=${stat.retransmittedPacketsSent}` + ); + + // retransmittedBytesSent + ok( + stat.retransmittedBytesSent >= 0 && + stat.retransmittedBytesSent < + (stat.kind == "video" ? video1Min : audio1Min), + `${stat.type}.retransmittedBytesSent is a sane number for a short ` + + `${stat.kind} test. value=${stat.retransmittedBytesSent}` + ); + + // + // Optional fields + // + + // qpSum + // This is supported for all of our vpx codecs (on the encode side, see + // bug 1519590) + const mimeType = report.get(stat.codecId).mimeType; + if (mimeType.includes("VP")) { + ok( + stat.qpSum >= 0, + `${stat.type}.qpSum is a sane number (${stat.kind}) ` + + `for ${report.get(stat.codecId).mimeType}. value=${stat.qpSum}` + ); + } else if (mimeType.includes("H264")) { + // OpenH264 encoder records QP so we check for either condition. + if (!stat.qpSum && !("qpSum" in stat)) { + ok( + !stat.qpSum && !("qpSum" in stat), + `${stat.type}.qpSum absent for ${report.get(stat.codecId).mimeType}` + ); + } else { + ok( + stat.qpSum >= 0, + `${stat.type}.qpSum is a sane number (${stat.kind}) ` + + `for ${report.get(stat.codecId).mimeType}. value=${stat.qpSum}` + ); + } + } else { + ok( + !stat.qpSum && !("qpSum" in stat), + `${stat.type}.qpSum absent for ${report.get(stat.codecId).mimeType}` + ); + } + + // + // Local video only stats + // + if (stat.inner.kind != "video") { + expectations.localVideoOnly.forEach(field => { + ok( + stat[field] === undefined, + `${stat.type} does not have field ` + + `${field} when kind is not 'video'` + ); + }); + } else { + expectations.localVideoOnly.forEach(field => { + ok( + stat.inner[field] !== undefined, + `${stat.type} has field ` + + `${field} when kind is video and isRemote is false` + ); + }); + + // framesEncoded + ok( + stat.framesEncoded >= 0 && stat.framesEncoded < 100000, + `${stat.type}.framesEncoded is a sane number for a short ` + + `${stat.kind} test. value=${stat.framesEncoded}` + ); + + // frameWidth + ok( + stat.frameWidth >= 0 && stat.frameWidth < 100000, + `${stat.type}.frameWidth is a sane number for a short ` + + `${stat.kind} test. value=${stat.frameWidth}` + ); + + // frameHeight + ok( + stat.frameHeight >= 0 && stat.frameHeight < 100000, + `${stat.type}.frameHeight is a sane number for a short ` + + `${stat.kind} test. value=${stat.frameHeight}` + ); + + // framesSent + ok( + stat.framesSent >= 0 && stat.framesSent < 100000, + `${stat.type}.framesSent is a sane number for a short ` + + `${stat.kind} test. value=${stat.framesSent}` + ); + + // hugeFramesSent + ok( + stat.hugeFramesSent >= 0 && stat.hugeFramesSent < 100000, + `${stat.type}.hugeFramesSent is a sane number for a short ` + + `${stat.kind} test. value=${stat.hugeFramesSent}` + ); + + // totalEncodeTime + ok( + stat.totalEncodeTime >= 0, + `${stat.type}.totalEncodeTime is a sane number for a short ` + + `${stat.kind} test. value=${stat.totalEncodeTime}` + ); + + // totalEncodedBytesTarget + ok( + stat.totalEncodedBytesTarget > 1000, + `${stat.type}.totalEncodedBytesTarget is a sane number for a short ` + + `${stat.kind} test. value=${stat.totalEncodedBytesTarget}` + ); + } + } else if (stat.type == "remote-outbound-rtp") { + // + // Required fields + // + + // packetsSent + ok( + stat.packetsSent > 0 && stat.packetsSent < 10000, + `${stat.type}.packetsSent is a sane number for a short ` + + `${stat.kind} test. value=${stat.packetsSent}` + ); + + // bytesSent + const audio1Min = 16000 * 60; // 128kbps + const video1Min = 250000 * 60; // 2Mbps + ok( + stat.bytesSent > 0 && + stat.bytesSent < (stat.kind == "video" ? video1Min : audio1Min), + `${stat.type}.bytesSent is a sane number for a short ` + + `${stat.kind} test. value=${stat.bytesSent}` + ); + + ok( + stat.remoteTimestamp !== undefined, + `${stat.type}.remoteTimestamp ` + `is not undefined (${stat.kind})` + ); + const ageSeconds = (stat.timestamp - stat.remoteTimestamp) / 1000; + // remoteTimestamp is exact (so it can be mapped to a packet), whereas + // timestamp has reduced precision. It is possible that + // remoteTimestamp occurs a millisecond into the future from + // timestamp. We also subtract half a millisecond when reducing + // precision on libwebrtc timestamps, to counteract the potential + // rounding up that libwebrtc may do since it tends to round its + // internal timestamps to whole milliseconds. In the worst case + // remoteTimestamp may therefore occur 2 milliseconds ahead of + // timestamp. + ok( + ageSeconds >= -0.002 && ageSeconds < 30, + `${stat.type}.remoteTimestamp is on the same timeline as ` + + `${stat.type}.timestamp, and no older than 30 seconds. ` + + `difference=${ageSeconds}s` + ); + } else if (stat.type == "media-source") { + // trackIdentifier + is(typeof stat.trackIdentifier, "string"); + isnot(stat.trackIdentifier, ""); + + // kind + is(typeof stat.kind, "string"); + ok(stat.kind == "audio" || stat.kind == "video"); + } else if (stat.type == "codec") { + // + // Required fields + // + + // mimeType & payloadType + switch (stat.mimeType) { + case "audio/opus": + is(stat.payloadType, 109, "codec.payloadType for opus"); + break; + case "video/VP8": + is(stat.payloadType, 120, "codec.payloadType for VP8"); + break; + case "video/VP9": + is(stat.payloadType, 121, "codec.payloadType for VP9"); + break; + case "video/H264": + ok( + stat.payloadType == 97 || stat.payloadType == 126, + `codec.payloadType for H264 was ${stat.payloadType}, exp. 97 or 126` + ); + break; + default: + ok( + false, + `Unexpected codec.mimeType ${stat.mimeType} for payloadType ` + + `${stat.payloadType}` + ); + break; + } + + // transportId + // (no transport stats yet) + ok(stat.transportId, "codec.transportId is set"); + + // clockRate + if (stat.mimeType.startsWith("audio")) { + is(stat.clockRate, 48000, "codec.clockRate for audio/opus"); + } else if (stat.mimeType.startsWith("video")) { + is(stat.clockRate, 90000, "codec.clockRate for video"); + } + + // sdpFmtpLine + // (not technically mandated by spec, but expected here) + ok(stat.sdpFmtpLine, "codec.sdpFmtpLine is set"); + const opusParams = [ + "maxplaybackrate", + "maxaveragebitrate", + "usedtx", + "stereo", + "useinbandfec", + "cbr", + "ptime", + "minptime", + "maxptime", + ]; + const vpxParams = ["max-fs", "max-fr"]; + const h264Params = [ + "packetization-mode", + "level-asymmetry-allowed", + "profile-level-id", + "max-fs", + "max-cpb", + "max-dpb", + "max-br", + "max-mbps", + ]; + for (const param of stat.sdpFmtpLine.split(";")) { + const [key, value] = param.split("="); + if (stat.payloadType == 109) { + ok( + opusParams.includes(key), + `codec.sdpFmtpLine param ${key}=${value} for opus` + ); + } else if (stat.payloadType == 120 || stat.payloadType == 121) { + ok( + vpxParams.includes(key), + `codec.sdpFmtpLine param ${key}=${value} for VPx` + ); + } else if (stat.payloadType == 97 || stat.payloadType == 126) { + ok( + h264Params.includes(key), + `codec.sdpFmtpLine param ${key}=${value} for H264` + ); + if (key == "packetization-mode") { + if (stat.payloadType == 97) { + is(value, "0", "codec.sdpFmtpLine: H264 (97) packetization-mode"); + } else if (stat.payloadType == 126) { + is( + value, + "1", + "codec.sdpFmtpLine: H264 (126) packetization-mode" + ); + } + } + if (key == "profile-level-id") { + is(value, "42e01f", "codec.sdpFmtpLine: H264 profile-level-id"); + } + } + } + + // + // Optional fields + // + + // codecType + ok( + !Object.keys(stat).includes("codecType") || + stat.codecType == "encode" || + stat.codecType == "decode", + "codec.codecType (${codec.codecType}) is an expected value or absent" + ); + let numRecvStreams = 0; + let numSendStreams = 0; + const counts = { + "inbound-rtp": 0, + "outbound-rtp": 0, + "remote-inbound-rtp": 0, + "remote-outbound-rtp": 0, + }; + const [kind] = stat.mimeType.split("/"); + report.forEach(other => { + if (other.type == "inbound-rtp" && other.kind == kind) { + numRecvStreams += 1; + } else if (other.type == "outbound-rtp" && other.kind == kind) { + numSendStreams += 1; + } + if (other.codecId == stat.id) { + counts[other.type] += 1; + } + }); + const expectedCounts = { + encode: { + "inbound-rtp": 0, + "outbound-rtp": numSendStreams, + "remote-inbound-rtp": numSendStreams, + "remote-outbound-rtp": 0, + }, + decode: { + "inbound-rtp": numRecvStreams, + "outbound-rtp": 0, + "remote-inbound-rtp": 0, + "remote-outbound-rtp": numRecvStreams, + }, + absent: { + "inbound-rtp": numRecvStreams, + "outbound-rtp": numSendStreams, + "remote-inbound-rtp": numSendStreams, + "remote-outbound-rtp": numRecvStreams, + }, + }; + // Note that the logic above assumes at most one sender and at most one + // receiver was used to generate this stats report. If more senders or + // receivers are present, they'd be referring to not only this codec stat, + // skewing `numSendStreams` and `numRecvStreams` above. + // This could be fixed when we support `senderId` and `receiverId` in + // RTCOutboundRtpStreamStats and RTCInboundRtpStreamStats respectively. + for (const [key, value] of Object.entries(counts)) { + is( + value, + expectedCounts[stat.codecType || "absent"][key], + `codec.codecType ${stat.codecType || "absent"} ref from ${key} stat` + ); + } + + // channels + if (stat.mimeType.startsWith("audio")) { + ok(stat.channels, "codec.channels should exist for audio"); + if (stat.channels) { + if (stat.sdpFmtpLine.includes("stereo=1")) { + is(stat.channels, 2, "codec.channels for stereo audio"); + } else { + is(stat.channels, 1, "codec.channels for mono audio"); + } + } + } else { + ok(!stat.channels, "codec.channels should not exist for video"); + } + } else if (stat.type == "candidate-pair") { + info("candidate-pair is: " + JSON.stringify(stat)); + // + // Required fields + // + + // transportId + ok( + stat.transportId, + `${stat.type}.transportId has a value. value=` + + `${stat.transportId} (${stat.kind})` + ); + + // localCandidateId + ok( + stat.localCandidateId, + `${stat.type}.localCandidateId has a value. value=` + + `${stat.localCandidateId} (${stat.kind})` + ); + + // remoteCandidateId + ok( + stat.remoteCandidateId, + `${stat.type}.remoteCandidateId has a value. value=` + + `${stat.remoteCandidateId} (${stat.kind})` + ); + + // priority + ok( + stat.priority, + `${stat.type}.priority has a value. value=` + + `${stat.priority} (${stat.kind})` + ); + + // readable + ok( + stat.readable, + `${stat.type}.readable is true. value=${stat.readable} ` + + `(${stat.kind})` + ); + + // writable + ok( + stat.writable, + `${stat.type}.writable is true. value=${stat.writable} ` + + `(${stat.kind})` + ); + + // state + if ( + stat.state == "succeeded" && + stat.selected !== undefined && + stat.selected + ) { + info("candidate-pair state is succeeded and selected is true"); + // nominated + ok( + stat.nominated, + `${stat.type}.nominated is true. value=${stat.nominated} ` + + `(${stat.kind})` + ); + + // bytesSent + ok( + stat.bytesSent > 1000, + `${stat.type}.bytesSent is a sane number (>1,000) for a short ` + + `${stat.kind} test. value=${stat.bytesSent}` + ); + + // bytesReceived + ok( + stat.bytesReceived > 500, + `${stat.type}.bytesReceived is a sane number (>500) for a short ` + + `${stat.kind} test. value=${stat.bytesReceived}` + ); + + // lastPacketSentTimestamp + ok( + stat.lastPacketSentTimestamp, + `${stat.type}.lastPacketSentTimestamp has a value. value=` + + `${stat.lastPacketSentTimestamp} (${stat.kind})` + ); + + // lastPacketReceivedTimestamp + ok( + stat.lastPacketReceivedTimestamp, + `${stat.type}.lastPacketReceivedTimestamp has a value. value=` + + `${stat.lastPacketReceivedTimestamp} (${stat.kind})` + ); + } else { + info("candidate-pair is _not_ both state == succeeded and selected"); + // nominated + ok( + stat.nominated !== undefined, + `${stat.type}.nominated exists. value=${stat.nominated} ` + + `(${stat.kind})` + ); + ok( + stat.bytesSent !== undefined, + `${stat.type}.bytesSent exists. value=${stat.bytesSent} ` + + `(${stat.kind})` + ); + ok( + stat.bytesReceived !== undefined, + `${stat.type}.bytesReceived exists. value=${stat.bytesReceived} ` + + `(${stat.kind})` + ); + ok( + stat.lastPacketSentTimestamp !== undefined, + `${stat.type}.lastPacketSentTimestamp exists. value=` + + `${stat.lastPacketSentTimestamp} (${stat.kind})` + ); + ok( + stat.lastPacketReceivedTimestamp !== undefined, + `${stat.type}.lastPacketReceivedTimestamp exists. value=` + + `${stat.lastPacketReceivedTimestamp} (${stat.kind})` + ); + } + + // + // Optional fields + // + // selected + ok( + stat.selected === undefined || + (stat.state == "succeeded" && stat.selected) || + !stat.selected, + `${stat.type}.selected is undefined, true when state is succeeded, ` + + `or false. value=${stat.selected} (${stat.kind})` + ); + } else if ( + stat.type == "local-candidate" || + stat.type == "remote-candidate" + ) { + info(`candidate is ${JSON.stringify(stat)}`); + + // address + ok( + stat.address, + `${stat.type} has address. value=${stat.address} ` + `(${stat.kind})` + ); + + // protocol + ok( + stat.protocol, + `${stat.type} has protocol. value=${stat.protocol} ` + `(${stat.kind})` + ); + + // port + ok( + stat.port >= 0, + `${stat.type} has port >= 0. value=${stat.port} ` + `(${stat.kind})` + ); + ok( + stat.port <= 65535, + `${stat.type} has port <= 65535. value=${stat.port} ` + `(${stat.kind})` + ); + + // candidateType + ok( + stat.candidateType, + `${stat.type} has candidateType. value=${stat.candidateType} ` + + `(${stat.kind})` + ); + + // priority + ok( + stat.priority > 0 && stat.priority < 2 ** 32 - 1, + `${stat.type} has priority between 1 and 2^32 - 1 inc. ` + + `value=${stat.priority} (${stat.kind})` + ); + + // relayProtocol + if (stat.type == "local-candidate" && stat.candidateType == "relay") { + ok( + stat.relayProtocol, + `relay ${stat.type} has relayProtocol. value=${stat.relayProtocol} ` + + `(${stat.kind})` + ); + } else { + is( + stat.relayProtocol, + undefined, + `relayProtocol is undefined for candidates that are not relay and ` + + `local. value=${stat.relayProtocol} (${stat.kind})` + ); + } + + // proxied + if (stat.proxied) { + ok( + stat.proxied == "proxied" || stat.proxied == "non-proxied", + `${stat.type} has proxied. value=${stat.proxied} (${stat.kind})` + ); + } + } + + // + // Ensure everything was tested + // + [...expectations.expected, ...expectations.optional].forEach(field => { + ok( + Object.keys(tested).includes(field), + `${stat.type}.${field} was tested.` + ); + }); + }); +} + +function dumpStats(stats) { + const dict = {}; + for (const [k, v] of stats.entries()) { + dict[k] = v; + } + info(`Got stats: ${JSON.stringify(dict)}`); +} + +async function waitForSyncedRtcp(pc) { + // Ensures that RTCP is present + let ensureSyncedRtcp = async () => { + let report = await pc.getStats(); + for (const v of report.values()) { + if (v.type.endsWith("bound-rtp") && !(v.remoteId || v.localId)) { + info(`${v.id} is missing remoteId or localId: ${JSON.stringify(v)}`); + return null; + } + if (v.type == "remote-inbound-rtp" && v.roundTripTime === undefined) { + info(`${v.id} is missing roundTripTime: ${JSON.stringify(v)}`); + return null; + } + } + return report; + }; + // Returns true if there is proof in aStats of rtcp flow for all remote stats + // objects, compared to baseStats. + const hasAllRtcpUpdated = (baseStats, stats) => { + let hasRtcpStats = false; + for (const v of stats.values()) { + if (v.type == "remote-outbound-rtp") { + hasRtcpStats = true; + if (!v.remoteTimestamp) { + // `remoteTimestamp` is 0 or not present. + return false; + } + if (v.remoteTimestamp <= baseStats.get(v.id)?.remoteTimestamp) { + // `remoteTimestamp` has not advanced further than the base stats, + // i.e., no new sender report has been received. + return false; + } + } else if (v.type == "remote-inbound-rtp") { + hasRtcpStats = true; + // The ideal thing here would be to check `reportsReceived`, but it's + // not yet implemented. + if (!v.packetsReceived) { + // `packetsReceived` is 0 or not present. + return false; + } + if (v.packetsReceived <= baseStats.get(v.id)?.packetsReceived) { + // `packetsReceived` has not advanced further than the base stats, + // i.e., no new receiver report has been received. + return false; + } + } + } + return hasRtcpStats; + }; + let attempts = 0; + const baseStats = await pc.getStats(); + // Time-units are MS + const waitPeriod = 100; + const maxTime = 20000; + for (let totalTime = maxTime; totalTime > 0; totalTime -= waitPeriod) { + try { + let syncedStats = await ensureSyncedRtcp(); + if (syncedStats && hasAllRtcpUpdated(baseStats, syncedStats)) { + dumpStats(syncedStats); + return syncedStats; + } + } catch (e) { + info(e); + info(e.stack); + throw e; + } + attempts += 1; + info(`waitForSyncedRtcp: no sync on attempt ${attempts}, retrying.`); + await wait(waitPeriod); + } + throw Error( + "Waiting for synced RTCP timed out after at least " + maxTime + "ms" + ); +} + +function checkSenderStats(senderStats, streamCount) { + const outboundRtpReports = []; + const remoteInboundRtpReports = []; + for (const v of senderStats.values()) { + if (v.type == "outbound-rtp") { + outboundRtpReports.push(v); + } else if (v.type == "remote-inbound-rtp") { + remoteInboundRtpReports.push(v); + } + } + is( + outboundRtpReports.length, + streamCount, + `Sender with ${streamCount} simulcast streams has ${streamCount} outbound-rtp reports` + ); + is( + remoteInboundRtpReports.length, + streamCount, + `Sender with ${streamCount} simulcast streams has ${streamCount} remote-inbound-rtp reports` + ); + for (const outboundRtpReport of outboundRtpReports) { + is( + outboundRtpReports.filter(r => r.ssrc == outboundRtpReport.ssrc).length, + 1, + "Simulcast send track SSRCs are distinct" + ); + const remoteReports = remoteInboundRtpReports.filter( + r => r.id == outboundRtpReport.remoteId + ); + is( + remoteReports.length, + 1, + "Simulcast send tracks have exactly one remote counterpart" + ); + const remoteInboundRtpReport = remoteReports[0]; + is( + outboundRtpReport.ssrc, + remoteInboundRtpReport.ssrc, + "SSRC matches for outbound-rtp and remote-inbound-rtp" + ); + } +} + +function PC_LOCAL_TEST_LOCAL_STATS(test) { + return waitForSyncedRtcp(test.pcLocal._pc).then(stats => { + checkExpectedFields(stats); + pedanticChecks(stats); + return Promise.all([ + test.pcLocal._pc.getSenders().map(async s => { + checkSenderStats( + await s.getStats(), + Math.max(1, s.getParameters()?.encodings?.length ?? 0) + ); + }), + ]); + }); +} + +function PC_REMOTE_TEST_REMOTE_STATS(test) { + return waitForSyncedRtcp(test.pcRemote._pc).then(stats => { + checkExpectedFields(stats); + pedanticChecks(stats); + return Promise.all([ + test.pcRemote._pc.getSenders().map(async s => { + checkSenderStats( + await s.getStats(), + s.track ? Math.max(1, s.getParameters()?.encodings?.length ?? 0) : 0 + ); + }), + ]); + }); +} diff --git a/dom/media/webrtc/tests/mochitests/templates.js b/dom/media/webrtc/tests/mochitests/templates.js new file mode 100644 index 0000000000..6b7750fd2c --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/templates.js @@ -0,0 +1,615 @@ +/** + * Default list of commands to execute for a PeerConnection test. + */ + +const STABLE = "stable"; +const HAVE_LOCAL_OFFER = "have-local-offer"; +const HAVE_REMOTE_OFFER = "have-remote-offer"; +const CLOSED = "closed"; + +const ICE_NEW = "new"; +const GATH_NEW = "new"; +const GATH_GATH = "gathering"; +const GATH_COMPLETE = "complete"; + +function deltaSeconds(date1, date2) { + return (date2.getTime() - date1.getTime()) / 1000; +} + +function dumpSdp(test) { + if (typeof test._local_offer !== "undefined") { + dump("ERROR: SDP offer: " + test._local_offer.sdp.replace(/[\r]/g, "")); + } + if (typeof test._remote_answer !== "undefined") { + dump("ERROR: SDP answer: " + test._remote_answer.sdp.replace(/[\r]/g, "")); + } + + if ( + test.pcLocal && + typeof test.pcLocal._local_ice_candidates !== "undefined" + ) { + dump( + "pcLocal._local_ice_candidates: " + + JSON.stringify(test.pcLocal._local_ice_candidates) + + "\n" + ); + dump( + "pcLocal._remote_ice_candidates: " + + JSON.stringify(test.pcLocal._remote_ice_candidates) + + "\n" + ); + dump( + "pcLocal._ice_candidates_to_add: " + + JSON.stringify(test.pcLocal._ice_candidates_to_add) + + "\n" + ); + } + if ( + test.pcRemote && + typeof test.pcRemote._local_ice_candidates !== "undefined" + ) { + dump( + "pcRemote._local_ice_candidates: " + + JSON.stringify(test.pcRemote._local_ice_candidates) + + "\n" + ); + dump( + "pcRemote._remote_ice_candidates: " + + JSON.stringify(test.pcRemote._remote_ice_candidates) + + "\n" + ); + dump( + "pcRemote._ice_candidates_to_add: " + + JSON.stringify(test.pcRemote._ice_candidates_to_add) + + "\n" + ); + } + + if (test.pcLocal && typeof test.pcLocal.iceConnectionLog !== "undefined") { + dump( + "pcLocal ICE connection state log: " + + test.pcLocal.iceConnectionLog + + "\n" + ); + } + if (test.pcRemote && typeof test.pcRemote.iceConnectionLog !== "undefined") { + dump( + "pcRemote ICE connection state log: " + + test.pcRemote.iceConnectionLog + + "\n" + ); + } + + if ( + test.pcLocal && + test.pcRemote && + typeof test.pcLocal.setRemoteDescDate !== "undefined" && + typeof test.pcRemote.setLocalDescDate !== "undefined" + ) { + var delta = deltaSeconds( + test.pcLocal.setRemoteDescDate, + test.pcRemote.setLocalDescDate + ); + dump( + "Delay between pcLocal.setRemote <-> pcRemote.setLocal: " + delta + "\n" + ); + } + if ( + test.pcLocal && + test.pcRemote && + typeof test.pcLocal.setRemoteDescDate !== "undefined" && + typeof test.pcLocal.setRemoteDescStableEventDate !== "undefined" + ) { + var delta = deltaSeconds( + test.pcLocal.setRemoteDescDate, + test.pcLocal.setRemoteDescStableEventDate + ); + dump( + "Delay between pcLocal.setRemote <-> pcLocal.signalingStateStable: " + + delta + + "\n" + ); + } + if ( + test.pcLocal && + test.pcRemote && + typeof test.pcRemote.setLocalDescDate !== "undefined" && + typeof test.pcRemote.setLocalDescStableEventDate !== "undefined" + ) { + var delta = deltaSeconds( + test.pcRemote.setLocalDescDate, + test.pcRemote.setLocalDescStableEventDate + ); + dump( + "Delay between pcRemote.setLocal <-> pcRemote.signalingStateStable: " + + delta + + "\n" + ); + } +} + +// We need to verify that at least one candidate has been (or will be) gathered. +function waitForAnIceCandidate(pc) { + return new Promise(resolve => { + if (!pc.localRequiresTrickleIce || pc._local_ice_candidates.length) { + resolve(); + } else { + // In some circumstances, especially when both PCs are on the same + // browser, even though we are connected, the connection can be + // established without receiving a single candidate from one or other + // peer. So we wait for at least one... + pc._pc.addEventListener("icecandidate", resolve); + } + }).then(() => { + ok( + pc._local_ice_candidates.length, + pc + " received local trickle ICE candidates" + ); + isnot( + pc._pc.iceGatheringState, + GATH_NEW, + pc + " ICE gathering state is not 'new'" + ); + }); +} + +async function checkTrackStats(pc, track, outbound) { + const audio = track.kind == "audio"; + const msg = + `${pc} stats ${outbound ? "outbound " : "inbound "}` + + `${audio ? "audio" : "video"} rtp track id ${track.id}`; + const stats = await pc.getStats(track); + ok( + pc.hasStat(stats, { + type: outbound ? "outbound-rtp" : "inbound-rtp", + kind: audio ? "audio" : "video", + }), + `${msg} - found expected stats` + ); + ok( + !pc.hasStat(stats, { + type: outbound ? "inbound-rtp" : "outbound-rtp", + }), + `${msg} - did not find extra stats with wrong direction` + ); + ok( + !pc.hasStat(stats, { + kind: audio ? "video" : "audio", + }), + `${msg} - did not find extra stats with wrong media type` + ); +} + +function checkAllTrackStats(pc) { + return Promise.all([ + ...pc + .getExpectedActiveReceivers() + .map(({ track }) => checkTrackStats(pc, track, false)), + ...pc + .getExpectedSenders() + .map(({ track }) => checkTrackStats(pc, track, true)), + ]); +} + +// Commands run once at the beginning of each test, even when performing a +// renegotiation test. +var commandsPeerConnectionInitial = [ + function PC_LOCAL_SETUP_ICE_LOGGER(test) { + test.pcLocal.logIceConnectionState(); + }, + + function PC_REMOTE_SETUP_ICE_LOGGER(test) { + test.pcRemote.logIceConnectionState(); + }, + + function PC_LOCAL_SETUP_SIGNALING_LOGGER(test) { + test.pcLocal.logSignalingState(); + }, + + function PC_REMOTE_SETUP_SIGNALING_LOGGER(test) { + test.pcRemote.logSignalingState(); + }, + + function PC_LOCAL_SETUP_TRACK_HANDLER(test) { + test.pcLocal.setupTrackEventHandler(); + }, + + function PC_REMOTE_SETUP_TRACK_HANDLER(test) { + test.pcRemote.setupTrackEventHandler(); + }, + + function PC_LOCAL_CHECK_INITIAL_SIGNALINGSTATE(test) { + is( + test.pcLocal.signalingState, + STABLE, + "Initial local signalingState is 'stable'" + ); + }, + + function PC_REMOTE_CHECK_INITIAL_SIGNALINGSTATE(test) { + is( + test.pcRemote.signalingState, + STABLE, + "Initial remote signalingState is 'stable'" + ); + }, + + function PC_LOCAL_CHECK_INITIAL_ICE_STATE(test) { + is( + test.pcLocal.iceConnectionState, + ICE_NEW, + "Initial local ICE connection state is 'new'" + ); + }, + + function PC_REMOTE_CHECK_INITIAL_ICE_STATE(test) { + is( + test.pcRemote.iceConnectionState, + ICE_NEW, + "Initial remote ICE connection state is 'new'" + ); + }, + + function PC_LOCAL_CHECK_INITIAL_CAN_TRICKLE_SYNC(test) { + is( + test.pcLocal._pc.canTrickleIceCandidates, + null, + "Local trickle status should start out unknown" + ); + }, + + function PC_REMOTE_CHECK_INITIAL_CAN_TRICKLE_SYNC(test) { + is( + test.pcRemote._pc.canTrickleIceCandidates, + null, + "Remote trickle status should start out unknown" + ); + }, +]; + +var commandsGetUserMedia = [ + function PC_LOCAL_GUM(test) { + return test.pcLocal.getAllUserMediaAndAddStreams(test.pcLocal.constraints); + }, + + function PC_REMOTE_GUM(test) { + return test.pcRemote.getAllUserMediaAndAddStreams( + test.pcRemote.constraints + ); + }, +]; + +var commandsPeerConnectionOfferAnswer = [ + function PC_LOCAL_SETUP_ICE_HANDLER(test) { + test.pcLocal.setupIceCandidateHandler(test); + }, + + function PC_REMOTE_SETUP_ICE_HANDLER(test) { + test.pcRemote.setupIceCandidateHandler(test); + }, + + function PC_LOCAL_CREATE_OFFER(test) { + return test.createOffer(test.pcLocal).then(offer => { + is( + test.pcLocal.signalingState, + STABLE, + "Local create offer does not change signaling state" + ); + }); + }, + + function PC_LOCAL_SET_LOCAL_DESCRIPTION(test) { + return test + .setLocalDescription(test.pcLocal, test.originalOffer, HAVE_LOCAL_OFFER) + .then(() => { + is( + test.pcLocal.signalingState, + HAVE_LOCAL_OFFER, + "signalingState after local setLocalDescription is 'have-local-offer'" + ); + }); + }, + + function PC_REMOTE_GET_OFFER(test) { + test._local_offer = test.originalOffer; + test._offer_constraints = test.pcLocal.constraints; + test._offer_options = test.pcLocal.offerOptions; + return Promise.resolve(); + }, + + function PC_REMOTE_SET_REMOTE_DESCRIPTION(test) { + return test + .setRemoteDescription(test.pcRemote, test._local_offer, HAVE_REMOTE_OFFER) + .then(() => { + is( + test.pcRemote.signalingState, + HAVE_REMOTE_OFFER, + "signalingState after remote setRemoteDescription is 'have-remote-offer'" + ); + }); + }, + + function PC_REMOTE_CHECK_CAN_TRICKLE_SYNC(test) { + is( + test.pcRemote._pc.canTrickleIceCandidates, + true, + "Remote thinks that local can trickle" + ); + }, + + function PC_LOCAL_SANE_LOCAL_SDP(test) { + test.pcLocal.localRequiresTrickleIce = sdputils.verifySdp( + test._local_offer, + "offer", + test._offer_constraints, + test._offer_options, + test.testOptions + ); + }, + + function PC_REMOTE_SANE_REMOTE_SDP(test) { + test.pcRemote.remoteRequiresTrickleIce = sdputils.verifySdp( + test._local_offer, + "offer", + test._offer_constraints, + test._offer_options, + test.testOptions + ); + }, + + function PC_REMOTE_CREATE_ANSWER(test) { + return test.createAnswer(test.pcRemote).then(answer => { + is( + test.pcRemote.signalingState, + HAVE_REMOTE_OFFER, + "Remote createAnswer does not change signaling state" + ); + }); + }, + + function PC_REMOTE_SET_LOCAL_DESCRIPTION(test) { + return test + .setLocalDescription(test.pcRemote, test.originalAnswer, STABLE) + .then(() => { + is( + test.pcRemote.signalingState, + STABLE, + "signalingState after remote setLocalDescription is 'stable'" + ); + }); + }, + + function PC_LOCAL_GET_ANSWER(test) { + test._remote_answer = test.originalAnswer; + test._answer_constraints = test.pcRemote.constraints; + return Promise.resolve(); + }, + + function PC_LOCAL_SET_REMOTE_DESCRIPTION(test) { + return test + .setRemoteDescription(test.pcLocal, test._remote_answer, STABLE) + .then(() => { + is( + test.pcLocal.signalingState, + STABLE, + "signalingState after local setRemoteDescription is 'stable'" + ); + }); + }, + + function PC_REMOTE_SANE_LOCAL_SDP(test) { + test.pcRemote.localRequiresTrickleIce = sdputils.verifySdp( + test._remote_answer, + "answer", + test._offer_constraints, + test._offer_options, + test.testOptions + ); + }, + function PC_LOCAL_SANE_REMOTE_SDP(test) { + test.pcLocal.remoteRequiresTrickleIce = sdputils.verifySdp( + test._remote_answer, + "answer", + test._offer_constraints, + test._offer_options, + test.testOptions + ); + }, + + function PC_LOCAL_CHECK_CAN_TRICKLE_SYNC(test) { + is( + test.pcLocal._pc.canTrickleIceCandidates, + true, + "Local thinks that remote can trickle" + ); + }, + + function PC_LOCAL_WAIT_FOR_ICE_CONNECTED(test) { + return test.pcLocal.waitForIceConnected().then(() => { + info( + test.pcLocal + + ": ICE connection state log: " + + test.pcLocal.iceConnectionLog + ); + }); + }, + + function PC_REMOTE_WAIT_FOR_ICE_CONNECTED(test) { + return test.pcRemote.waitForIceConnected().then(() => { + info( + test.pcRemote + + ": ICE connection state log: " + + test.pcRemote.iceConnectionLog + ); + }); + }, + + function PC_LOCAL_VERIFY_ICE_GATHERING(test) { + return waitForAnIceCandidate(test.pcLocal); + }, + + function PC_REMOTE_VERIFY_ICE_GATHERING(test) { + return waitForAnIceCandidate(test.pcRemote); + }, + + function PC_LOCAL_WAIT_FOR_MEDIA_FLOW(test) { + return test.pcLocal.waitForMediaFlow(); + }, + + function PC_REMOTE_WAIT_FOR_MEDIA_FLOW(test) { + return test.pcRemote.waitForMediaFlow(); + }, + + function PC_LOCAL_CHECK_STATS(test) { + return test.pcLocal.getStats().then(stats => { + test.pcLocal.checkStats(stats); + }); + }, + + function PC_REMOTE_CHECK_STATS(test) { + return test.pcRemote.getStats().then(stats => { + test.pcRemote.checkStats(stats); + }); + }, + + function PC_LOCAL_CHECK_ICE_CONNECTION_TYPE(test) { + return test.pcLocal.getStats().then(stats => { + test.pcLocal.checkStatsIceConnectionType( + stats, + test.testOptions.expectedLocalCandidateType + ); + }); + }, + + function PC_REMOTE_CHECK_ICE_CONNECTION_TYPE(test) { + return test.pcRemote.getStats().then(stats => { + test.pcRemote.checkStatsIceConnectionType( + stats, + test.testOptions.expectedRemoteCandidateType + ); + }); + }, + + function PC_LOCAL_CHECK_ICE_CONNECTIONS(test) { + return test.pcLocal.getStats().then(stats => { + test.pcLocal.checkStatsIceConnections(stats, test.testOptions); + }); + }, + + function PC_REMOTE_CHECK_ICE_CONNECTIONS(test) { + return test.pcRemote.getStats().then(stats => { + test.pcRemote.checkStatsIceConnections(stats, test.testOptions); + }); + }, + + function PC_LOCAL_CHECK_MSID(test) { + return test.pcLocal.checkLocalMsids(); + }, + function PC_REMOTE_CHECK_MSID(test) { + return test.pcRemote.checkLocalMsids(); + }, + + function PC_LOCAL_CHECK_TRACK_STATS(test) { + return checkAllTrackStats(test.pcLocal); + }, + function PC_REMOTE_CHECK_TRACK_STATS(test) { + return checkAllTrackStats(test.pcRemote); + }, + function PC_LOCAL_VERIFY_SDP_AFTER_END_OF_TRICKLE(test) { + if (test.pcLocal.endOfTrickleSdp) { + /* In case the endOfTrickleSdp promise is resolved already it will win the + * race because it gets evaluated first. But if endOfTrickleSdp is still + * pending the rejection will win the race. */ + return Promise.race([ + test.pcLocal.endOfTrickleSdp, + Promise.reject("No SDP"), + ]).then( + sdp => + sdputils.checkSdpAfterEndOfTrickle( + sdp, + test.testOptions, + test.pcLocal.label + ), + () => + info( + "pcLocal: Gathering is not complete yet, skipping post-gathering SDP check" + ) + ); + } + }, + function PC_REMOTE_VERIFY_SDP_AFTER_END_OF_TRICKLE(test) { + if (test.pcRemote.endOfTrickleSdp) { + /* In case the endOfTrickleSdp promise is resolved already it will win the + * race because it gets evaluated first. But if endOfTrickleSdp is still + * pending the rejection will win the race. */ + return Promise.race([ + test.pcRemote.endOfTrickleSdp, + Promise.reject("No SDP"), + ]).then( + sdp => + sdputils.checkSdpAfterEndOfTrickle( + sdp, + test.testOptions, + test.pcRemote.label + ), + () => + info( + "pcRemote: Gathering is not complete yet, skipping post-gathering SDP check" + ) + ); + } + }, +]; + +function PC_LOCAL_REMOVE_ALL_BUT_H264_FROM_OFFER(test) { + isnot( + test.originalOffer.sdp.search("H264/90000"), + -1, + "H.264 should be present in the SDP offer" + ); + test.originalOffer.sdp = sdputils.removeCodec( + sdputils.removeCodec( + sdputils.removeCodec(test.originalOffer.sdp, 120), + 121, + 97 + ) + ); + info("Updated H264 only offer: " + JSON.stringify(test.originalOffer)); +} + +function PC_LOCAL_REMOVE_BUNDLE_FROM_OFFER(test) { + test.originalOffer.sdp = sdputils.removeBundle(test.originalOffer.sdp); + info("Updated no bundle offer: " + JSON.stringify(test.originalOffer)); +} + +function PC_LOCAL_REMOVE_RTCPMUX_FROM_OFFER(test) { + test.originalOffer.sdp = sdputils.removeRtcpMux(test.originalOffer.sdp); + info("Updated no RTCP-Mux offer: " + JSON.stringify(test.originalOffer)); +} + +function PC_LOCAL_REMOVE_SSRC_FROM_OFFER(test) { + test.originalOffer.sdp = sdputils.removeSSRCs(test.originalOffer.sdp); + info("Updated no SSRCs offer: " + JSON.stringify(test.originalOffer)); +} + +function PC_REMOTE_REMOVE_SSRC_FROM_ANSWER(test) { + test.originalAnswer.sdp = sdputils.removeSSRCs(test.originalAnswer.sdp); + info("Updated no SSRCs answer: " + JSON.stringify(test.originalAnswer)); +} + +var addRenegotiation = (chain, commands, checks) => { + chain.append(commands); + chain.append(commandsPeerConnectionOfferAnswer); + if (checks) { + chain.append(checks); + } +}; + +var addRenegotiationAnswerer = (chain, commands, checks) => { + chain.append(function SWAP_PC_LOCAL_PC_REMOTE(test) { + var temp = test.pcLocal; + test.pcLocal = test.pcRemote; + test.pcRemote = temp; + }); + addRenegotiation(chain, commands, checks); +}; diff --git a/dom/media/webrtc/tests/mochitests/test_1488832.html b/dom/media/webrtc/tests/mochitests/test_1488832.html new file mode 100644 index 0000000000..8798994b24 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_1488832.html @@ -0,0 +1,37 @@ + + + + + + + + + + diff --git a/dom/media/webrtc/tests/mochitests/test_1717318.html b/dom/media/webrtc/tests/mochitests/test_1717318.html new file mode 100644 index 0000000000..425bd29e7e --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_1717318.html @@ -0,0 +1,26 @@ + + + + PC construct with no global object (bug 1717318) + + + + + + + + diff --git a/dom/media/webrtc/tests/mochitests/test_a_noOp.html b/dom/media/webrtc/tests/mochitests/test_a_noOp.html new file mode 100644 index 0000000000..971f5d7666 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_a_noOp.html @@ -0,0 +1,32 @@ + + + + + Test for Bug 1264772 + + + + + +Mozilla Bug 1264772 +

+ +
+
+
+ diff --git a/dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudio.html b/dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudio.html new file mode 100644 index 0000000000..06ca9562ad --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudio.html @@ -0,0 +1,25 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudioVideo.html b/dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudioVideo.html new file mode 100644 index 0000000000..ea534ca2e7 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudioVideo.html @@ -0,0 +1,26 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudioVideoCombined.html b/dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudioVideoCombined.html new file mode 100644 index 0000000000..d5409986ec --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudioVideoCombined.html @@ -0,0 +1,26 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudioVideoNoBundle.html b/dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudioVideoNoBundle.html new file mode 100644 index 0000000000..7dc22d86ad --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudioVideoNoBundle.html @@ -0,0 +1,27 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_dataChannel_basicDataOnly.html b/dom/media/webrtc/tests/mochitests/test_dataChannel_basicDataOnly.html new file mode 100644 index 0000000000..98e72f7a21 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_dataChannel_basicDataOnly.html @@ -0,0 +1,24 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_dataChannel_basicVideo.html b/dom/media/webrtc/tests/mochitests/test_dataChannel_basicVideo.html new file mode 100644 index 0000000000..90f2d7caff --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_dataChannel_basicVideo.html @@ -0,0 +1,25 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_dataChannel_bug1013809.html b/dom/media/webrtc/tests/mochitests/test_dataChannel_bug1013809.html new file mode 100644 index 0000000000..e36caebab4 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_dataChannel_bug1013809.html @@ -0,0 +1,27 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_dataChannel_dataOnlyBufferedAmountLow.html b/dom/media/webrtc/tests/mochitests/test_dataChannel_dataOnlyBufferedAmountLow.html new file mode 100644 index 0000000000..26767e0865 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_dataChannel_dataOnlyBufferedAmountLow.html @@ -0,0 +1,25 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_dataChannel_dtlsVersions.html b/dom/media/webrtc/tests/mochitests/test_dataChannel_dtlsVersions.html new file mode 100644 index 0000000000..6f0cbc5d3d --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_dataChannel_dtlsVersions.html @@ -0,0 +1,38 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_dataChannel_hostnameObfuscation.html b/dom/media/webrtc/tests/mochitests/test_dataChannel_hostnameObfuscation.html new file mode 100644 index 0000000000..d0790fb9c9 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_dataChannel_hostnameObfuscation.html @@ -0,0 +1,59 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_dataChannel_noOffer.html b/dom/media/webrtc/tests/mochitests/test_dataChannel_noOffer.html new file mode 100644 index 0000000000..a6e9fa5214 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_dataChannel_noOffer.html @@ -0,0 +1,33 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_dataChannel_stats.html b/dom/media/webrtc/tests/mochitests/test_dataChannel_stats.html new file mode 100644 index 0000000000..4498e2d23a --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_dataChannel_stats.html @@ -0,0 +1,50 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_defaultAudioConstraints.html b/dom/media/webrtc/tests/mochitests/test_defaultAudioConstraints.html new file mode 100644 index 0000000000..8e0db48fff --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_defaultAudioConstraints.html @@ -0,0 +1,80 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_enumerateDevices.html b/dom/media/webrtc/tests/mochitests/test_enumerateDevices.html new file mode 100644 index 0000000000..48bec0006a --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_enumerateDevices.html @@ -0,0 +1,141 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_enumerateDevices_getUserMediaFake.html b/dom/media/webrtc/tests/mochitests/test_enumerateDevices_getUserMediaFake.html new file mode 100644 index 0000000000..7952bcba1b --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_enumerateDevices_getUserMediaFake.html @@ -0,0 +1,63 @@ + + + + + + + + + + diff --git a/dom/media/webrtc/tests/mochitests/test_enumerateDevices_iframe.html b/dom/media/webrtc/tests/mochitests/test_enumerateDevices_iframe.html new file mode 100644 index 0000000000..beea3a4f97 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_enumerateDevices_iframe.html @@ -0,0 +1,28 @@ + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_enumerateDevices_iframe_pre_gum.html b/dom/media/webrtc/tests/mochitests/test_enumerateDevices_iframe_pre_gum.html new file mode 100644 index 0000000000..f2dc2d1f65 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_enumerateDevices_iframe_pre_gum.html @@ -0,0 +1,22 @@ + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_enumerateDevices_legacy.html b/dom/media/webrtc/tests/mochitests/test_enumerateDevices_legacy.html new file mode 100644 index 0000000000..c599f2b599 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_enumerateDevices_legacy.html @@ -0,0 +1,147 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_enumerateDevices_navigation.html b/dom/media/webrtc/tests/mochitests/test_enumerateDevices_navigation.html new file mode 100644 index 0000000000..bf7650223f --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_enumerateDevices_navigation.html @@ -0,0 +1,54 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_fingerprinting_resistance.html b/dom/media/webrtc/tests/mochitests/test_fingerprinting_resistance.html new file mode 100644 index 0000000000..7e9cd5a219 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_fingerprinting_resistance.html @@ -0,0 +1,112 @@ + + + + + + + + + + diff --git a/dom/media/webrtc/tests/mochitests/test_forceSampleRate.html b/dom/media/webrtc/tests/mochitests/test_forceSampleRate.html new file mode 100644 index 0000000000..c5a9820aaa --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_forceSampleRate.html @@ -0,0 +1,23 @@ + + + + Test the pref media.cubeb.force_sample_rate + + + + + + + + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_GC_MediaStream.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_GC_MediaStream.html new file mode 100644 index 0000000000..5aa0e64947 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_GC_MediaStream.html @@ -0,0 +1,59 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_active_autoplay.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_active_autoplay.html new file mode 100644 index 0000000000..c1a39cdd4c --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_active_autoplay.html @@ -0,0 +1,61 @@ + + + + + + +
+
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_addTrackRemoveTrack.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_addTrackRemoveTrack.html new file mode 100644 index 0000000000..27dad2519f --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_addTrackRemoveTrack.html @@ -0,0 +1,169 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_addtrack_removetrack_events.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_addtrack_removetrack_events.html new file mode 100644 index 0000000000..833653ebb2 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_addtrack_removetrack_events.html @@ -0,0 +1,110 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_audioCapture.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_audioCapture.html new file mode 100644 index 0000000000..2cc649a321 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_audioCapture.html @@ -0,0 +1,104 @@ + + + + Test AudioCapture + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_audioConstraints.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_audioConstraints.html new file mode 100644 index 0000000000..162e83063a --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_audioConstraints.html @@ -0,0 +1,93 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_audioConstraints_concurrentIframes.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_audioConstraints_concurrentIframes.html new file mode 100644 index 0000000000..d07dbc41f1 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_audioConstraints_concurrentIframes.html @@ -0,0 +1,157 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_audioConstraints_concurrentStreams.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_audioConstraints_concurrentStreams.html new file mode 100644 index 0000000000..f5b5e784ea --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_audioConstraints_concurrentStreams.html @@ -0,0 +1,123 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicAudio.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicAudio.html new file mode 100644 index 0000000000..b4775b4244 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicAudio.html @@ -0,0 +1,27 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicAudio_loopback.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicAudio_loopback.html new file mode 100644 index 0000000000..10bf669c00 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicAudio_loopback.html @@ -0,0 +1,99 @@ + + + + + + +
+
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicScreenshare.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicScreenshare.html new file mode 100644 index 0000000000..cc73de77da --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicScreenshare.html @@ -0,0 +1,260 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicTabshare.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicTabshare.html new file mode 100644 index 0000000000..635cf387d4 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicTabshare.html @@ -0,0 +1,67 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicVideo.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicVideo.html new file mode 100644 index 0000000000..786d9f2e4b --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicVideo.html @@ -0,0 +1,30 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicVideoAudio.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicVideoAudio.html new file mode 100644 index 0000000000..5218bf7301 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicVideoAudio.html @@ -0,0 +1,30 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicVideo_playAfterLoadedmetadata.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicVideo_playAfterLoadedmetadata.html new file mode 100644 index 0000000000..fbab1b4357 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicVideo_playAfterLoadedmetadata.html @@ -0,0 +1,42 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicWindowshare.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicWindowshare.html new file mode 100644 index 0000000000..7b27944bdc --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicWindowshare.html @@ -0,0 +1,39 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_bug1223696.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_bug1223696.html new file mode 100644 index 0000000000..6af7b69d70 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_bug1223696.html @@ -0,0 +1,54 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_callbacks.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_callbacks.html new file mode 100644 index 0000000000..14c6cc7e7f --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_callbacks.html @@ -0,0 +1,35 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_constraints.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_constraints.html new file mode 100644 index 0000000000..d6439ce9d6 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_constraints.html @@ -0,0 +1,166 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_cubebDisabled.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_cubebDisabled.html new file mode 100644 index 0000000000..54142aeb77 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_cubebDisabled.html @@ -0,0 +1,42 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_cubebDisabledFakeStreams.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_cubebDisabledFakeStreams.html new file mode 100644 index 0000000000..f8150cc4c1 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_cubebDisabledFakeStreams.html @@ -0,0 +1,43 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_getTrackById.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_getTrackById.html new file mode 100644 index 0000000000..161bf631e3 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_getTrackById.html @@ -0,0 +1,50 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_gumWithinGum.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_gumWithinGum.html new file mode 100644 index 0000000000..86a7aa5606 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_gumWithinGum.html @@ -0,0 +1,38 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_loadedmetadata.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_loadedmetadata.html new file mode 100644 index 0000000000..d6efac4650 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_loadedmetadata.html @@ -0,0 +1,39 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaElementCapture_audio.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaElementCapture_audio.html new file mode 100644 index 0000000000..3b9e00896c --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaElementCapture_audio.html @@ -0,0 +1,116 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaElementCapture_tracks.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaElementCapture_tracks.html new file mode 100644 index 0000000000..a747e75de9 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaElementCapture_tracks.html @@ -0,0 +1,179 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaElementCapture_video.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaElementCapture_video.html new file mode 100644 index 0000000000..d177e93bfb --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaElementCapture_video.html @@ -0,0 +1,91 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaStreamClone.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaStreamClone.html new file mode 100644 index 0000000000..029ce77dd0 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaStreamClone.html @@ -0,0 +1,258 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaStreamConstructors.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaStreamConstructors.html new file mode 100644 index 0000000000..4ea6e3f444 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaStreamConstructors.html @@ -0,0 +1,171 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaStreamTrackClone.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaStreamTrackClone.html new file mode 100644 index 0000000000..e5e0764427 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaStreamTrackClone.html @@ -0,0 +1,170 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_nonDefaultRate.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_nonDefaultRate.html new file mode 100644 index 0000000000..8a6ac8c62b --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_nonDefaultRate.html @@ -0,0 +1,37 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_peerIdentity.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_peerIdentity.html new file mode 100644 index 0000000000..c4dfb9acb8 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_peerIdentity.html @@ -0,0 +1,51 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_permission.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_permission.html new file mode 100644 index 0000000000..cd02c7326c --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_permission.html @@ -0,0 +1,104 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_permission_iframe.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_permission_iframe.html new file mode 100644 index 0000000000..732c2cf98c --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_permission_iframe.html @@ -0,0 +1,30 @@ + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_playAudioTwice.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_playAudioTwice.html new file mode 100644 index 0000000000..30d168bf38 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_playAudioTwice.html @@ -0,0 +1,25 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_playVideoAudioTwice.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_playVideoAudioTwice.html new file mode 100644 index 0000000000..7b5e6effd1 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_playVideoAudioTwice.html @@ -0,0 +1,26 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_playVideoTwice.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_playVideoTwice.html new file mode 100644 index 0000000000..2890f45eab --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_playVideoTwice.html @@ -0,0 +1,26 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_scarySources.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_scarySources.html new file mode 100644 index 0000000000..782110823e --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_scarySources.html @@ -0,0 +1,51 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_spinEventLoop.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_spinEventLoop.html new file mode 100644 index 0000000000..ae691785f5 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_spinEventLoop.html @@ -0,0 +1,28 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_trackCloneCleanup.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_trackCloneCleanup.html new file mode 100644 index 0000000000..60077ec73b --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_trackCloneCleanup.html @@ -0,0 +1,32 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_trackEnded.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_trackEnded.html new file mode 100644 index 0000000000..b275f4555f --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_trackEnded.html @@ -0,0 +1,68 @@ + + + + + + +
+
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_groupId.html b/dom/media/webrtc/tests/mochitests/test_groupId.html new file mode 100644 index 0000000000..f2aefe5e80 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_groupId.html @@ -0,0 +1,53 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_multi_mics.html b/dom/media/webrtc/tests/mochitests/test_multi_mics.html new file mode 100644 index 0000000000..95bcbfd3e4 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_multi_mics.html @@ -0,0 +1,66 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_ondevicechange.html b/dom/media/webrtc/tests/mochitests/test_ondevicechange.html new file mode 100644 index 0000000000..4358d9d748 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_ondevicechange.html @@ -0,0 +1,180 @@ + + + + + + + + + + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_addAudioTrackToExistingVideoStream.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_addAudioTrackToExistingVideoStream.html new file mode 100644 index 0000000000..b09d7ffeb5 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_addAudioTrackToExistingVideoStream.html @@ -0,0 +1,55 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_addDataChannel.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_addDataChannel.html new file mode 100644 index 0000000000..c7536214e5 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_addDataChannel.html @@ -0,0 +1,33 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_addDataChannelNoBundle.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_addDataChannelNoBundle.html new file mode 100644 index 0000000000..6ad754336c --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_addDataChannelNoBundle.html @@ -0,0 +1,44 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondAudioStream.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondAudioStream.html new file mode 100644 index 0000000000..61a0250887 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondAudioStream.html @@ -0,0 +1,45 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondAudioStreamNoBundle.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondAudioStreamNoBundle.html new file mode 100644 index 0000000000..32d0564717 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondAudioStreamNoBundle.html @@ -0,0 +1,53 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondVideoStream.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondVideoStream.html new file mode 100644 index 0000000000..1565958d01 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondVideoStream.html @@ -0,0 +1,53 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondVideoStreamNoBundle.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondVideoStreamNoBundle.html new file mode 100644 index 0000000000..2857100998 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondVideoStreamNoBundle.html @@ -0,0 +1,60 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_addtrack_removetrack_events.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_addtrack_removetrack_events.html new file mode 100644 index 0000000000..ff9ca9a772 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_addtrack_removetrack_events.html @@ -0,0 +1,75 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_answererAddSecondAudioStream.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_answererAddSecondAudioStream.html new file mode 100644 index 0000000000..d9b01bf722 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_answererAddSecondAudioStream.html @@ -0,0 +1,32 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_audioChannels.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_audioChannels.html new file mode 100644 index 0000000000..f6e77f8271 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_audioChannels.html @@ -0,0 +1,102 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_audioCodecs.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_audioCodecs.html new file mode 100644 index 0000000000..8874436e3b --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_audioCodecs.html @@ -0,0 +1,81 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_audioContributingSources.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_audioContributingSources.html new file mode 100644 index 0000000000..333b40a888 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_audioContributingSources.html @@ -0,0 +1,144 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_audioRenegotiationInactiveAnswer.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_audioRenegotiationInactiveAnswer.html new file mode 100644 index 0000000000..6d3a23b57a --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_audioRenegotiationInactiveAnswer.html @@ -0,0 +1,69 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_audioSynchronizationSources.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_audioSynchronizationSources.html new file mode 100644 index 0000000000..32603b2e40 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_audioSynchronizationSources.html @@ -0,0 +1,95 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_audioSynchronizationSourcesUnidirectional.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_audioSynchronizationSourcesUnidirectional.html new file mode 100644 index 0000000000..6d66614e91 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_audioSynchronizationSourcesUnidirectional.html @@ -0,0 +1,54 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudio.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudio.html new file mode 100644 index 0000000000..5fd10a67f9 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudio.html @@ -0,0 +1,25 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioDynamicPtMissingRtpmap.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioDynamicPtMissingRtpmap.html new file mode 100644 index 0000000000..a076bf80f1 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioDynamicPtMissingRtpmap.html @@ -0,0 +1,36 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelay.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelay.html new file mode 100644 index 0000000000..180abc075a --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelay.html @@ -0,0 +1,47 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTCP.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTCP.html new file mode 100644 index 0000000000..7bb51764bd --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTCP.html @@ -0,0 +1,42 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTCPWithStun300.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTCPWithStun300.html new file mode 100644 index 0000000000..43ea6aaea7 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTCPWithStun300.html @@ -0,0 +1,54 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTLS.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTLS.html new file mode 100644 index 0000000000..7446401f87 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTLS.html @@ -0,0 +1,41 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayWithStun300.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayWithStun300.html new file mode 100644 index 0000000000..286e67bc2f --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayWithStun300.html @@ -0,0 +1,53 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATSrflx.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATSrflx.html new file mode 100644 index 0000000000..78fa8bcb2c --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATSrflx.html @@ -0,0 +1,44 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNoisyUDPBlock.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNoisyUDPBlock.html new file mode 100644 index 0000000000..297121cd94 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNoisyUDPBlock.html @@ -0,0 +1,41 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioPcmaPcmuOnly.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioPcmaPcmuOnly.html new file mode 100644 index 0000000000..f0fe721b8e --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioPcmaPcmuOnly.html @@ -0,0 +1,39 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioRelayPolicy.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioRelayPolicy.html new file mode 100644 index 0000000000..ced57ff8a3 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioRelayPolicy.html @@ -0,0 +1,83 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioRequireEOC.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioRequireEOC.html new file mode 100644 index 0000000000..afad4550d4 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioRequireEOC.html @@ -0,0 +1,35 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVerifyRtpHeaderExtensions.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVerifyRtpHeaderExtensions.html new file mode 100644 index 0000000000..f28a990bd2 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVerifyRtpHeaderExtensions.html @@ -0,0 +1,63 @@ + + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideo.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideo.html new file mode 100644 index 0000000000..c2c2d43f09 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideo.html @@ -0,0 +1,24 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoCombined.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoCombined.html new file mode 100644 index 0000000000..02a561f9b8 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoCombined.html @@ -0,0 +1,24 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoNoBundle.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoNoBundle.html new file mode 100644 index 0000000000..cae7f6617f --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoNoBundle.html @@ -0,0 +1,25 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoNoBundleNoRtcpMux.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoNoBundleNoRtcpMux.html new file mode 100644 index 0000000000..49b0136752 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoNoBundleNoRtcpMux.html @@ -0,0 +1,39 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoNoRtcpMux.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoNoRtcpMux.html new file mode 100644 index 0000000000..48524604ba --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoNoRtcpMux.html @@ -0,0 +1,38 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoTransceivers.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoTransceivers.html new file mode 100644 index 0000000000..181d089d26 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoTransceivers.html @@ -0,0 +1,31 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoVerifyExtmap.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoVerifyExtmap.html new file mode 100644 index 0000000000..e3da00bfa5 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoVerifyExtmap.html @@ -0,0 +1,97 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoVerifyExtmapSendonly.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoVerifyExtmapSendonly.html new file mode 100644 index 0000000000..6cbc9e4c00 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoVerifyExtmapSendonly.html @@ -0,0 +1,97 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoVerifyTooLongMidFails.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoVerifyTooLongMidFails.html new file mode 100644 index 0000000000..70d27b48c6 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoVerifyTooLongMidFails.html @@ -0,0 +1,47 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudio_forced_higher_rate.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudio_forced_higher_rate.html new file mode 100644 index 0000000000..95bfb06514 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudio_forced_higher_rate.html @@ -0,0 +1,19 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudio_forced_lower_rate.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudio_forced_lower_rate.html new file mode 100644 index 0000000000..aab9778971 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudio_forced_lower_rate.html @@ -0,0 +1,19 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicH264Video.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicH264Video.html new file mode 100644 index 0000000000..072c35da39 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicH264Video.html @@ -0,0 +1,26 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicScreenshare.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicScreenshare.html new file mode 100644 index 0000000000..93148ac5fd --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicScreenshare.html @@ -0,0 +1,56 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicVideo.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicVideo.html new file mode 100644 index 0000000000..4a0655d696 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicVideo.html @@ -0,0 +1,23 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicVideoVerifyRtpHeaderExtensions.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicVideoVerifyRtpHeaderExtensions.html new file mode 100644 index 0000000000..7874e52a10 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicVideoVerifyRtpHeaderExtensions.html @@ -0,0 +1,82 @@ + + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicWindowshare.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicWindowshare.html new file mode 100644 index 0000000000..1cfb0797db --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicWindowshare.html @@ -0,0 +1,25 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_bug1013809.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug1013809.html new file mode 100644 index 0000000000..a8c7004793 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug1013809.html @@ -0,0 +1,25 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_bug1042791.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug1042791.html new file mode 100644 index 0000000000..a84dcf9d09 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug1042791.html @@ -0,0 +1,36 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_bug1227781.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug1227781.html new file mode 100644 index 0000000000..41e4aec457 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug1227781.html @@ -0,0 +1,37 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_bug1512281.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug1512281.html new file mode 100644 index 0000000000..e6451becea --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug1512281.html @@ -0,0 +1,47 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_bug1773067.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug1773067.html new file mode 100644 index 0000000000..9e6d79a107 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug1773067.html @@ -0,0 +1,32 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_bug822674.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug822674.html new file mode 100644 index 0000000000..fceb2c2a1d --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug822674.html @@ -0,0 +1,26 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_bug825703.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug825703.html new file mode 100644 index 0000000000..5cd168af8a --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug825703.html @@ -0,0 +1,140 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_bug827843.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug827843.html new file mode 100644 index 0000000000..06cfde9e5d --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug827843.html @@ -0,0 +1,50 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_bug834153.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug834153.html new file mode 100644 index 0000000000..6d8ca2a7ce --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug834153.html @@ -0,0 +1,36 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_callbacks.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_callbacks.html new file mode 100644 index 0000000000..4c890e4400 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_callbacks.html @@ -0,0 +1,86 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_captureStream_canvas_2d.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_captureStream_canvas_2d.html new file mode 100644 index 0000000000..db3a735008 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_captureStream_canvas_2d.html @@ -0,0 +1,81 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_captureStream_canvas_2d_noSSRC.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_captureStream_canvas_2d_noSSRC.html new file mode 100644 index 0000000000..e33a7e8886 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_captureStream_canvas_2d_noSSRC.html @@ -0,0 +1,83 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_captureStream_canvas_webgl.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_captureStream_canvas_webgl.html new file mode 100644 index 0000000000..167379fb37 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_captureStream_canvas_webgl.html @@ -0,0 +1,130 @@ + + + + + + + + +
+
+
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_capturedVideo.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_capturedVideo.html new file mode 100644 index 0000000000..f6f48ba429 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_capturedVideo.html @@ -0,0 +1,81 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_certificates.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_certificates.html new file mode 100644 index 0000000000..561f285f60 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_certificates.html @@ -0,0 +1,185 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_checkPacketDumpHook.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_checkPacketDumpHook.html new file mode 100644 index 0000000000..248e102dd2 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_checkPacketDumpHook.html @@ -0,0 +1,107 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_close.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_close.html new file mode 100644 index 0000000000..3edf677203 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_close.html @@ -0,0 +1,134 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_closeDuringIce.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_closeDuringIce.html new file mode 100644 index 0000000000..db3a2922d5 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_closeDuringIce.html @@ -0,0 +1,79 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_codecNegotiationFailure.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_codecNegotiationFailure.html new file mode 100644 index 0000000000..819e13fe1b --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_codecNegotiationFailure.html @@ -0,0 +1,111 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_constructedStream.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_constructedStream.html new file mode 100644 index 0000000000..8431b7534e --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_constructedStream.html @@ -0,0 +1,67 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_disabledVideoPreNegotiation.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_disabledVideoPreNegotiation.html new file mode 100644 index 0000000000..4c06de792e --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_disabledVideoPreNegotiation.html @@ -0,0 +1,45 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_encodingsNegotiation.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_encodingsNegotiation.html new file mode 100644 index 0000000000..f46d7eb0d2 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_encodingsNegotiation.html @@ -0,0 +1,85 @@ + + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_errorCallbacks.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_errorCallbacks.html new file mode 100644 index 0000000000..851a256509 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_errorCallbacks.html @@ -0,0 +1,55 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_extmapRenegotiation.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_extmapRenegotiation.html new file mode 100644 index 0000000000..78c6bb986c --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_extmapRenegotiation.html @@ -0,0 +1,325 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_forwarding_basicAudioVideoCombined.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_forwarding_basicAudioVideoCombined.html new file mode 100644 index 0000000000..84b53a123b --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_forwarding_basicAudioVideoCombined.html @@ -0,0 +1,41 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_gatherWithSetConfiguration.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_gatherWithSetConfiguration.html new file mode 100644 index 0000000000..6710e628aa --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_gatherWithSetConfiguration.html @@ -0,0 +1,450 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_gatherWithStun300.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_gatherWithStun300.html new file mode 100644 index 0000000000..50bc4a6553 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_gatherWithStun300.html @@ -0,0 +1,269 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_gatherWithStun300IPv6.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_gatherWithStun300IPv6.html new file mode 100644 index 0000000000..16f8f39978 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_gatherWithStun300IPv6.html @@ -0,0 +1,283 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_glean.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_glean.html new file mode 100644 index 0000000000..d5d6026ee5 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_glean.html @@ -0,0 +1,488 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_iceFailure.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_iceFailure.html new file mode 100644 index 0000000000..1b82473997 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_iceFailure.html @@ -0,0 +1,84 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_insertDTMF.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_insertDTMF.html new file mode 100644 index 0000000000..ca8b866a6d --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_insertDTMF.html @@ -0,0 +1,76 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_localReofferRollback.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_localReofferRollback.html new file mode 100644 index 0000000000..16406ece6e --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_localReofferRollback.html @@ -0,0 +1,44 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_localRollback.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_localRollback.html new file mode 100644 index 0000000000..5bdc8cc029 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_localRollback.html @@ -0,0 +1,47 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_maxFsConstraint.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_maxFsConstraint.html new file mode 100644 index 0000000000..a2f2555020 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_maxFsConstraint.html @@ -0,0 +1,112 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_multiple_captureStream_canvas_2d.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_multiple_captureStream_canvas_2d.html new file mode 100644 index 0000000000..9ad25e7852 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_multiple_captureStream_canvas_2d.html @@ -0,0 +1,115 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_noTrickleAnswer.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_noTrickleAnswer.html new file mode 100644 index 0000000000..7e3fd78430 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_noTrickleAnswer.html @@ -0,0 +1,25 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_noTrickleOffer.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_noTrickleOffer.html new file mode 100644 index 0000000000..12b2a95596 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_noTrickleOffer.html @@ -0,0 +1,25 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_noTrickleOfferAnswer.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_noTrickleOfferAnswer.html new file mode 100644 index 0000000000..554750e975 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_noTrickleOfferAnswer.html @@ -0,0 +1,26 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_nonDefaultRate.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_nonDefaultRate.html new file mode 100644 index 0000000000..ad9414cef2 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_nonDefaultRate.html @@ -0,0 +1,200 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_offerRequiresReceiveAudio.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_offerRequiresReceiveAudio.html new file mode 100644 index 0000000000..1f936714f1 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_offerRequiresReceiveAudio.html @@ -0,0 +1,23 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_offerRequiresReceiveVideo.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_offerRequiresReceiveVideo.html new file mode 100644 index 0000000000..c5afbb5c1f --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_offerRequiresReceiveVideo.html @@ -0,0 +1,23 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_offerRequiresReceiveVideoAudio.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_offerRequiresReceiveVideoAudio.html new file mode 100644 index 0000000000..d7bc29c6d3 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_offerRequiresReceiveVideoAudio.html @@ -0,0 +1,23 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_portRestrictions.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_portRestrictions.html new file mode 100644 index 0000000000..7cd695ff54 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_portRestrictions.html @@ -0,0 +1,63 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_promiseSendOnly.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_promiseSendOnly.html new file mode 100644 index 0000000000..a3fbb5753c --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_promiseSendOnly.html @@ -0,0 +1,61 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_recordReceiveTrack.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_recordReceiveTrack.html new file mode 100644 index 0000000000..d5cb91b048 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_recordReceiveTrack.html @@ -0,0 +1,101 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_relayOnly.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_relayOnly.html new file mode 100644 index 0000000000..3b07783c04 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_relayOnly.html @@ -0,0 +1,60 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_remoteReofferRollback.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_remoteReofferRollback.html new file mode 100644 index 0000000000..80aa30beaa --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_remoteReofferRollback.html @@ -0,0 +1,50 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_remoteRollback.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_remoteRollback.html new file mode 100644 index 0000000000..827646b0de --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_remoteRollback.html @@ -0,0 +1,51 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_removeAudioTrack.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_removeAudioTrack.html new file mode 100644 index 0000000000..e1e99b38c9 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_removeAudioTrack.html @@ -0,0 +1,57 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddAudioTrack.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddAudioTrack.html new file mode 100644 index 0000000000..28b76e3b43 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddAudioTrack.html @@ -0,0 +1,87 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddAudioTrackNoBundle.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddAudioTrackNoBundle.html new file mode 100644 index 0000000000..cff424e12c --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddAudioTrackNoBundle.html @@ -0,0 +1,76 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddVideoTrack.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddVideoTrack.html new file mode 100644 index 0000000000..b1be690e5b --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddVideoTrack.html @@ -0,0 +1,98 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddVideoTrackNoBundle.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddVideoTrackNoBundle.html new file mode 100644 index 0000000000..dcaf7943e2 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddVideoTrackNoBundle.html @@ -0,0 +1,89 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_removeVideoTrack.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_removeVideoTrack.html new file mode 100644 index 0000000000..4c4e7905e1 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_removeVideoTrack.html @@ -0,0 +1,64 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_renderAfterRenegotiation.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_renderAfterRenegotiation.html new file mode 100644 index 0000000000..c8091d7a9e --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_renderAfterRenegotiation.html @@ -0,0 +1,89 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceNullTrackThenRenegotiateAudio.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceNullTrackThenRenegotiateAudio.html new file mode 100644 index 0000000000..2253b87672 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceNullTrackThenRenegotiateAudio.html @@ -0,0 +1,53 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceNullTrackThenRenegotiateVideo.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceNullTrackThenRenegotiateVideo.html new file mode 100644 index 0000000000..d7bd6d8a37 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceNullTrackThenRenegotiateVideo.html @@ -0,0 +1,63 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack.html new file mode 100644 index 0000000000..9befc5c564 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack.html @@ -0,0 +1,187 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack_camera.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack_camera.html new file mode 100644 index 0000000000..356517e79f --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack_camera.html @@ -0,0 +1,48 @@ + + + + + + + +
+
+
+
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack_disabled.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack_disabled.html new file mode 100644 index 0000000000..11b2762d96 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack_disabled.html @@ -0,0 +1,60 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack_microphone.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack_microphone.html new file mode 100644 index 0000000000..5886caf6a4 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack_microphone.html @@ -0,0 +1,46 @@ + + + + + + + +
+
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceVideoThenRenegotiate.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceVideoThenRenegotiate.html new file mode 100644 index 0000000000..070cb42fcb --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceVideoThenRenegotiate.html @@ -0,0 +1,74 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIce.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIce.html new file mode 100644 index 0000000000..d94bb084b7 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIce.html @@ -0,0 +1,41 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceBadAnswer.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceBadAnswer.html new file mode 100644 index 0000000000..b71001b0db --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceBadAnswer.html @@ -0,0 +1,58 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalAndRemoteRollback.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalAndRemoteRollback.html new file mode 100644 index 0000000000..6bbf9440fc --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalAndRemoteRollback.html @@ -0,0 +1,82 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalAndRemoteRollbackNoSubsequentRestart.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalAndRemoteRollbackNoSubsequentRestart.html new file mode 100644 index 0000000000..37b0fc68fc --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalAndRemoteRollbackNoSubsequentRestart.html @@ -0,0 +1,77 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalRollback.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalRollback.html new file mode 100644 index 0000000000..f5f9a1f220 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalRollback.html @@ -0,0 +1,76 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalRollbackNoSubsequentRestart.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalRollbackNoSubsequentRestart.html new file mode 100644 index 0000000000..8e27864aae --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalRollbackNoSubsequentRestart.html @@ -0,0 +1,60 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoBundle.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoBundle.html new file mode 100644 index 0000000000..134fa97cc0 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoBundle.html @@ -0,0 +1,43 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoBundleNoRtcpMux.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoBundleNoRtcpMux.html new file mode 100644 index 0000000000..06a3a3c980 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoBundleNoRtcpMux.html @@ -0,0 +1,44 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoRtcpMux.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoRtcpMux.html new file mode 100644 index 0000000000..5d4780211a --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoRtcpMux.html @@ -0,0 +1,43 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_restrictBandwidthTargetBitrate.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_restrictBandwidthTargetBitrate.html new file mode 100644 index 0000000000..ff9fb1fc22 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_restrictBandwidthTargetBitrate.html @@ -0,0 +1,29 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_restrictBandwidthWithTias.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_restrictBandwidthWithTias.html new file mode 100644 index 0000000000..85b831e9a8 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_restrictBandwidthWithTias.html @@ -0,0 +1,30 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_rtcp_rsize.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_rtcp_rsize.html new file mode 100644 index 0000000000..25270984ea --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_rtcp_rsize.html @@ -0,0 +1,81 @@ + + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_scaleResolution.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_scaleResolution.html new file mode 100644 index 0000000000..4be6873fa6 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_scaleResolution.html @@ -0,0 +1,119 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_scaleResolution_oldSetParameters.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_scaleResolution_oldSetParameters.html new file mode 100644 index 0000000000..85a989ba32 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_scaleResolution_oldSetParameters.html @@ -0,0 +1,122 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_sender_and_receiver_stats.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_sender_and_receiver_stats.html new file mode 100644 index 0000000000..72749e8c50 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_sender_and_receiver_stats.html @@ -0,0 +1,73 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_setLocalAnswerInHaveLocalOffer.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_setLocalAnswerInHaveLocalOffer.html new file mode 100644 index 0000000000..07cdd7d6bd --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_setLocalAnswerInHaveLocalOffer.html @@ -0,0 +1,34 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_setLocalAnswerInStable.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_setLocalAnswerInStable.html new file mode 100644 index 0000000000..e57c0640f4 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_setLocalAnswerInStable.html @@ -0,0 +1,34 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_setLocalOfferInHaveRemoteOffer.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_setLocalOfferInHaveRemoteOffer.html new file mode 100644 index 0000000000..bd98a83635 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_setLocalOfferInHaveRemoteOffer.html @@ -0,0 +1,31 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters.html new file mode 100644 index 0000000000..5df97e39f5 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters.html @@ -0,0 +1,470 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_maxFramerate.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_maxFramerate.html new file mode 100644 index 0000000000..8047719775 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_maxFramerate.html @@ -0,0 +1,63 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_maxFramerate_oldSetParameters.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_maxFramerate_oldSetParameters.html new file mode 100644 index 0000000000..9c68a31c0a --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_maxFramerate_oldSetParameters.html @@ -0,0 +1,60 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_oldSetParameters.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_oldSetParameters.html new file mode 100644 index 0000000000..2b55ec46e6 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_oldSetParameters.html @@ -0,0 +1,86 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_scaleResolutionDownBy.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_scaleResolutionDownBy.html new file mode 100644 index 0000000000..d1275d6523 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_scaleResolutionDownBy.html @@ -0,0 +1,98 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_scaleResolutionDownBy_oldSetParameters.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_scaleResolutionDownBy_oldSetParameters.html new file mode 100644 index 0000000000..4d515bd5c1 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_scaleResolutionDownBy_oldSetParameters.html @@ -0,0 +1,96 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_setRemoteAnswerInHaveRemoteOffer.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_setRemoteAnswerInHaveRemoteOffer.html new file mode 100644 index 0000000000..1912835160 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_setRemoteAnswerInHaveRemoteOffer.html @@ -0,0 +1,34 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_setRemoteAnswerInStable.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_setRemoteAnswerInStable.html new file mode 100644 index 0000000000..6208fdea3e --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_setRemoteAnswerInStable.html @@ -0,0 +1,34 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_setRemoteOfferInHaveLocalOffer.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_setRemoteOfferInHaveLocalOffer.html new file mode 100644 index 0000000000..20236f442c --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_setRemoteOfferInHaveLocalOffer.html @@ -0,0 +1,37 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer.html new file mode 100644 index 0000000000..ba75c72022 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer.html @@ -0,0 +1,121 @@ + + + + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer_lowResFirst.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer_lowResFirst.html new file mode 100644 index 0000000000..00c6e4ad3a --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer_lowResFirst.html @@ -0,0 +1,113 @@ + + + + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer_lowResFirst_oldSetParameters.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer_lowResFirst_oldSetParameters.html new file mode 100644 index 0000000000..c2aafc4575 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer_lowResFirst_oldSetParameters.html @@ -0,0 +1,115 @@ + + + + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer_oldSetParameters.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer_oldSetParameters.html new file mode 100644 index 0000000000..bc0b9f71cc --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer_oldSetParameters.html @@ -0,0 +1,115 @@ + + + + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOddResolution.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOddResolution.html new file mode 100644 index 0000000000..c380b34f1a --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOddResolution.html @@ -0,0 +1,183 @@ + + + + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOddResolution_oldSetParameters.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOddResolution_oldSetParameters.html new file mode 100644 index 0000000000..0f6d3c8520 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOddResolution_oldSetParameters.html @@ -0,0 +1,172 @@ + + + + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer.html new file mode 100644 index 0000000000..cb7c13a0d1 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer.html @@ -0,0 +1,109 @@ + + + + + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer_lowResFirst.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer_lowResFirst.html new file mode 100644 index 0000000000..93141311f1 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer_lowResFirst.html @@ -0,0 +1,109 @@ + + + + + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer_lowResFirst_oldSetParameters.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer_lowResFirst_oldSetParameters.html new file mode 100644 index 0000000000..73e2d38eb2 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer_lowResFirst_oldSetParameters.html @@ -0,0 +1,112 @@ + + + + + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer_oldSetParameters.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer_oldSetParameters.html new file mode 100644 index 0000000000..551273af5e --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer_oldSetParameters.html @@ -0,0 +1,112 @@ + + + + + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_stats.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_stats.html new file mode 100644 index 0000000000..2ef98dc9c8 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_stats.html @@ -0,0 +1,42 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_stats_jitter.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_stats_jitter.html new file mode 100644 index 0000000000..6e1ef698b4 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_stats_jitter.html @@ -0,0 +1,58 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_stats_oneway.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_stats_oneway.html new file mode 100644 index 0000000000..02ace530a9 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_stats_oneway.html @@ -0,0 +1,65 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_stats_relayProtocol.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_stats_relayProtocol.html new file mode 100644 index 0000000000..cdc328fd2b --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_stats_relayProtocol.html @@ -0,0 +1,58 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_stereoFmtpPref.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_stereoFmtpPref.html new file mode 100644 index 0000000000..ab7811fe82 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_stereoFmtpPref.html @@ -0,0 +1,61 @@ + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_syncSetDescription.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_syncSetDescription.html new file mode 100644 index 0000000000..98f0de1b4a --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_syncSetDescription.html @@ -0,0 +1,53 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_telephoneEventFirst.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_telephoneEventFirst.html new file mode 100644 index 0000000000..bde51c1fd0 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_telephoneEventFirst.html @@ -0,0 +1,56 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_threeUnbundledConnections.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_threeUnbundledConnections.html new file mode 100644 index 0000000000..75f0d12463 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_threeUnbundledConnections.html @@ -0,0 +1,134 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_throwInCallbacks.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_throwInCallbacks.html new file mode 100644 index 0000000000..5a3872c120 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_throwInCallbacks.html @@ -0,0 +1,83 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_toJSON.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_toJSON.html new file mode 100644 index 0000000000..96c2c42b78 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_toJSON.html @@ -0,0 +1,39 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_trackDisabling.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_trackDisabling.html new file mode 100644 index 0000000000..73323cf007 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_trackDisabling.html @@ -0,0 +1,108 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_trackDisabling_clones.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_trackDisabling_clones.html new file mode 100644 index 0000000000..ae7647fa1a --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_trackDisabling_clones.html @@ -0,0 +1,162 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_trackless_sender_stats.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_trackless_sender_stats.html new file mode 100644 index 0000000000..f0356f5655 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_trackless_sender_stats.html @@ -0,0 +1,56 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioStreams.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioStreams.html new file mode 100644 index 0000000000..7ea18ab3dd --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioStreams.html @@ -0,0 +1,23 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioTracksInOneStream.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioTracksInOneStream.html new file mode 100644 index 0000000000..99d4ad625a --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioTracksInOneStream.html @@ -0,0 +1,37 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioVideoStreams.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioVideoStreams.html new file mode 100644 index 0000000000..5f4bd463d4 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioVideoStreams.html @@ -0,0 +1,26 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioVideoStreamsCombined.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioVideoStreamsCombined.html new file mode 100644 index 0000000000..fcc9c6c8fa --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioVideoStreamsCombined.html @@ -0,0 +1,70 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioVideoStreamsCombinedNoBundle.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioVideoStreamsCombinedNoBundle.html new file mode 100644 index 0000000000..8b825db617 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioVideoStreamsCombinedNoBundle.html @@ -0,0 +1,107 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_twoVideoStreams.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_twoVideoStreams.html new file mode 100644 index 0000000000..0ab180cc55 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_twoVideoStreams.html @@ -0,0 +1,23 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_twoVideoTracksInOneStream.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_twoVideoTracksInOneStream.html new file mode 100644 index 0000000000..4eaf8b3f48 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_twoVideoTracksInOneStream.html @@ -0,0 +1,37 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_verifyAudioAfterRenegotiation.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_verifyAudioAfterRenegotiation.html new file mode 100644 index 0000000000..86ef6d4678 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_verifyAudioAfterRenegotiation.html @@ -0,0 +1,99 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_verifyDescriptions.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_verifyDescriptions.html new file mode 100644 index 0000000000..f685f7c99a --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_verifyDescriptions.html @@ -0,0 +1,58 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_verifyVideoAfterRenegotiation.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_verifyVideoAfterRenegotiation.html new file mode 100644 index 0000000000..8d4155ddff --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_verifyVideoAfterRenegotiation.html @@ -0,0 +1,123 @@ + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_videoCodecs.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_videoCodecs.html new file mode 100644 index 0000000000..7a245b5d8c --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_videoCodecs.html @@ -0,0 +1,142 @@ + + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_videoRenegotiationInactiveAnswer.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_videoRenegotiationInactiveAnswer.html new file mode 100644 index 0000000000..b77633493d --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_videoRenegotiationInactiveAnswer.html @@ -0,0 +1,95 @@ + + + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_webAudio.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_webAudio.html new file mode 100644 index 0000000000..1d695ecbfa --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_webAudio.html @@ -0,0 +1,43 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_selftest.html b/dom/media/webrtc/tests/mochitests/test_selftest.html new file mode 100644 index 0000000000..3f1ce1402d --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_selftest.html @@ -0,0 +1,37 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_setSinkId.html b/dom/media/webrtc/tests/mochitests/test_setSinkId.html new file mode 100644 index 0000000000..0d85114a0e --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_setSinkId.html @@ -0,0 +1,83 @@ + + + + + + +
+
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_setSinkId_default_addTrack.html b/dom/media/webrtc/tests/mochitests/test_setSinkId_default_addTrack.html new file mode 100644 index 0000000000..64db4cad7c --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_setSinkId_default_addTrack.html @@ -0,0 +1,52 @@ + + + + + + +
+
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_setSinkId_preMutedElement.html b/dom/media/webrtc/tests/mochitests/test_setSinkId_preMutedElement.html new file mode 100644 index 0000000000..fb65c3312f --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_setSinkId_preMutedElement.html @@ -0,0 +1,100 @@ + + + + + + +
+
+
+ + diff --git a/dom/media/webrtc/tests/mochitests/test_unfocused_pref.html b/dom/media/webrtc/tests/mochitests/test_unfocused_pref.html new file mode 100644 index 0000000000..22df020f7c --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_unfocused_pref.html @@ -0,0 +1,49 @@ + + + + + + + + + + diff --git a/dom/media/webrtc/tests/mochitests/turnConfig.js b/dom/media/webrtc/tests/mochitests/turnConfig.js new file mode 100644 index 0000000000..1267de4ec5 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/turnConfig.js @@ -0,0 +1,16 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* An example of how to specify two TURN server configs: + * + * Note: If turn URL uses FQDN rather then an IP address the TURN relay + * verification step in checkStatsIceConnectionType might fail. + * + * var turnServers = { + * local: { iceServers: [{"username":"mozilla","credential":"mozilla","url":"turn:10.0.0.1"}] }, + * remote: { iceServers: [{"username":"firefox","credential":"firefox","url":"turn:10.0.0.2"}] } + * }; + */ + +var turnServers = {}; -- cgit v1.2.3