web: Use gloo_net instead of manually using web_sys

This relieves some headaches with on connect callback and spawning of the
sending async task, since some SWFs like to send data before the connect event
is called.
This commit is contained in:
sleepycatcoding 2023-09-08 02:14:17 +03:00 committed by Nathan Adams
parent a067e9a5e8
commit d3765027f0
3 changed files with 65 additions and 56 deletions

32
Cargo.lock generated
View File

@ -2031,6 +2031,36 @@ dependencies = [
"regex", "regex",
] ]
[[package]]
name = "gloo-net"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ac9e8288ae2c632fa9f8657ac70bfe38a1530f345282d7ba66a1f70b72b7dc4"
dependencies = [
"futures-channel",
"futures-core",
"futures-sink",
"gloo-utils",
"http",
"js-sys",
"pin-project",
"thiserror",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "gloo-utils"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa"
dependencies = [
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]] [[package]]
name = "glow" name = "glow"
version = "0.12.3" version = "0.12.3"
@ -4217,8 +4247,10 @@ dependencies = [
"base64", "base64",
"chrono", "chrono",
"console_error_panic_hook", "console_error_panic_hook",
"futures-util",
"generational-arena", "generational-arena",
"getrandom", "getrandom",
"gloo-net",
"js-sys", "js-sys",
"ruffle_core", "ruffle_core",
"ruffle_render", "ruffle_render",

View File

@ -50,6 +50,8 @@ serde = { version = "1.0.188", features = ["derive"] }
thiserror = "1.0" thiserror = "1.0"
base64 = "0.21.4" base64 = "0.21.4"
async-channel = "1.9.0" async-channel = "1.9.0"
futures-util = { version = "0.3.28", features = ["sink"] }
gloo-net = { version = "0.4.0", default-features = false, features = ["websocket"] }
[dependencies.ruffle_core] [dependencies.ruffle_core]
path = "../core" path = "../core"
@ -64,5 +66,5 @@ features = [
"ChannelMergerNode", "ChannelSplitterNode", "ClipboardEvent", "DataTransfer", "Element", "Event", "ChannelMergerNode", "ChannelSplitterNode", "ClipboardEvent", "DataTransfer", "Element", "Event",
"EventTarget", "GainNode", "Headers", "HtmlCanvasElement", "HtmlDocument", "HtmlElement", "HtmlFormElement", "EventTarget", "GainNode", "Headers", "HtmlCanvasElement", "HtmlDocument", "HtmlElement", "HtmlFormElement",
"HtmlInputElement", "HtmlTextAreaElement", "KeyboardEvent", "Location", "PointerEvent", "HtmlInputElement", "HtmlTextAreaElement", "KeyboardEvent", "Location", "PointerEvent",
"Request", "RequestInit", "Response", "Storage", "WheelEvent", "Window", "ProgressEvent", "WebSocket", "MessageEvent", "BinaryType", "ErrorEvent", "CloseEvent", "Request", "RequestInit", "Response", "Storage", "WheelEvent", "Window",
] ]

View File

