script: Move WebRTC DOM interfaces to script/dom/webrtc/ (#39023)

Move interfaces defined by the WebRTC specification to the
`script/dom/webrtc/` module from `script/dom/`.

`script/dom/rtc*.rs -> script/dom/webrtc/`

Testing: No changes, just a refactoring

Fixes (partially): #38901

Signed-off-by: Andrei Volykhin <volykhin.andrei@huawei.com>
Co-authored-by: Andrei Volykhin <volykhin.andrei@huawei.com>
This commit is contained in:
Andrei Volykhin 2025-08-29 16:55:50 +03:00 committed by GitHub
parent 66d9f957e6
commit 00d0783471
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 17 additions and 11 deletions

View file

@ -0,0 +1,15 @@
/* 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 https://mozilla.org/MPL/2.0/. */
pub(crate) mod rtcdatachannel;
pub(crate) mod rtcdatachannelevent;
pub(crate) mod rtcerror;
pub(crate) mod rtcerrorevent;
pub(crate) mod rtcicecandidate;
pub(crate) mod rtcpeerconnection;
pub(crate) mod rtcpeerconnectioniceevent;
pub(crate) mod rtcrtpsender;
pub(crate) mod rtcrtptransceiver;
pub(crate) mod rtcsessiondescription;
pub(crate) mod rtctrackevent;

View file

@ -0,0 +1,424 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use std::cell::Cell;
use std::ptr;
use constellation_traits::BlobImpl;
use dom_struct::dom_struct;
use js::jsapi::{JSAutoRealm, JSObject};
use js::jsval::UndefinedValue;
use js::rust::CustomAutoRooterGuard;
use js::typedarray::{ArrayBuffer, ArrayBufferView, CreateWith};
use script_bindings::conversions::SafeToJSValConvertible;
use script_bindings::weakref::WeakRef;
use servo_media::webrtc::{
DataChannelId, DataChannelInit, DataChannelMessage, DataChannelState, WebRtcError,
};
use crate::conversions::Convert;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::RTCDataChannelBinding::{
RTCDataChannelInit, RTCDataChannelMethods, RTCDataChannelState,
};
use crate::dom::bindings::codegen::Bindings::RTCErrorBinding::{RTCErrorDetailType, RTCErrorInit};
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::{DomGlobal, DomObject, reflect_dom_object};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::{DOMString, USVString};
use crate::dom::blob::Blob;
use crate::dom::event::{Event, EventBubbles, EventCancelable};
use crate::dom::eventtarget::EventTarget;
use crate::dom::globalscope::GlobalScope;
use crate::dom::messageevent::MessageEvent;
use crate::dom::rtcerror::RTCError;
use crate::dom::rtcerrorevent::RTCErrorEvent;
use crate::dom::rtcpeerconnection::RTCPeerConnection;
use crate::script_runtime::CanGc;
#[derive(JSTraceable, MallocSizeOf)]
struct DroppableRTCDataChannel {
#[ignore_malloc_size_of = "defined in servo-media"]
servo_media_id: DataChannelId,
peer_connection: WeakRef<RTCPeerConnection>,
}
impl DroppableRTCDataChannel {
fn new(peer_connection: WeakRef<RTCPeerConnection>, servo_media_id: DataChannelId) -> Self {
DroppableRTCDataChannel {
servo_media_id,
peer_connection,
}
}
pub(crate) fn get_servo_media_id(&self) -> DataChannelId {
self.servo_media_id
}
}
impl Drop for DroppableRTCDataChannel {
fn drop(&mut self) {
if let Some(root) = self.peer_connection.root() {
root.unregister_data_channel(&self.get_servo_media_id());
}
}
}
#[dom_struct]
pub(crate) struct RTCDataChannel {
eventtarget: EventTarget,
label: USVString,
ordered: bool,
max_packet_life_time: Option<u16>,
max_retransmits: Option<u16>,
protocol: USVString,
negotiated: bool,
id: Option<u16>,
ready_state: Cell<RTCDataChannelState>,
binary_type: DomRefCell<DOMString>,
peer_connection: Dom<RTCPeerConnection>,
droppable: DroppableRTCDataChannel,
}
impl RTCDataChannel {
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new_inherited(
peer_connection: &RTCPeerConnection,
label: USVString,
options: &RTCDataChannelInit,
servo_media_id: Option<DataChannelId>,
) -> RTCDataChannel {
let mut init: DataChannelInit = options.convert();
init.label = label.to_string();
let controller = peer_connection.get_webrtc_controller().borrow();
let servo_media_id = servo_media_id.unwrap_or(
controller
.as_ref()
.unwrap()
.create_data_channel(init)
.expect("Expected data channel id"),
);
RTCDataChannel {
eventtarget: EventTarget::new_inherited(),
label,
ordered: options.ordered,
max_packet_life_time: options.maxPacketLifeTime,
max_retransmits: options.maxRetransmits,
protocol: options.protocol.clone(),
negotiated: options.negotiated,
id: options.id,
ready_state: Cell::new(RTCDataChannelState::Connecting),
binary_type: DomRefCell::new(DOMString::from("blob")),
peer_connection: Dom::from_ref(peer_connection),
droppable: DroppableRTCDataChannel::new(WeakRef::new(peer_connection), servo_media_id),
}
}
pub(crate) fn new(
global: &GlobalScope,
peer_connection: &RTCPeerConnection,
label: USVString,
options: &RTCDataChannelInit,
servo_media_id: Option<DataChannelId>,
can_gc: CanGc,
) -> DomRoot<RTCDataChannel> {
let rtc_data_channel = reflect_dom_object(
Box::new(RTCDataChannel::new_inherited(
peer_connection,
label,
options,
servo_media_id,
)),
global,
can_gc,
);
peer_connection
.register_data_channel(rtc_data_channel.get_servo_media_id(), &rtc_data_channel);
rtc_data_channel
}
pub(crate) fn get_servo_media_id(&self) -> DataChannelId {
self.droppable.get_servo_media_id()
}
pub(crate) fn on_open(&self, can_gc: CanGc) {
let event = Event::new(
&self.global(),
atom!("open"),
EventBubbles::DoesNotBubble,
EventCancelable::NotCancelable,
can_gc,
);
event.upcast::<Event>().fire(self.upcast(), can_gc);
}
pub(crate) fn on_close(&self, can_gc: CanGc) {
let event = Event::new(
&self.global(),
atom!("close"),
EventBubbles::DoesNotBubble,
EventCancelable::NotCancelable,
can_gc,
);
event.upcast::<Event>().fire(self.upcast(), can_gc);
self.peer_connection
.unregister_data_channel(&self.get_servo_media_id());
}
pub(crate) fn on_error(&self, error: WebRtcError, can_gc: CanGc) {
let global = self.global();
let window = global.as_window();
let cx = GlobalScope::get_cx();
let _ac = JSAutoRealm::new(*cx, self.reflector().get_jsobject().get());
let init = RTCErrorInit {
errorDetail: RTCErrorDetailType::Data_channel_failure,
httpRequestStatusCode: None,
receivedAlert: None,
sctpCauseCode: None,
sdpLineNumber: None,
sentAlert: None,
};
let message = match error {
WebRtcError::Backend(message) => DOMString::from(message),
};
let error = RTCError::new(window, &init, message, can_gc);
let event = RTCErrorEvent::new(window, atom!("error"), false, false, &error, can_gc);
event.upcast::<Event>().fire(self.upcast(), can_gc);
}
#[allow(unsafe_code)]
pub(crate) fn on_message(&self, channel_message: DataChannelMessage, can_gc: CanGc) {
let global = self.global();
let cx = GlobalScope::get_cx();
let _ac = JSAutoRealm::new(*cx, self.reflector().get_jsobject().get());
rooted!(in(*cx) let mut message = UndefinedValue());
match channel_message {
DataChannelMessage::Text(text) => {
text.safe_to_jsval(cx, message.handle_mut());
},
DataChannelMessage::Binary(data) => match &**self.binary_type.borrow() {
"blob" => {
let blob = Blob::new(
&global,
BlobImpl::new_from_bytes(data, "".to_owned()),
can_gc,
);
blob.safe_to_jsval(cx, message.handle_mut());
},
"arraybuffer" => {
rooted!(in(*cx) let mut array_buffer = ptr::null_mut::<JSObject>());
unsafe {
assert!(
ArrayBuffer::create(
*cx,
CreateWith::Slice(&data),
array_buffer.handle_mut()
)
.is_ok()
)
};
(*array_buffer).safe_to_jsval(cx, message.handle_mut());
},
_ => unreachable!(),
},
}
MessageEvent::dispatch_jsval(
self.upcast(),
&global,
message.handle(),
Some(&global.origin().immutable().ascii_serialization()),
None,
vec![],
can_gc,
);
}
pub(crate) fn on_state_change(&self, state: DataChannelState, can_gc: CanGc) {
if let DataChannelState::Closing = state {
let event = Event::new(
&self.global(),
atom!("closing"),
EventBubbles::DoesNotBubble,
EventCancelable::NotCancelable,
can_gc,
);
event.upcast::<Event>().fire(self.upcast(), can_gc);
};
self.ready_state.set(state.convert());
}
fn send(&self, source: &SendSource) -> Fallible<()> {
if self.ready_state.get() != RTCDataChannelState::Open {
return Err(Error::InvalidState);
}
let message = match source {
SendSource::String(string) => DataChannelMessage::Text(string.0.clone()),
SendSource::Blob(blob) => {
DataChannelMessage::Binary(blob.get_bytes().unwrap_or(vec![]))
},
SendSource::ArrayBuffer(array) => DataChannelMessage::Binary(array.to_vec()),
SendSource::ArrayBufferView(array) => DataChannelMessage::Binary(array.to_vec()),
};
let controller = self.peer_connection.get_webrtc_controller().borrow();
controller
.as_ref()
.unwrap()
.send_data_channel_message(&self.get_servo_media_id(), message);
Ok(())
}
}
enum SendSource<'a, 'b> {
String(&'a USVString),
Blob(&'a Blob),
ArrayBuffer(CustomAutoRooterGuard<'b, ArrayBuffer>),
ArrayBufferView(CustomAutoRooterGuard<'b, ArrayBufferView>),
}
impl RTCDataChannelMethods<crate::DomTypeHolder> for RTCDataChannel {
// https://www.w3.org/TR/webrtc/#dom-rtcdatachannel-onopen
event_handler!(open, GetOnopen, SetOnopen);
// https://www.w3.org/TR/webrtc/#dom-rtcdatachannel-onbufferedamountlow
event_handler!(
bufferedamountlow,
GetOnbufferedamountlow,
SetOnbufferedamountlow
);
// https://www.w3.org/TR/webrtc/#dom-rtcdatachannel-onerror
event_handler!(error, GetOnerror, SetOnerror);
// https://www.w3.org/TR/webrtc/#dom-rtcdatachannel-onclosing
event_handler!(closing, GetOnclosing, SetOnclosing);
// https://www.w3.org/TR/webrtc/#dom-rtcdatachannel-onclose
event_handler!(close, GetOnclose, SetOnclose);
// https://www.w3.org/TR/webrtc/#dom-rtcdatachannel-onmessage
event_handler!(message, GetOnmessage, SetOnmessage);
// https://www.w3.org/TR/webrtc/#dom-datachannel-label
fn Label(&self) -> USVString {
self.label.clone()
}
// https://www.w3.org/TR/webrtc/#dom-datachannel-ordered
fn Ordered(&self) -> bool {
self.ordered
}
// https://www.w3.org/TR/webrtc/#dom-datachannel-maxpacketlifetime
fn GetMaxPacketLifeTime(&self) -> Option<u16> {
self.max_packet_life_time
}
// https://www.w3.org/TR/webrtc/#dom-datachannel-maxretransmits
fn GetMaxRetransmits(&self) -> Option<u16> {
self.max_retransmits
}
// https://www.w3.org/TR/webrtc/#dom-datachannel-protocol
fn Protocol(&self) -> USVString {
self.protocol.clone()
}
// https://www.w3.org/TR/webrtc/#dom-datachannel-negotiated
fn Negotiated(&self) -> bool {
self.negotiated
}
// https://www.w3.org/TR/webrtc/#dom-rtcdatachannel-id
fn GetId(&self) -> Option<u16> {
self.id
}
// https://www.w3.org/TR/webrtc/#dom-datachannel-readystate
fn ReadyState(&self) -> RTCDataChannelState {
self.ready_state.get()
}
// XXX We need a way to know when the underlying data transport
// actually sends data from its queue to decrease buffered amount.
// fn BufferedAmount(&self) -> u32;
// fn BufferedAmountLowThreshold(&self) -> u32;
// fn SetBufferedAmountLowThreshold(&self, value: u32) -> ();
// https://www.w3.org/TR/webrtc/#dom-rtcdatachannel-close
fn Close(&self) {
let controller = self.peer_connection.get_webrtc_controller().borrow();
controller
.as_ref()
.unwrap()
.close_data_channel(&self.get_servo_media_id());
}
// https://www.w3.org/TR/webrtc/#dom-datachannel-binarytype
fn BinaryType(&self) -> DOMString {
self.binary_type.borrow().clone()
}
// https://www.w3.org/TR/webrtc/#dom-datachannel-binarytype
fn SetBinaryType(&self, value: DOMString) -> Fallible<()> {
if value != "blob" || value != "arraybuffer" {
return Err(Error::Syntax);
}
*self.binary_type.borrow_mut() = value;
Ok(())
}
// https://www.w3.org/TR/webrtc/#dom-rtcdatachannel-send
fn Send(&self, data: USVString) -> Fallible<()> {
self.send(&SendSource::String(&data))
}
// https://www.w3.org/TR/webrtc/#dom-rtcdatachannel-send!overload-1
fn Send_(&self, data: &Blob) -> Fallible<()> {
self.send(&SendSource::Blob(data))
}
// https://www.w3.org/TR/webrtc/#dom-rtcdatachannel-send!overload-2
fn Send__(&self, data: CustomAutoRooterGuard<ArrayBuffer>) -> Fallible<()> {
self.send(&SendSource::ArrayBuffer(data))
}
// https://www.w3.org/TR/webrtc/#dom-rtcdatachannel-send!overload-3
fn Send___(&self, data: CustomAutoRooterGuard<ArrayBufferView>) -> Fallible<()> {
self.send(&SendSource::ArrayBufferView(data))
}
}
impl Convert<DataChannelInit> for &RTCDataChannelInit {
fn convert(self) -> DataChannelInit {
DataChannelInit {
label: String::new(),
id: self.id,
max_packet_life_time: self.maxPacketLifeTime,
max_retransmits: self.maxRetransmits,
negotiated: self.negotiated,
ordered: self.ordered,
protocol: self.protocol.to_string(),
}
}
}
impl Convert<RTCDataChannelState> for DataChannelState {
fn convert(self) -> RTCDataChannelState {
match self {
DataChannelState::Connecting | DataChannelState::__Unknown(_) => {
RTCDataChannelState::Connecting
},
DataChannelState::Open => RTCDataChannelState::Open,
DataChannelState::Closing => RTCDataChannelState::Closing,
DataChannelState::Closed => RTCDataChannelState::Closed,
}
}
}

View file

@ -0,0 +1,99 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use js::rust::HandleObject;
use stylo_atoms::Atom;
use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods;
use crate::dom::bindings::codegen::Bindings::RTCDataChannelEventBinding::{
RTCDataChannelEventInit, RTCDataChannelEventMethods,
};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::reflect_dom_object_with_proto;
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::DOMString;
use crate::dom::event::Event;
use crate::dom::rtcdatachannel::RTCDataChannel;
use crate::dom::window::Window;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct RTCDataChannelEvent {
event: Event,
channel: Dom<RTCDataChannel>,
}
impl RTCDataChannelEvent {
fn new_inherited(channel: &RTCDataChannel) -> RTCDataChannelEvent {
RTCDataChannelEvent {
event: Event::new_inherited(),
channel: Dom::from_ref(channel),
}
}
pub(crate) fn new(
window: &Window,
type_: Atom,
bubbles: bool,
cancelable: bool,
channel: &RTCDataChannel,
can_gc: CanGc,
) -> DomRoot<RTCDataChannelEvent> {
Self::new_with_proto(window, None, type_, bubbles, cancelable, channel, can_gc)
}
fn new_with_proto(
window: &Window,
proto: Option<HandleObject>,
type_: Atom,
bubbles: bool,
cancelable: bool,
channel: &RTCDataChannel,
can_gc: CanGc,
) -> DomRoot<RTCDataChannelEvent> {
let event = reflect_dom_object_with_proto(
Box::new(RTCDataChannelEvent::new_inherited(channel)),
window,
proto,
can_gc,
);
{
let event = event.upcast::<Event>();
event.init_event(type_, bubbles, cancelable);
}
event
}
}
impl RTCDataChannelEventMethods<crate::DomTypeHolder> for RTCDataChannelEvent {
// https://www.w3.org/TR/webrtc/#dom-rtcdatachannelevent-constructor
fn Constructor(
window: &Window,
proto: Option<HandleObject>,
can_gc: CanGc,
type_: DOMString,
init: &RTCDataChannelEventInit,
) -> DomRoot<RTCDataChannelEvent> {
RTCDataChannelEvent::new_with_proto(
window,
proto,
Atom::from(type_),
init.parent.bubbles,
init.parent.cancelable,
&init.channel,
can_gc,
)
}
// https://www.w3.org/TR/webrtc/#dom-datachannelevent-channel
fn Channel(&self) -> DomRoot<RTCDataChannel> {
DomRoot::from_ref(&*self.channel)
}
// https://dom.spec.whatwg.org/#dom-event-istrusted
fn IsTrusted(&self) -> bool {
self.event.IsTrusted()
}
}

View file

@ -0,0 +1,108 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use js::rust::HandleObject;
use crate::dom::bindings::codegen::Bindings::RTCErrorBinding::{
RTCErrorDetailType, RTCErrorInit, RTCErrorMethods,
};
use crate::dom::bindings::reflector::reflect_dom_object_with_proto;
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::domexception::DOMException;
use crate::dom::window::Window;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct RTCError {
exception: DOMException,
error_detail: RTCErrorDetailType,
sdp_line_number: Option<i32>,
http_request_status_code: Option<i32>,
sctp_cause_code: Option<i32>,
received_alert: Option<u32>,
sent_alert: Option<u32>,
}
impl RTCError {
fn new_inherited(init: &RTCErrorInit, message: DOMString) -> RTCError {
RTCError {
exception: DOMException::new_inherited(message, "OperationError".into()),
error_detail: init.errorDetail,
sdp_line_number: init.sdpLineNumber,
http_request_status_code: init.httpRequestStatusCode,
sctp_cause_code: init.sctpCauseCode,
received_alert: init.receivedAlert,
sent_alert: init.sentAlert,
}
}
pub(crate) fn new(
window: &Window,
init: &RTCErrorInit,
message: DOMString,
can_gc: CanGc,
) -> DomRoot<RTCError> {
Self::new_with_proto(window, None, init, message, can_gc)
}
fn new_with_proto(
window: &Window,
proto: Option<HandleObject>,
init: &RTCErrorInit,
message: DOMString,
can_gc: CanGc,
) -> DomRoot<RTCError> {
reflect_dom_object_with_proto(
Box::new(RTCError::new_inherited(init, message)),
window,
proto,
can_gc,
)
}
}
impl RTCErrorMethods<crate::DomTypeHolder> for RTCError {
// https://www.w3.org/TR/webrtc/#dom-rtcerror-constructor
fn Constructor(
window: &Window,
proto: Option<HandleObject>,
can_gc: CanGc,
init: &RTCErrorInit,
message: DOMString,
) -> DomRoot<RTCError> {
RTCError::new_with_proto(window, proto, init, message, can_gc)
}
// https://www.w3.org/TR/webrtc/#dom-rtcerror-errordetail
fn ErrorDetail(&self) -> RTCErrorDetailType {
self.error_detail
}
// https://www.w3.org/TR/webrtc/#dom-rtcerror-sdplinenumber
fn GetSdpLineNumber(&self) -> Option<i32> {
self.sdp_line_number
}
// https://www.w3.org/TR/webrtc/#dom-rtcerror
fn GetHttpRequestStatusCode(&self) -> Option<i32> {
self.http_request_status_code
}
// https://www.w3.org/TR/webrtc/#dom-rtcerror-sctpcausecode
fn GetSctpCauseCode(&self) -> Option<i32> {
self.sctp_cause_code
}
// https://www.w3.org/TR/webrtc/#dom-rtcerror-receivedalert
fn GetReceivedAlert(&self) -> Option<u32> {
self.received_alert
}
// https://www.w3.org/TR/webrtc/#dom-rtcerror-sentalert
fn GetSentAlert(&self) -> Option<u32> {
self.sent_alert
}
}

View file

@ -0,0 +1,99 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use js::rust::HandleObject;
use stylo_atoms::Atom;
use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods;
use crate::dom::bindings::codegen::Bindings::RTCErrorEventBinding::{
RTCErrorEventInit, RTCErrorEventMethods,
};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::reflect_dom_object_with_proto;
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::DOMString;
use crate::dom::event::Event;
use crate::dom::rtcerror::RTCError;
use crate::dom::window::Window;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct RTCErrorEvent {
event: Event,
error: Dom<RTCError>,
}
impl RTCErrorEvent {
fn new_inherited(error: &RTCError) -> RTCErrorEvent {
RTCErrorEvent {
event: Event::new_inherited(),
error: Dom::from_ref(error),
}
}
pub(crate) fn new(
window: &Window,
type_: Atom,
bubbles: bool,
cancelable: bool,
error: &RTCError,
can_gc: CanGc,
) -> DomRoot<RTCErrorEvent> {
Self::new_with_proto(window, None, type_, bubbles, cancelable, error, can_gc)
}
fn new_with_proto(
window: &Window,
proto: Option<HandleObject>,
type_: Atom,
bubbles: bool,
cancelable: bool,
error: &RTCError,
can_gc: CanGc,
) -> DomRoot<RTCErrorEvent> {
let event = reflect_dom_object_with_proto(
Box::new(RTCErrorEvent::new_inherited(error)),
window,
proto,
can_gc,
);
{
let event = event.upcast::<Event>();
event.init_event(type_, bubbles, cancelable);
}
event
}
}
impl RTCErrorEventMethods<crate::DomTypeHolder> for RTCErrorEvent {
// https://www.w3.org/TR/webrtc/#dom-rtcerrorevent-constructor
fn Constructor(
window: &Window,
proto: Option<HandleObject>,
can_gc: CanGc,
type_: DOMString,
init: &RTCErrorEventInit,
) -> DomRoot<RTCErrorEvent> {
RTCErrorEvent::new_with_proto(
window,
proto,
Atom::from(type_),
init.parent.bubbles,
init.parent.cancelable,
&init.error,
can_gc,
)
}
// https://www.w3.org/TR/webrtc/#dom-rtcerrorevent-error
fn Error(&self) -> DomRoot<RTCError> {
DomRoot::from_ref(&*self.error)
}
// https://dom.spec.whatwg.org/#dom-event-istrusted
fn IsTrusted(&self) -> bool {
self.event.IsTrusted()
}
}

View file

@ -0,0 +1,138 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use js::rust::HandleObject;
use crate::dom::bindings::codegen::Bindings::RTCIceCandidateBinding::{
RTCIceCandidateInit, RTCIceCandidateMethods,
};
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::window::Window;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct RTCIceCandidate {
reflector: Reflector,
candidate: DOMString,
sdp_m_id: Option<DOMString>,
sdp_m_line_index: Option<u16>,
username_fragment: Option<DOMString>,
}
impl RTCIceCandidate {
pub(crate) fn new_inherited(
candidate: DOMString,
sdp_m_id: Option<DOMString>,
sdp_m_line_index: Option<u16>,
username_fragment: Option<DOMString>,
) -> RTCIceCandidate {
RTCIceCandidate {
reflector: Reflector::new(),
candidate,
sdp_m_id,
sdp_m_line_index,
username_fragment,
}
}
pub(crate) fn new(
window: &Window,
candidate: DOMString,
sdp_m_id: Option<DOMString>,
sdp_m_line_index: Option<u16>,
username_fragment: Option<DOMString>,
can_gc: CanGc,
) -> DomRoot<RTCIceCandidate> {
Self::new_with_proto(
window,
None,
candidate,
sdp_m_id,
sdp_m_line_index,
username_fragment,
can_gc,
)
}
fn new_with_proto(
window: &Window,
proto: Option<HandleObject>,
candidate: DOMString,
sdp_m_id: Option<DOMString>,
sdp_m_line_index: Option<u16>,
username_fragment: Option<DOMString>,
can_gc: CanGc,
) -> DomRoot<RTCIceCandidate> {
reflect_dom_object_with_proto(
Box::new(RTCIceCandidate::new_inherited(
candidate,
sdp_m_id,
sdp_m_line_index,
username_fragment,
)),
window,
proto,
can_gc,
)
}
}
impl RTCIceCandidateMethods<crate::DomTypeHolder> for RTCIceCandidate {
/// <https://w3c.github.io/webrtc-pc/#dom-rtcicecandidate-constructor>
fn Constructor(
window: &Window,
proto: Option<HandleObject>,
can_gc: CanGc,
config: &RTCIceCandidateInit,
) -> Fallible<DomRoot<RTCIceCandidate>> {
if config.sdpMid.is_none() && config.sdpMLineIndex.is_none() {
return Err(Error::Type(
"one of sdpMid and sdpMLineIndex must be set".to_string(),
));
}
Ok(RTCIceCandidate::new_with_proto(
window,
proto,
config.candidate.clone(),
config.sdpMid.clone(),
config.sdpMLineIndex,
config.usernameFragment.clone(),
can_gc,
))
}
/// <https://w3c.github.io/webrtc-pc/#dom-rtcicecandidate-candidate>
fn Candidate(&self) -> DOMString {
self.candidate.clone()
}
/// <https://w3c.github.io/webrtc-pc/#dom-rtcicecandidate-sdpmid>
fn GetSdpMid(&self) -> Option<DOMString> {
self.sdp_m_id.clone()
}
/// <https://w3c.github.io/webrtc-pc/#dom-rtcicecandidate-sdpmlineindex>
fn GetSdpMLineIndex(&self) -> Option<u16> {
self.sdp_m_line_index
}
/// <https://w3c.github.io/webrtc-pc/#dom-rtcicecandidate-usernamefragment>
fn GetUsernameFragment(&self) -> Option<DOMString> {
self.username_fragment.clone()
}
/// <https://w3c.github.io/webrtc-pc/#dom-rtcicecandidate-tojson>
fn ToJSON(&self) -> RTCIceCandidateInit {
RTCIceCandidateInit {
candidate: self.candidate.clone(),
sdpMid: self.sdp_m_id.clone(),
sdpMLineIndex: self.sdp_m_line_index,
usernameFragment: self.username_fragment.clone(),
}
}
}

View file

@ -0,0 +1,858 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use std::cell::Cell;
use std::collections::HashMap;
use std::rc::Rc;
use dom_struct::dom_struct;
use js::rust::HandleObject;
use servo_media::ServoMedia;
use servo_media::streams::MediaStreamType;
use servo_media::streams::registry::MediaStreamId;
use servo_media::webrtc::{
BundlePolicy, DataChannelEvent, DataChannelId, DataChannelState, GatheringState, IceCandidate,
IceConnectionState, SdpType, SessionDescription, SignalingState, WebRtcController,
WebRtcSignaller,
};
use crate::conversions::Convert;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::RTCDataChannelBinding::RTCDataChannelInit;
use crate::dom::bindings::codegen::Bindings::RTCIceCandidateBinding::RTCIceCandidateInit;
use crate::dom::bindings::codegen::Bindings::RTCPeerConnectionBinding::{
RTCAnswerOptions, RTCBundlePolicy, RTCConfiguration, RTCIceConnectionState,
RTCIceGatheringState, RTCOfferOptions, RTCPeerConnectionMethods, RTCRtpTransceiverInit,
RTCSignalingState,
};
use crate::dom::bindings::codegen::Bindings::RTCSessionDescriptionBinding::{
RTCSdpType, RTCSessionDescriptionInit, RTCSessionDescriptionMethods,
};
use crate::dom::bindings::codegen::UnionTypes::{MediaStreamTrackOrString, StringOrStringSequence};
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object_with_proto};
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
use crate::dom::bindings::str::USVString;
use crate::dom::event::{Event, EventBubbles, EventCancelable};
use crate::dom::eventtarget::EventTarget;
use crate::dom::mediastream::MediaStream;
use crate::dom::mediastreamtrack::MediaStreamTrack;
use crate::dom::promise::Promise;
use crate::dom::rtcdatachannel::RTCDataChannel;
use crate::dom::rtcdatachannelevent::RTCDataChannelEvent;
use crate::dom::rtcicecandidate::RTCIceCandidate;
use crate::dom::rtcpeerconnectioniceevent::RTCPeerConnectionIceEvent;
use crate::dom::rtcrtptransceiver::RTCRtpTransceiver;
use crate::dom::rtcsessiondescription::RTCSessionDescription;
use crate::dom::rtctrackevent::RTCTrackEvent;
use crate::dom::window::Window;
use crate::realms::{InRealm, enter_realm};
use crate::script_runtime::CanGc;
use crate::task_source::SendableTaskSource;
#[dom_struct]
pub(crate) struct RTCPeerConnection {
eventtarget: EventTarget,
#[ignore_malloc_size_of = "defined in servo-media"]
#[no_trace]
controller: DomRefCell<Option<WebRtcController>>,
closed: Cell<bool>,
// Helps track state changes between the time createOffer/createAnswer
// is called and resolved
offer_answer_generation: Cell<u32>,
#[ignore_malloc_size_of = "promises are hard"]
offer_promises: DomRefCell<Vec<Rc<Promise>>>,
#[ignore_malloc_size_of = "promises are hard"]
answer_promises: DomRefCell<Vec<Rc<Promise>>>,
local_description: MutNullableDom<RTCSessionDescription>,
remote_description: MutNullableDom<RTCSessionDescription>,
gathering_state: Cell<RTCIceGatheringState>,
ice_connection_state: Cell<RTCIceConnectionState>,
signaling_state: Cell<RTCSignalingState>,
#[ignore_malloc_size_of = "defined in servo-media"]
data_channels: DomRefCell<HashMap<DataChannelId, Dom<RTCDataChannel>>>,
}
struct RTCSignaller {
trusted: Trusted<RTCPeerConnection>,
task_source: SendableTaskSource,
}
impl WebRtcSignaller for RTCSignaller {
fn on_ice_candidate(&self, _: &WebRtcController, candidate: IceCandidate) {
let this = self.trusted.clone();
self.task_source.queue(task!(on_ice_candidate: move || {
let this = this.root();
this.on_ice_candidate(candidate, CanGc::note());
}));
}
fn on_negotiation_needed(&self, _: &WebRtcController) {
let this = self.trusted.clone();
self.task_source
.queue(task!(on_negotiation_needed: move || {
let this = this.root();
this.on_negotiation_needed(CanGc::note());
}));
}
fn update_gathering_state(&self, state: GatheringState) {
let this = self.trusted.clone();
self.task_source
.queue(task!(update_gathering_state: move || {
let this = this.root();
this.update_gathering_state(state, CanGc::note());
}));
}
fn update_ice_connection_state(&self, state: IceConnectionState) {
let this = self.trusted.clone();
self.task_source
.queue(task!(update_ice_connection_state: move || {
let this = this.root();
this.update_ice_connection_state(state, CanGc::note());
}));
}
fn update_signaling_state(&self, state: SignalingState) {
let this = self.trusted.clone();
self.task_source
.queue(task!(update_signaling_state: move || {
let this = this.root();
this.update_signaling_state(state, CanGc::note());
}));
}
fn on_add_stream(&self, id: &MediaStreamId, ty: MediaStreamType) {
let this = self.trusted.clone();
let id = *id;
self.task_source.queue(task!(on_add_stream: move || {
let this = this.root();
this.on_add_stream(id, ty, CanGc::note());
}));
}
fn on_data_channel_event(
&self,
channel: DataChannelId,
event: DataChannelEvent,
_: &WebRtcController,
) {
// XXX(ferjm) get label and options from channel properties.
let this = self.trusted.clone();
self.task_source
.queue(task!(on_data_channel_event: move || {
let this = this.root();
let global = this.global();
let _ac = enter_realm(&*global);
this.on_data_channel_event(channel, event, CanGc::note());
}));
}
fn close(&self) {
// do nothing
}
}
impl RTCPeerConnection {
pub(crate) fn new_inherited() -> RTCPeerConnection {
RTCPeerConnection {
eventtarget: EventTarget::new_inherited(),
controller: DomRefCell::new(None),
closed: Cell::new(false),
offer_answer_generation: Cell::new(0),
offer_promises: DomRefCell::new(vec![]),
answer_promises: DomRefCell::new(vec![]),
local_description: Default::default(),
remote_description: Default::default(),
gathering_state: Cell::new(RTCIceGatheringState::New),
ice_connection_state: Cell::new(RTCIceConnectionState::New),
signaling_state: Cell::new(RTCSignalingState::Stable),
data_channels: DomRefCell::new(HashMap::new()),
}
}
fn new(
window: &Window,
proto: Option<HandleObject>,
config: &RTCConfiguration,
can_gc: CanGc,
) -> DomRoot<RTCPeerConnection> {
let this = reflect_dom_object_with_proto(
Box::new(RTCPeerConnection::new_inherited()),
window,
proto,
can_gc,
);
let signaller = this.make_signaller();
*this.controller.borrow_mut() = Some(ServoMedia::get().create_webrtc(signaller));
if let Some(ref servers) = config.iceServers {
if let Some(server) = servers.first() {
let server = match server.urls {
StringOrStringSequence::String(ref s) => Some(s.clone()),
StringOrStringSequence::StringSequence(ref s) => s.first().cloned(),
};
if let Some(server) = server {
let policy = match config.bundlePolicy {
RTCBundlePolicy::Balanced => BundlePolicy::Balanced,
RTCBundlePolicy::Max_compat => BundlePolicy::MaxCompat,
RTCBundlePolicy::Max_bundle => BundlePolicy::MaxBundle,
};
this.controller
.borrow()
.as_ref()
.unwrap()
.configure(server.to_string(), policy);
}
}
}
this
}
pub(crate) fn get_webrtc_controller(&self) -> &DomRefCell<Option<WebRtcController>> {
&self.controller
}
fn make_signaller(&self) -> Box<dyn WebRtcSignaller> {
let trusted = Trusted::new(self);
Box::new(RTCSignaller {
trusted,
task_source: self.global().task_manager().networking_task_source().into(),
})
}
fn on_ice_candidate(&self, candidate: IceCandidate, can_gc: CanGc) {
if self.closed.get() {
return;
}
let candidate = RTCIceCandidate::new(
self.global().as_window(),
candidate.candidate.into(),
None,
Some(candidate.sdp_mline_index as u16),
None,
can_gc,
);
let event = RTCPeerConnectionIceEvent::new(
self.global().as_window(),
atom!("icecandidate"),
Some(&candidate),
None,
true,
can_gc,
);
event.upcast::<Event>().fire(self.upcast(), can_gc);
}
fn on_negotiation_needed(&self, can_gc: CanGc) {
if self.closed.get() {
return;
}
let event = Event::new(
&self.global(),
atom!("negotiationneeded"),
EventBubbles::DoesNotBubble,
EventCancelable::NotCancelable,
can_gc,
);
event.upcast::<Event>().fire(self.upcast(), can_gc);
}
fn on_add_stream(&self, id: MediaStreamId, ty: MediaStreamType, can_gc: CanGc) {
if self.closed.get() {
return;
}
let track = MediaStreamTrack::new(&self.global(), id, ty, can_gc);
let event = RTCTrackEvent::new(
self.global().as_window(),
atom!("track"),
false,
false,
&track,
can_gc,
);
event.upcast::<Event>().fire(self.upcast(), can_gc);
}
fn on_data_channel_event(
&self,
channel_id: DataChannelId,
event: DataChannelEvent,
can_gc: CanGc,
) {
if self.closed.get() {
return;
}
match event {
DataChannelEvent::NewChannel => {
let channel = RTCDataChannel::new(
&self.global(),
self,
USVString::from("".to_owned()),
&RTCDataChannelInit::empty(),
Some(channel_id),
can_gc,
);
let event = RTCDataChannelEvent::new(
self.global().as_window(),
atom!("datachannel"),
false,
false,
&channel,
can_gc,
);
event.upcast::<Event>().fire(self.upcast(), can_gc);
},
_ => {
let channel: DomRoot<RTCDataChannel> =
if let Some(channel) = self.data_channels.borrow().get(&channel_id) {
DomRoot::from_ref(&**channel)
} else {
warn!(
"Got an event for an unregistered data channel {:?}",
channel_id
);
return;
};
match event {
DataChannelEvent::Open => channel.on_open(can_gc),
DataChannelEvent::Close => channel.on_close(can_gc),
DataChannelEvent::Error(error) => channel.on_error(error, can_gc),
DataChannelEvent::OnMessage(message) => channel.on_message(message, can_gc),
DataChannelEvent::StateChange(state) => channel.on_state_change(state, can_gc),
DataChannelEvent::NewChannel => unreachable!(),
}
},
};
}
pub(crate) fn register_data_channel(&self, id: DataChannelId, channel: &RTCDataChannel) {
if self
.data_channels
.borrow_mut()
.insert(id, Dom::from_ref(channel))
.is_some()
{
warn!("Data channel already registered {:?}", id);
}
}
pub(crate) fn unregister_data_channel(&self, id: &DataChannelId) {
self.data_channels.borrow_mut().remove(id);
}
/// <https://www.w3.org/TR/webrtc/#update-ice-gathering-state>
fn update_gathering_state(&self, state: GatheringState, can_gc: CanGc) {
// step 1
if self.closed.get() {
return;
}
// step 2 (state derivation already done by gstreamer)
let state: RTCIceGatheringState = state.convert();
// step 3
if state == self.gathering_state.get() {
return;
}
// step 4
self.gathering_state.set(state);
// step 5
let event = Event::new(
&self.global(),
atom!("icegatheringstatechange"),
EventBubbles::DoesNotBubble,
EventCancelable::NotCancelable,
can_gc,
);
event.upcast::<Event>().fire(self.upcast(), can_gc);
// step 6
if state == RTCIceGatheringState::Complete {
let event = RTCPeerConnectionIceEvent::new(
self.global().as_window(),
atom!("icecandidate"),
None,
None,
true,
can_gc,
);
event.upcast::<Event>().fire(self.upcast(), can_gc);
}
}
/// <https://www.w3.org/TR/webrtc/#update-ice-connection-state>
fn update_ice_connection_state(&self, state: IceConnectionState, can_gc: CanGc) {
// step 1
if self.closed.get() {
return;
}
// step 2 (state derivation already done by gstreamer)
let state: RTCIceConnectionState = state.convert();
// step 3
if state == self.ice_connection_state.get() {
return;
}
// step 4
self.ice_connection_state.set(state);
// step 5
let event = Event::new(
&self.global(),
atom!("iceconnectionstatechange"),
EventBubbles::DoesNotBubble,
EventCancelable::NotCancelable,
can_gc,
);
event.upcast::<Event>().fire(self.upcast(), can_gc);
}
fn update_signaling_state(&self, state: SignalingState, can_gc: CanGc) {
if self.closed.get() {
return;
}
let state: RTCSignalingState = state.convert();
if state == self.signaling_state.get() {
return;
}
self.signaling_state.set(state);
let event = Event::new(
&self.global(),
atom!("signalingstatechange"),
EventBubbles::DoesNotBubble,
EventCancelable::NotCancelable,
can_gc,
);
event.upcast::<Event>().fire(self.upcast(), can_gc);
}
fn create_offer(&self) {
let generation = self.offer_answer_generation.get();
let task_source = self
.global()
.task_manager()
.networking_task_source()
.to_sendable();
let this = Trusted::new(self);
self.controller
.borrow_mut()
.as_ref()
.unwrap()
.create_offer(Box::new(move |desc: SessionDescription| {
task_source.queue(task!(offer_created: move || {
let this = this.root();
if this.offer_answer_generation.get() != generation {
// the state has changed since we last created the offer,
// create a fresh one
this.create_offer();
} else {
let init: RTCSessionDescriptionInit = desc.convert();
for promise in this.offer_promises.borrow_mut().drain(..) {
promise.resolve_native(&init, CanGc::note());
}
}
}));
}));
}
fn create_answer(&self) {
let generation = self.offer_answer_generation.get();
let task_source = self
.global()
.task_manager()
.networking_task_source()
.to_sendable();
let this = Trusted::new(self);
self.controller
.borrow_mut()
.as_ref()
.unwrap()
.create_answer(Box::new(move |desc: SessionDescription| {
task_source.queue(task!(answer_created: move || {
let this = this.root();
if this.offer_answer_generation.get() != generation {
// the state has changed since we last created the offer,
// create a fresh one
this.create_answer();
} else {
let init: RTCSessionDescriptionInit = desc.convert();
for promise in this.answer_promises.borrow_mut().drain(..) {
promise.resolve_native(&init, CanGc::note());
}
}
}));
}));
}
}
impl RTCPeerConnectionMethods<crate::DomTypeHolder> for RTCPeerConnection {
// https://w3c.github.io/webrtc-pc/#dom-peerconnection
fn Constructor(
window: &Window,
proto: Option<HandleObject>,
can_gc: CanGc,
config: &RTCConfiguration,
) -> Fallible<DomRoot<RTCPeerConnection>> {
Ok(RTCPeerConnection::new(window, proto, config, can_gc))
}
// https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-icecandidate
event_handler!(icecandidate, GetOnicecandidate, SetOnicecandidate);
// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-ontrack
event_handler!(track, GetOntrack, SetOntrack);
// https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-iceconnectionstatechange
event_handler!(
iceconnectionstatechange,
GetOniceconnectionstatechange,
SetOniceconnectionstatechange
);
// https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-icegatheringstatechange
event_handler!(
icegatheringstatechange,
GetOnicegatheringstatechange,
SetOnicegatheringstatechange
);
// https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-onnegotiationneeded
event_handler!(
negotiationneeded,
GetOnnegotiationneeded,
SetOnnegotiationneeded
);
// https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-signalingstatechange
event_handler!(
signalingstatechange,
GetOnsignalingstatechange,
SetOnsignalingstatechange
);
// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-ondatachannel
event_handler!(datachannel, GetOndatachannel, SetOndatachannel);
/// <https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-addicecandidate>
fn AddIceCandidate(
&self,
candidate: &RTCIceCandidateInit,
comp: InRealm,
can_gc: CanGc,
) -> Rc<Promise> {
let p = Promise::new_in_current_realm(comp, can_gc);
if candidate.sdpMid.is_none() && candidate.sdpMLineIndex.is_none() {
p.reject_error(
Error::Type("one of sdpMid and sdpMLineIndex must be set".to_string()),
can_gc,
);
return p;
}
// XXXManishearth add support for sdpMid
if candidate.sdpMLineIndex.is_none() {
p.reject_error(
Error::Type("servo only supports sdpMLineIndex right now".to_string()),
can_gc,
);
return p;
}
// XXXManishearth this should be enqueued
// https://w3c.github.io/webrtc-pc/#enqueue-an-operation
self.controller
.borrow_mut()
.as_ref()
.unwrap()
.add_ice_candidate(IceCandidate {
sdp_mline_index: candidate.sdpMLineIndex.unwrap() as u32,
candidate: candidate.candidate.to_string(),
});
// XXXManishearth add_ice_candidate should have a callback
p.resolve_native(&(), can_gc);
p
}
/// <https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-createoffer>
fn CreateOffer(&self, _options: &RTCOfferOptions, comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
let p = Promise::new_in_current_realm(comp, can_gc);
if self.closed.get() {
p.reject_error(Error::InvalidState, can_gc);
return p;
}
self.offer_promises.borrow_mut().push(p.clone());
self.create_offer();
p
}
/// <https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-createoffer>
fn CreateAnswer(
&self,
_options: &RTCAnswerOptions,
comp: InRealm,
can_gc: CanGc,
) -> Rc<Promise> {
let p = Promise::new_in_current_realm(comp, can_gc);
if self.closed.get() {
p.reject_error(Error::InvalidState, can_gc);
return p;
}
self.answer_promises.borrow_mut().push(p.clone());
self.create_answer();
p
}
/// <https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-localdescription>
fn GetLocalDescription(&self) -> Option<DomRoot<RTCSessionDescription>> {
self.local_description.get()
}
/// <https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-remotedescription>
fn GetRemoteDescription(&self) -> Option<DomRoot<RTCSessionDescription>> {
self.remote_description.get()
}
/// <https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-setlocaldescription>
fn SetLocalDescription(
&self,
desc: &RTCSessionDescriptionInit,
comp: InRealm,
can_gc: CanGc,
) -> Rc<Promise> {
// XXXManishearth validate the current state
let p = Promise::new_in_current_realm(comp, can_gc);
let this = Trusted::new(self);
let desc: SessionDescription = desc.convert();
let trusted_promise = TrustedPromise::new(p.clone());
let task_source = self
.global()
.task_manager()
.networking_task_source()
.to_sendable();
self.controller
.borrow_mut()
.as_ref()
.unwrap()
.set_local_description(
desc.clone(),
Box::new(move || {
task_source.queue(task!(local_description_set: move || {
// XXXManishearth spec actually asks for an intricate
// dance between pending/current local/remote descriptions
let this = this.root();
let desc = desc.convert();
let desc = RTCSessionDescription::Constructor(
this.global().as_window(),
None,
CanGc::note(),
&desc,
).unwrap();
this.local_description.set(Some(&desc));
trusted_promise.root().resolve_native(&(), CanGc::note())
}));
}),
);
p
}
/// <https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-setremotedescription>
fn SetRemoteDescription(
&self,
desc: &RTCSessionDescriptionInit,
comp: InRealm,
can_gc: CanGc,
) -> Rc<Promise> {
// XXXManishearth validate the current state
let p = Promise::new_in_current_realm(comp, can_gc);
let this = Trusted::new(self);
let desc: SessionDescription = desc.convert();
let trusted_promise = TrustedPromise::new(p.clone());
let task_source = self
.global()
.task_manager()
.networking_task_source()
.to_sendable();
self.controller
.borrow_mut()
.as_ref()
.unwrap()
.set_remote_description(
desc.clone(),
Box::new(move || {
task_source.queue(task!(remote_description_set: move || {
// XXXManishearth spec actually asks for an intricate
// dance between pending/current local/remote descriptions
let this = this.root();
let desc = desc.convert();
let desc = RTCSessionDescription::Constructor(
this.global().as_window(),
None,
CanGc::note(),
&desc,
).unwrap();
this.remote_description.set(Some(&desc));
trusted_promise.root().resolve_native(&(), CanGc::note())
}));
}),
);
p
}
// https://w3c.github.io/webrtc-pc/#legacy-interface-extensions
fn AddStream(&self, stream: &MediaStream) {
for track in &*stream.get_tracks() {
self.controller
.borrow()
.as_ref()
.unwrap()
.add_stream(&track.id());
}
}
/// <https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-icegatheringstate>
fn IceGatheringState(&self) -> RTCIceGatheringState {
self.gathering_state.get()
}
/// <https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-iceconnectionstate>
fn IceConnectionState(&self) -> RTCIceConnectionState {
self.ice_connection_state.get()
}
/// <https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-signalingstate>
fn SignalingState(&self) -> RTCSignalingState {
self.signaling_state.get()
}
/// <https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close>
fn Close(&self, can_gc: CanGc) {
// Step 1
if self.closed.get() {
return;
}
// Step 2
self.closed.set(true);
// Step 4
self.signaling_state.set(RTCSignalingState::Closed);
// Step 5 handled by backend
self.controller.borrow_mut().as_ref().unwrap().quit();
// Step 6
for (_, val) in self.data_channels.borrow().iter() {
val.on_state_change(DataChannelState::Closed, can_gc);
}
// Step 7-10
// (no current support for transports, etc)
// Step 11
self.ice_connection_state.set(RTCIceConnectionState::Closed);
// Step 11
// (no current support for connection state)
}
/// <https://www.w3.org/TR/webrtc/#dom-peerconnection-createdatachannel>
fn CreateDataChannel(
&self,
label: USVString,
init: &RTCDataChannelInit,
) -> DomRoot<RTCDataChannel> {
RTCDataChannel::new(&self.global(), self, label, init, None, CanGc::note())
}
/// <https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-addtransceiver>
fn AddTransceiver(
&self,
_track_or_kind: MediaStreamTrackOrString,
init: &RTCRtpTransceiverInit,
) -> DomRoot<RTCRtpTransceiver> {
RTCRtpTransceiver::new(&self.global(), init.direction, CanGc::note())
}
}
impl Convert<RTCSessionDescriptionInit> for SessionDescription {
fn convert(self) -> RTCSessionDescriptionInit {
let type_ = match self.type_ {
SdpType::Answer => RTCSdpType::Answer,
SdpType::Offer => RTCSdpType::Offer,
SdpType::Pranswer => RTCSdpType::Pranswer,
SdpType::Rollback => RTCSdpType::Rollback,
};
RTCSessionDescriptionInit {
type_,
sdp: self.sdp.into(),
}
}
}
impl Convert<SessionDescription> for &RTCSessionDescriptionInit {
fn convert(self) -> SessionDescription {
let type_ = match self.type_ {
RTCSdpType::Answer => SdpType::Answer,
RTCSdpType::Offer => SdpType::Offer,
RTCSdpType::Pranswer => SdpType::Pranswer,
RTCSdpType::Rollback => SdpType::Rollback,
};
SessionDescription {
type_,
sdp: self.sdp.to_string(),
}
}
}
impl Convert<RTCIceGatheringState> for GatheringState {
fn convert(self) -> RTCIceGatheringState {
match self {
GatheringState::New => RTCIceGatheringState::New,
GatheringState::Gathering => RTCIceGatheringState::Gathering,
GatheringState::Complete => RTCIceGatheringState::Complete,
}
}
}
impl Convert<RTCIceConnectionState> for IceConnectionState {
fn convert(self) -> RTCIceConnectionState {
match self {
IceConnectionState::New => RTCIceConnectionState::New,
IceConnectionState::Checking => RTCIceConnectionState::Checking,
IceConnectionState::Connected => RTCIceConnectionState::Connected,
IceConnectionState::Completed => RTCIceConnectionState::Completed,
IceConnectionState::Disconnected => RTCIceConnectionState::Disconnected,
IceConnectionState::Failed => RTCIceConnectionState::Failed,
IceConnectionState::Closed => RTCIceConnectionState::Closed,
}
}
}
impl Convert<RTCSignalingState> for SignalingState {
fn convert(self) -> RTCSignalingState {
match self {
SignalingState::Stable => RTCSignalingState::Stable,
SignalingState::HaveLocalOffer => RTCSignalingState::Have_local_offer,
SignalingState::HaveRemoteOffer => RTCSignalingState::Have_remote_offer,
SignalingState::HaveLocalPranswer => RTCSignalingState::Have_local_pranswer,
SignalingState::HaveRemotePranswer => RTCSignalingState::Have_remote_pranswer,
SignalingState::Closed => RTCSignalingState::Closed,
}
}
}

View file

@ -0,0 +1,112 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use js::rust::HandleObject;
use stylo_atoms::Atom;
use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods;
use crate::dom::bindings::codegen::Bindings::RTCPeerConnectionIceEventBinding::{
RTCPeerConnectionIceEventInit, RTCPeerConnectionIceEventMethods,
};
use crate::dom::bindings::error::Fallible;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::reflect_dom_object_with_proto;
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::DOMString;
use crate::dom::event::Event;
use crate::dom::rtcicecandidate::RTCIceCandidate;
use crate::dom::window::Window;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct RTCPeerConnectionIceEvent {
event: Event,
candidate: Option<Dom<RTCIceCandidate>>,
url: Option<DOMString>,
}
impl RTCPeerConnectionIceEvent {
pub(crate) fn new_inherited(
candidate: Option<&RTCIceCandidate>,
url: Option<DOMString>,
) -> RTCPeerConnectionIceEvent {
RTCPeerConnectionIceEvent {
event: Event::new_inherited(),
candidate: candidate.map(Dom::from_ref),
url,
}
}
pub(crate) fn new(
window: &Window,
ty: Atom,
candidate: Option<&RTCIceCandidate>,
url: Option<DOMString>,
trusted: bool,
can_gc: CanGc,
) -> DomRoot<RTCPeerConnectionIceEvent> {
Self::new_with_proto(window, None, ty, candidate, url, trusted, can_gc)
}
fn new_with_proto(
window: &Window,
proto: Option<HandleObject>,
ty: Atom,
candidate: Option<&RTCIceCandidate>,
url: Option<DOMString>,
trusted: bool,
can_gc: CanGc,
) -> DomRoot<RTCPeerConnectionIceEvent> {
let e = reflect_dom_object_with_proto(
Box::new(RTCPeerConnectionIceEvent::new_inherited(candidate, url)),
window,
proto,
can_gc,
);
let evt = e.upcast::<Event>();
evt.init_event(ty, false, false); // XXXManishearth bubbles/cancelable?
evt.set_trusted(trusted);
e
}
}
impl RTCPeerConnectionIceEventMethods<crate::DomTypeHolder> for RTCPeerConnectionIceEvent {
/// <https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnectioniceevent-constructor>
fn Constructor(
window: &Window,
proto: Option<HandleObject>,
can_gc: CanGc,
ty: DOMString,
init: &RTCPeerConnectionIceEventInit,
) -> Fallible<DomRoot<RTCPeerConnectionIceEvent>> {
Ok(RTCPeerConnectionIceEvent::new_with_proto(
window,
proto,
ty.into(),
init.candidate
.as_ref()
.and_then(|x| x.as_ref())
.map(|x| &**x),
init.url.as_ref().and_then(|x| x.clone()),
false,
can_gc,
))
}
/// <https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnectioniceevent-candidate>
fn GetCandidate(&self) -> Option<DomRoot<RTCIceCandidate>> {
self.candidate.as_ref().map(|x| DomRoot::from_ref(&**x))
}
/// <https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnectioniceevent-url>
fn GetUrl(&self) -> Option<DOMString> {
self.url.clone()
}
/// <https://dom.spec.whatwg.org/#dom-event-istrusted>
fn IsTrusted(&self) -> bool {
self.event.IsTrusted()
}
}

View file

@ -0,0 +1,59 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use std::rc::Rc;
use dom_struct::dom_struct;
use crate::dom::bindings::codegen::Bindings::RTCRtpSenderBinding::{
RTCRtcpParameters, RTCRtpParameters, RTCRtpSendParameters, RTCRtpSenderMethods,
};
use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::globalscope::GlobalScope;
use crate::dom::promise::Promise;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct RTCRtpSender {
reflector_: Reflector,
}
impl RTCRtpSender {
fn new_inherited() -> Self {
Self {
reflector_: Reflector::new(),
}
}
pub(crate) fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<Self> {
reflect_dom_object(Box::new(Self::new_inherited()), global, can_gc)
}
}
impl RTCRtpSenderMethods<crate::DomTypeHolder> for RTCRtpSender {
// https://w3c.github.io/webrtc-pc/#dom-rtcrtpsender-getparameters
fn GetParameters(&self) -> RTCRtpSendParameters {
RTCRtpSendParameters {
parent: RTCRtpParameters {
headerExtensions: vec![],
rtcp: RTCRtcpParameters {
cname: None,
reducedSize: None,
},
codecs: vec![],
},
transactionId: DOMString::new(),
encodings: vec![],
}
}
// https://w3c.github.io/webrtc-pc/#dom-rtcrtpsender-setparameters
fn SetParameters(&self, _parameters: &RTCRtpSendParameters, can_gc: CanGc) -> Rc<Promise> {
let promise = Promise::new(&self.global(), can_gc);
promise.resolve_native(&(), can_gc);
promise
}
}

View file

@ -0,0 +1,67 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use std::cell::Cell;
use dom_struct::dom_struct;
use crate::dom::bindings::codegen::Bindings::RTCRtpTransceiverBinding::{
RTCRtpTransceiverDirection, RTCRtpTransceiverMethods,
};
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::globalscope::GlobalScope;
use crate::dom::rtcrtpsender::RTCRtpSender;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct RTCRtpTransceiver {
reflector_: Reflector,
sender: Dom<RTCRtpSender>,
direction: Cell<RTCRtpTransceiverDirection>,
}
impl RTCRtpTransceiver {
fn new_inherited(
global: &GlobalScope,
direction: RTCRtpTransceiverDirection,
can_gc: CanGc,
) -> Self {
let sender = RTCRtpSender::new(global, can_gc);
Self {
reflector_: Reflector::new(),
direction: Cell::new(direction),
sender: Dom::from_ref(&*sender),
}
}
pub(crate) fn new(
global: &GlobalScope,
direction: RTCRtpTransceiverDirection,
can_gc: CanGc,
) -> DomRoot<Self> {
reflect_dom_object(
Box::new(Self::new_inherited(global, direction, can_gc)),
global,
can_gc,
)
}
}
impl RTCRtpTransceiverMethods<crate::DomTypeHolder> for RTCRtpTransceiver {
/// <https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-direction>
fn Direction(&self) -> RTCRtpTransceiverDirection {
self.direction.get()
}
/// <https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-direction>
fn SetDirection(&self, direction: RTCRtpTransceiverDirection) {
self.direction.set(direction);
}
/// <https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-sender>
fn Sender(&self) -> DomRoot<RTCRtpSender> {
DomRoot::from_ref(&*self.sender)
}
}

View file

@ -0,0 +1,76 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use js::rust::HandleObject;
use crate::dom::bindings::codegen::Bindings::RTCSessionDescriptionBinding::{
RTCSdpType, RTCSessionDescriptionInit, RTCSessionDescriptionMethods,
};
use crate::dom::bindings::error::Fallible;
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::window::Window;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct RTCSessionDescription {
reflector: Reflector,
ty: RTCSdpType,
sdp: DOMString,
}
impl RTCSessionDescription {
pub(crate) fn new_inherited(ty: RTCSdpType, sdp: DOMString) -> RTCSessionDescription {
RTCSessionDescription {
reflector: Reflector::new(),
ty,
sdp,
}
}
fn new(
window: &Window,
proto: Option<HandleObject>,
ty: RTCSdpType,
sdp: DOMString,
can_gc: CanGc,
) -> DomRoot<RTCSessionDescription> {
reflect_dom_object_with_proto(
Box::new(RTCSessionDescription::new_inherited(ty, sdp)),
window,
proto,
can_gc,
)
}
}
impl RTCSessionDescriptionMethods<crate::DomTypeHolder> for RTCSessionDescription {
/// <https://w3c.github.io/webrtc-pc/#dom-sessiondescription>
fn Constructor(
window: &Window,
proto: Option<HandleObject>,
can_gc: CanGc,
config: &RTCSessionDescriptionInit,
) -> Fallible<DomRoot<RTCSessionDescription>> {
Ok(RTCSessionDescription::new(
window,
proto,
config.type_,
config.sdp.clone(),
can_gc,
))
}
/// <https://w3c.github.io/webrtc-pc/#dom-rtcsessiondescription-type>
fn Type(&self) -> RTCSdpType {
self.ty
}
/// <https://w3c.github.io/webrtc-pc/#dom-rtcsessiondescription-sdp>
fn Sdp(&self) -> DOMString {
self.sdp.clone()
}
}

View file

@ -0,0 +1,99 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use js::rust::HandleObject;
use stylo_atoms::Atom;
use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods;
use crate::dom::bindings::codegen::Bindings::RTCTrackEventBinding::{self, RTCTrackEventMethods};
use crate::dom::bindings::error::Fallible;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::reflect_dom_object_with_proto;
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::DOMString;
use crate::dom::event::Event;
use crate::dom::mediastreamtrack::MediaStreamTrack;
use crate::dom::window::Window;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct RTCTrackEvent {
event: Event,
track: Dom<MediaStreamTrack>,
}
impl RTCTrackEvent {
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
fn new_inherited(track: &MediaStreamTrack) -> RTCTrackEvent {
RTCTrackEvent {
event: Event::new_inherited(),
track: Dom::from_ref(track),
}
}
pub(crate) fn new(
window: &Window,
type_: Atom,
bubbles: bool,
cancelable: bool,
track: &MediaStreamTrack,
can_gc: CanGc,
) -> DomRoot<RTCTrackEvent> {
Self::new_with_proto(window, None, type_, bubbles, cancelable, track, can_gc)
}
fn new_with_proto(
window: &Window,
proto: Option<HandleObject>,
type_: Atom,
bubbles: bool,
cancelable: bool,
track: &MediaStreamTrack,
can_gc: CanGc,
) -> DomRoot<RTCTrackEvent> {
let trackevent = reflect_dom_object_with_proto(
Box::new(RTCTrackEvent::new_inherited(track)),
window,
proto,
can_gc,
);
{
let event = trackevent.upcast::<Event>();
event.init_event(type_, bubbles, cancelable);
}
trackevent
}
}
impl RTCTrackEventMethods<crate::DomTypeHolder> for RTCTrackEvent {
// https://w3c.github.io/webrtc-pc/#dom-rtctrackevent-constructor
fn Constructor(
window: &Window,
proto: Option<HandleObject>,
can_gc: CanGc,
type_: DOMString,
init: &RTCTrackEventBinding::RTCTrackEventInit,
) -> Fallible<DomRoot<RTCTrackEvent>> {
Ok(RTCTrackEvent::new_with_proto(
window,
proto,
Atom::from(type_),
init.parent.bubbles,
init.parent.cancelable,
&init.track,
can_gc,
))
}
// https://w3c.github.io/webrtc-pc/#dom-rtctrackevent-track
fn Track(&self) -> DomRoot<MediaStreamTrack> {
DomRoot::from_ref(&*self.track)
}
// https://dom.spec.whatwg.org/#dom-event-istrusted
fn IsTrusted(&self) -> bool {
self.event.IsTrusted()
}
}