diff --git a/components/script/dom/promise.rs b/components/script/dom/promise.rs index 0ef2a86b2de..57fb08aee65 100644 --- a/components/script/dom/promise.rs +++ b/components/script/dom/promise.rs @@ -73,6 +73,9 @@ impl PromiseHelper for Rc { } } +// Promise objects are stored inside Rc values, so Drop is run when the last Rc is dropped, +// rather than when SpiderMonkey runs a GC. This makes it safe to interact with the JS engine unlike +// Drop implementations for other DOM types. impl Drop for Promise { #[allow(unsafe_code)] fn drop(&mut self) { diff --git a/components/script/dom/rtcdatachannel.rs b/components/script/dom/rtcdatachannel.rs index 1aad3ef6794..2f9d962dd87 100644 --- a/components/script/dom/rtcdatachannel.rs +++ b/components/script/dom/rtcdatachannel.rs @@ -12,6 +12,7 @@ use js::jsapi::{JSAutoRealm, JSObject}; use js::jsval::UndefinedValue; use js::rust::CustomAutoRooterGuard; use js::typedarray::{ArrayBuffer, ArrayBufferView, CreateWith}; +use script_bindings::weakref::WeakRef; use servo_media::webrtc::{ DataChannelId, DataChannelInit, DataChannelMessage, DataChannelState, WebRtcError, }; @@ -37,12 +38,37 @@ 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, +} + +impl DroppableRTCDataChannel { + fn new(peer_connection: WeakRef, 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, - #[ignore_malloc_size_of = "defined in servo-media"] - servo_media_id: DataChannelId, - peer_connection: Dom, label: USVString, ordered: bool, max_packet_life_time: Option, @@ -52,6 +78,8 @@ pub(crate) struct RTCDataChannel { id: Option, ready_state: Cell, binary_type: DomRefCell, + peer_connection: Dom, + droppable: DroppableRTCDataChannel, } impl RTCDataChannel { @@ -76,8 +104,6 @@ impl RTCDataChannel { RTCDataChannel { eventtarget: EventTarget::new_inherited(), - servo_media_id, - peer_connection: Dom::from_ref(peer_connection), label, ordered: options.ordered, max_packet_life_time: options.maxPacketLifeTime, @@ -87,6 +113,8 @@ impl RTCDataChannel { 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), } } @@ -109,11 +137,16 @@ impl RTCDataChannel { can_gc, ); - peer_connection.register_data_channel(rtc_data_channel.servo_media_id, &rtc_data_channel); + 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(), @@ -136,7 +169,7 @@ impl RTCDataChannel { event.upcast::().fire(self.upcast(), can_gc); self.peer_connection - .unregister_data_channel(&self.servo_media_id); + .unregister_data_channel(&self.get_servo_media_id()); } pub(crate) fn on_error(&self, error: WebRtcError, can_gc: CanGc) { @@ -242,19 +275,12 @@ impl RTCDataChannel { controller .as_ref() .unwrap() - .send_data_channel_message(&self.servo_media_id, message); + .send_data_channel_message(&self.get_servo_media_id(), message); Ok(()) } } -impl Drop for RTCDataChannel { - fn drop(&mut self) { - self.peer_connection - .unregister_data_channel(&self.servo_media_id); - } -} - enum SendSource<'a, 'b> { String(&'a USVString), Blob(&'a Blob), @@ -332,7 +358,7 @@ impl RTCDataChannelMethods for RTCDataChannel { controller .as_ref() .unwrap() - .close_data_channel(&self.servo_media_id); + .close_data_channel(&self.get_servo_media_id()); } // https://www.w3.org/TR/webrtc/#dom-datachannel-binarytype diff --git a/components/script/dom/rtcpeerconnection.rs b/components/script/dom/rtcpeerconnection.rs index b96634ac95e..2282846dc7a 100644 --- a/components/script/dom/rtcpeerconnection.rs +++ b/components/script/dom/rtcpeerconnection.rs @@ -8,6 +8,7 @@ use std::rc::Rc; use dom_struct::dom_struct; use js::rust::HandleObject; +use script_bindings::weakref::WeakReferenceable; use servo_media::ServoMedia; use servo_media::streams::MediaStreamType; use servo_media::streams::registry::MediaStreamId; @@ -309,15 +310,16 @@ impl RTCPeerConnection { event.upcast::().fire(self.upcast(), can_gc); }, _ => { - let channel = 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; - }; + let channel: DomRoot = + 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), @@ -789,6 +791,8 @@ impl RTCPeerConnectionMethods for RTCPeerConnection { } } +impl WeakReferenceable for RTCPeerConnection {} + impl Convert for SessionDescription { fn convert(self) -> RTCSessionDescriptionInit { let type_ = match self.type_ { diff --git a/components/script_bindings/codegen/Bindings.conf b/components/script_bindings/codegen/Bindings.conf index d40070622e9..80f28869d91 100644 --- a/components/script_bindings/codegen/Bindings.conf +++ b/components/script_bindings/codegen/Bindings.conf @@ -559,6 +559,10 @@ DOMInterfaces = { 'canGc': ['Error', 'Redirect', 'Clone', 'CreateFromJson', 'Text', 'Blob', 'FormData', 'Json', 'ArrayBuffer', 'Headers', 'Bytes'], }, +'RTCDataChannel': { + 'weakReferenceable': True, +}, + 'RTCPeerConnection': { 'inRealms': ['AddIceCandidate', 'CreateAnswer', 'CreateOffer', 'SetLocalDescription', 'SetRemoteDescription'], 'canGc': ['Close', 'AddIceCandidate', 'CreateAnswer', 'CreateOffer', 'SetLocalDescription', 'SetRemoteDescription'],