@ -1,6 +1,8 @@
//! Navigator backend for web //! Navigator backend for web
use crate::WebSocketProxy; use crate::WebSocketProxy;
use async_channel::Receiver; use async_channel::Receiver;
use futures_util::{SinkExt, StreamExt};
use gloo_net::websocket::{futures::WebSocket, Message};
use js_sys::{Array, ArrayBuffer, Uint8Array}; use js_sys::{Array, ArrayBuffer, Uint8Array};
use ruffle_core::backend::navigator::{ use ruffle_core::backend::navigator::{
async_return, create_fetch_error, create_specific_fetch_error, ErrorResponse, NavigationMethod, async_return, create_fetch_error, create_specific_fetch_error, ErrorResponse, NavigationMethod,
@ -17,12 +19,11 @@ use tracing_subscriber::layer::Layered;
use tracing_subscriber::Registry; use tracing_subscriber::Registry;
use tracing_wasm::WASMLayer; use tracing_wasm::WASMLayer;
use url::{ParseError, Url}; use url::{ParseError, Url};
use wasm_bindgen::prelude::Closure;
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
use wasm_bindgen_futures::{spawn_local, JsFuture}; use wasm_bindgen_futures::{spawn_local, JsFuture};
use web_sys::{ use web_sys::{
window, Blob, BlobPropertyBag, CloseEvent, ErrorEvent, HtmlFormElement, HtmlInputElement, window, Blob, BlobPropertyBag, HtmlFormElement, HtmlInputElement, Request as WebRequest,
MessageEvent, Request as WebRequest, RequestInit, Response as WebResponse, WebSocket, RequestInit, Response as WebResponse,
}; };
pub struct WebNavigatorBackend { pub struct WebNavigatorBackend {
@ -368,6 +369,7 @@ impl NavigatorBackend for WebNavigatorBackend {
fn connect_socket( fn connect_socket(
&mut self, &mut self,
host: String, host: String,
port: u16, port: u16,
// NOTE: WebSocket does not allow specifying a timeout, so this goes unused. // NOTE: WebSocket does not allow specifying a timeout, so this goes unused.
_timeout: Duration, _timeout: Duration,
@ -389,7 +391,7 @@ impl NavigatorBackend for WebNavigatorBackend {
tracing::info!("Connecting to {}", proxy.proxy_url); tracing::info!("Connecting to {}", proxy.proxy_url);
let ws = match WebSocket::new(&proxy.proxy_url) { let ws = match WebSocket::open(&proxy.proxy_url) {
Ok(x) => x, Ok(x) => x,
Err(e) => { Err(e) => {
tracing::error!("Failed to create WebSocket, reason {:?}", e); tracing::error!("Failed to create WebSocket, reason {:?}", e);
@ -400,70 +402,43 @@ impl NavigatorBackend for WebNavigatorBackend {
} }
}; };
ws.set_binary_type(web_sys::BinaryType::Arraybuffer); let (mut sink, mut stream) = ws.split();
sender
let sender_onopen = sender.clone();
let onopen_callback = Closure::<dyn FnMut()>::new(move || {
sender_onopen
.send(SocketAction::Connect(handle, ConnectionState::Connected)) .send(SocketAction::Connect(handle, ConnectionState::Connected))
.expect("Working channel send"); .expect("working channel send");
});
let sender_onmessage = sender.clone(); // Spawn future to handle incoming messages.
let onmessage_callback = Closure::<dyn FnMut(_)>::new(move |e: MessageEvent| { let stream_sender = sender.clone();
if let Ok(buf) = e.data().dyn_into::<js_sys::ArrayBuffer>() { self.spawn_future(Box::pin(async move {
// Handle array buffer. while let Some(msg) = stream.next().await {
let array = js_sys::Uint8Array::new(&buf); match msg {
sender_onmessage Ok(Message::Bytes(buf)) => stream_sender
.send(SocketAction::Data(handle, array.to_vec())) .send(SocketAction::Data(handle, buf))
.expect("Working channel send"); .expect("working channel send"),
} else if let Ok(_) = e.data().dyn_into::<web_sys::Blob>() { Ok(_) => tracing::warn!("Server sent unexpected text message"),
todo!("Unsupported blob payload"); Err(_) => {
} else { stream_sender
todo!("Unsupported WebSocket payload"); .send(SocketAction::Close(handle))
.expect("working channel send");
return Ok(());
}
}
} }
});
let sender_onclose = sender.clone(); Ok(())
let onclose_callback = Closure::<dyn FnMut(_)>::new(move |_e: CloseEvent| { }));
sender_onclose
.send(SocketAction::Close(handle))
.expect("working channel send");
});
let sender_onerror = sender.clone();
let onerror_callback = Closure::<dyn FnMut(_)>::new(move |_e: ErrorEvent| {
sender_onerror
.send(SocketAction::Close(handle))
.expect("working channel send");
});
ws.set_onopen(Some(onopen_callback.as_ref().unchecked_ref()));
ws.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
ws.set_onclose(Some(onclose_callback.as_ref().unchecked_ref()));
ws.set_onerror(Some(onerror_callback.as_ref().unchecked_ref()));
onopen_callback.forget();
onmessage_callback.forget();
onclose_callback.forget();
onerror_callback.forget();
// Spawn future to handle outgoing messages. // Spawn future to handle outgoing messages.
self.spawn_future(Box::pin(async move { self.spawn_future(Box::pin(async move {
while let Ok(msg) = receiver.recv().await { while let Ok(msg) = receiver.recv().await {
if let Err(e) = ws.send_with_u8_array(&msg) { if let Err(e) = sink.send(Message::Bytes(msg)).await {
tracing::error!("Failed to send message to WebSocket {:?}", e); tracing::warn!("Failed to send message to WebSocket {}", e);
sender sender
.send(SocketAction::Close(handle)) .send(SocketAction::Close(handle))
.expect("working channel send"); .expect("working channel send");
} }
} }
// Close WebSocket as the sender was dropped.
if let Err(e) = ws.close() {
tracing::error!("Failed to close WebSocket connection {:?}", e);
}
Ok(()) Ok(())
})); }));
} }