WebTransport.cpp (40072B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "WebTransport.h" 8 9 #include "WebTransportBidirectionalStream.h" 10 #include "mozilla/Assertions.h" 11 #include "mozilla/RefPtr.h" 12 #include "mozilla/dom/DOMExceptionBinding.h" 13 #include "mozilla/dom/Document.h" 14 #include "mozilla/dom/PWebTransport.h" 15 #include "mozilla/dom/Promise.h" 16 #include "mozilla/dom/ReadableStream.h" 17 #include "mozilla/dom/ReadableStreamDefaultController.h" 18 #include "mozilla/dom/RemoteWorkerChild.h" 19 #include "mozilla/dom/WebTransportDatagramDuplexStream.h" 20 #include "mozilla/dom/WebTransportError.h" 21 #include "mozilla/dom/WebTransportLog.h" 22 #include "mozilla/dom/WindowGlobalChild.h" 23 #include "mozilla/dom/WorkerPrivate.h" 24 #include "mozilla/dom/WorkerRunnable.h" 25 #include "mozilla/dom/WritableStream.h" 26 #include "mozilla/ipc/BackgroundChild.h" 27 #include "mozilla/ipc/Endpoint.h" 28 #include "mozilla/ipc/PBackgroundChild.h" 29 #include "nsIURL.h" 30 #include "nsIWebTransportStream.h" 31 #include "nsUTF8Utils.h" 32 33 using namespace mozilla::ipc; 34 35 namespace mozilla::dom { 36 37 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(WebTransport) 38 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WebTransport) 39 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal) 40 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncomingUnidirectionalStreams) 41 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncomingBidirectionalStreams) 42 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncomingUnidirectionalAlgorithm) 43 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncomingBidirectionalAlgorithm) 44 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDatagrams) 45 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReady) 46 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mClosed) 47 for (const auto& hashEntry : tmp->mSendStreams.Values()) { 48 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSendStreams entry item"); 49 cb.NoteXPCOMChild(hashEntry); 50 } 51 for (const auto& hashEntry : tmp->mReceiveStreams.Values()) { 52 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mReceiveStreams entry item"); 53 cb.NoteXPCOMChild(hashEntry); 54 } 55 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 56 57 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WebTransport) 58 tmp->mSendStreams.Clear(); 59 tmp->mReceiveStreams.Clear(); 60 NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal) 61 NS_IMPL_CYCLE_COLLECTION_UNLINK(mUnidirectionalStreams) 62 NS_IMPL_CYCLE_COLLECTION_UNLINK(mBidirectionalStreams) 63 NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncomingUnidirectionalStreams) 64 NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncomingBidirectionalStreams) 65 NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncomingUnidirectionalAlgorithm) 66 NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncomingBidirectionalAlgorithm) 67 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDatagrams) 68 NS_IMPL_CYCLE_COLLECTION_UNLINK(mReady) 69 NS_IMPL_CYCLE_COLLECTION_UNLINK(mClosed) 70 if (tmp->mChild) { 71 tmp->mChild->Shutdown(false); 72 tmp->mChild = nullptr; 73 } 74 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER 75 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 76 77 NS_IMPL_CYCLE_COLLECTING_ADDREF(WebTransport) 78 NS_IMPL_CYCLE_COLLECTING_RELEASE(WebTransport) 79 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebTransport) 80 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 81 NS_INTERFACE_MAP_ENTRY(nsISupports) 82 NS_INTERFACE_MAP_END 83 84 WebTransport::WebTransport(nsIGlobalObject* aGlobal) 85 : mGlobal(aGlobal), 86 mState(WebTransportState::CONNECTING), 87 mReliability(WebTransportReliabilityMode::Pending) { 88 LOG(("Creating WebTransport %p", this)); 89 } 90 91 WebTransport::~WebTransport() { 92 // Should be empty by this point, because we should always have run cleanup: 93 // https://w3c.github.io/webtransport/#webtransport-procedures 94 LOG(("~WebTransport() for %p", this)); 95 MOZ_ASSERT(mSendStreams.IsEmpty()); 96 MOZ_ASSERT(mReceiveStreams.IsEmpty()); 97 // If this WebTransport was destroyed without being closed properly, make 98 // sure to clean up the channel. 99 // Since child has a raw ptr to us, we MUST call Shutdown() before we're 100 // destroyed 101 if (mChild) { 102 mChild->Shutdown(true); 103 } 104 } 105 106 // From parent 107 void WebTransport::NewBidirectionalStream( 108 uint64_t aStreamId, const RefPtr<DataPipeReceiver>& aIncoming, 109 const RefPtr<DataPipeSender>& aOutgoing) { 110 LOG_VERBOSE(("NewBidirectionalStream()")); 111 // Create a Bidirectional stream and push it into the 112 // IncomingBidirectionalStreams stream. Must be added to the ReceiveStreams 113 // and SendStreams arrays 114 115 UniquePtr<BidirectionalPair> streams( 116 new BidirectionalPair(aIncoming, aOutgoing)); 117 auto tuple = std::tuple<uint64_t, UniquePtr<BidirectionalPair>>( 118 aStreamId, std::move(streams)); 119 mBidirectionalStreams.AppendElement(std::move(tuple)); 120 // We need to delete them all! 121 122 // Notify something to wake up readers of IncomingReceiveStreams 123 // The callback is always set/used from the same thread (MainThread or a 124 // Worker thread). 125 if (mIncomingBidirectionalAlgorithm) { 126 RefPtr<WebTransportIncomingStreamsAlgorithms> callback = 127 mIncomingBidirectionalAlgorithm; 128 LOG(("NotifyIncomingStream")); 129 callback->NotifyIncomingStream(); 130 } 131 } 132 133 void WebTransport::NewUnidirectionalStream( 134 uint64_t aStreamId, const RefPtr<mozilla::ipc::DataPipeReceiver>& aStream) { 135 LOG_VERBOSE(("NewUnidirectionalStream()")); 136 // Create a Unidirectional stream and push it into the 137 // IncomingUnidirectionalStreams stream. Must be added to the ReceiveStreams 138 // array 139 140 mUnidirectionalStreams.AppendElement( 141 std::tuple<uint64_t, RefPtr<mozilla::ipc::DataPipeReceiver>>(aStreamId, 142 aStream)); 143 // Notify something to wake up readers of IncomingReceiveStreams 144 // The callback is always set/used from the same thread (MainThread or a 145 // Worker thread). 146 if (mIncomingUnidirectionalAlgorithm) { 147 RefPtr<WebTransportIncomingStreamsAlgorithms> callback = 148 mIncomingUnidirectionalAlgorithm; 149 LOG(("NotifyIncomingStream")); 150 callback->NotifyIncomingStream(); 151 } 152 } 153 154 void WebTransport::NewDatagramReceived(nsTArray<uint8_t>&& aData, 155 const mozilla::TimeStamp& aTimeStamp) { 156 mDatagrams->NewDatagramReceived(std::move(aData), aTimeStamp); 157 } 158 159 // WebIDL Boilerplate 160 161 nsIGlobalObject* WebTransport::GetParentObject() const { return mGlobal; } 162 163 JSObject* WebTransport::WrapObject(JSContext* aCx, 164 JS::Handle<JSObject*> aGivenProto) { 165 return WebTransport_Binding::Wrap(aCx, this, aGivenProto); 166 } 167 168 // WebIDL Interface 169 170 /* static */ 171 already_AddRefed<WebTransport> WebTransport::Constructor( 172 const GlobalObject& aGlobal, const nsAString& aURL, 173 const WebTransportOptions& aOptions, ErrorResult& aError) { 174 LOG(("Creating WebTransport for %s", NS_ConvertUTF16toUTF8(aURL).get())); 175 // https://w3c.github.io/webtransport/#webtransport-constructor 176 177 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); 178 RefPtr<WebTransport> result = new WebTransport(global); 179 result->Init(aGlobal, aURL, aOptions, aError); 180 if (aError.Failed()) { 181 return nullptr; 182 } 183 184 // Don't let this document go into BFCache 185 result->NotifyToWindow(true); 186 187 // Step 25 Return transport 188 return result.forget(); 189 } 190 191 void WebTransport::Init(const GlobalObject& aGlobal, const nsAString& aURL, 192 const WebTransportOptions& aOptions, 193 ErrorResult& aError) { 194 // https://w3c.github.io/webtransport/#webtransport-constructor 195 // Initiate connection with parent 196 using mozilla::ipc::BackgroundChild; 197 using mozilla::ipc::Endpoint; 198 using mozilla::ipc::PBackgroundChild; 199 200 // https://w3c.github.io/webtransport/#webtransport-constructor 201 // Steps 1-4: Parse string for validity and Throw a SyntaxError if it isn't 202 // Let parsedURL be the URL record resulting from parsing url. 203 // If parsedURL is a failure, throw a SyntaxError exception. 204 // If parsedURL scheme is not https, throw a SyntaxError exception. 205 // If parsedURL fragment is not null, throw a SyntaxError exception. 206 if (!ParseURL(aURL)) { 207 aError.ThrowSyntaxError("Invalid WebTransport URL"); 208 return; 209 } 210 // Step 5: Let allowPooling be options's allowPooling if it exists, and false 211 // otherwise. 212 // Step 6: Let dedicated be the negation of allowPooling. 213 bool dedicated = !aOptions.mAllowPooling; 214 // Step 7: Let serverCertificateHashes be options's serverCertificateHashes if 215 // it exists, and null otherwise. 216 // Step 8: If dedicated is false and serverCertificateHashes is non-null, 217 // then throw a TypeError. 218 nsTArray<mozilla::ipc::WebTransportHash> aServerCertHashes; 219 if (aOptions.mServerCertificateHashes.WasPassed()) { 220 if (!dedicated) { 221 aError.ThrowNotSupportedError( 222 "serverCertificateHashes not supported for non-dedicated " 223 "connections"); 224 return; 225 } 226 for (const auto& hash : aOptions.mServerCertificateHashes.Value()) { 227 if (!hash.mAlgorithm.WasPassed() || !hash.mValue.WasPassed()) continue; 228 229 if (hash.mAlgorithm.Value() != u"sha-256") { 230 LOG(("Algorithms other than SHA-256 are not supported")); 231 continue; 232 } 233 234 nsTArray<uint8_t> data; 235 if (!AppendTypedArrayDataTo(hash.mValue.Value(), data)) { 236 aError.Throw(NS_ERROR_OUT_OF_MEMORY); 237 return; 238 } 239 240 nsCString alg = NS_ConvertUTF16toUTF8(hash.mAlgorithm.Value()); 241 aServerCertHashes.EmplaceBack(alg, data); 242 } 243 } 244 // Step 9: Let requireUnreliable be options's requireUnreliable. 245 bool requireUnreliable = aOptions.mRequireUnreliable; 246 // Step 10: Let congestionControl be options's congestionControl. 247 // Step 11: If congestionControl is not "default", and the user agent 248 // does not support any congestion control algorithms that optimize for 249 // congestionControl, as allowed by [RFC9002] section 7, then set 250 // congestionControl to "default". 251 WebTransportCongestionControl congestionControl = 252 WebTransportCongestionControl::Default; // aOptions.mCongestionControl; 253 // Set this to 'default' until we add congestion control setting 254 255 // Setup up WebTransportDatagramDuplexStream 256 // Step 12: Let incomingDatagrams be a new ReadableStream. 257 // Step 13: Let outgoingDatagrams be a new WritableStream. 258 // Step 14: Let datagrams be the result of creating a 259 // WebTransportDatagramDuplexStream, its readable set to 260 // incomingDatagrams and its writable set to outgoingDatagrams. 261 mDatagrams = new WebTransportDatagramDuplexStream(mGlobal, this); 262 mDatagrams->Init(aError); 263 if (aError.Failed()) { 264 return; 265 } 266 267 // TODO: Fix the shared and service worker use cases 268 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); 269 mService = workerPrivate && (workerPrivate->IsSharedWorker() || 270 workerPrivate->IsServiceWorker()) 271 ? nullptr 272 : net::WebTransportEventService::GetOrCreate(); 273 // XXX TODO 274 275 // Step 15 Let transport be a newly constructed WebTransport object, with: 276 // SendStreams: empty ordered set 277 // ReceiveStreams: empty ordered set 278 // Ready: new promise 279 mReady = Promise::CreateInfallible(mGlobal); 280 281 // Closed: new promise 282 mClosed = Promise::CreateInfallible(mGlobal); 283 284 PBackgroundChild* backgroundChild = 285 BackgroundChild::GetOrCreateForCurrentThread(); 286 if (NS_WARN_IF(!backgroundChild)) { 287 aError.Throw(NS_ERROR_FAILURE); 288 return; 289 } 290 291 nsCOMPtr<nsIPrincipal> principal = mGlobal->PrincipalOrNull(); 292 mozilla::Maybe<IPCClientInfo> ipcClientInfo; 293 294 if (mGlobal->GetClientInfo().isSome()) { 295 ipcClientInfo = mozilla::Some(mGlobal->GetClientInfo().ref().ToIPC()); 296 } 297 298 nsPIDOMWindowInner* window = mGlobal->GetAsInnerWindow(); 299 if (window) { 300 mBrowsingContextID = window->GetBrowsingContext()->Id(); 301 } 302 // Create a new IPC connection 303 Endpoint<PWebTransportParent> parentEndpoint; 304 Endpoint<PWebTransportChild> childEndpoint; 305 MOZ_ALWAYS_SUCCEEDS( 306 PWebTransport::CreateEndpoints(&parentEndpoint, &childEndpoint)); 307 308 RefPtr<WebTransportChild> child = new WebTransportChild(this); 309 if (NS_IsMainThread()) { 310 if (!childEndpoint.Bind(child)) { 311 aError.Throw(NS_ERROR_FAILURE); 312 return; 313 } 314 } else if (!childEndpoint.Bind(child, mGlobal->SerialEventTarget())) { 315 aError.Throw(NS_ERROR_FAILURE); 316 return; 317 } 318 319 mState = WebTransportState::CONNECTING; 320 321 JSContext* cx = aGlobal.Context(); 322 // Set up Datagram streams 323 // Step 16: Let pullDatagramsAlgorithm be an action that runs pullDatagrams 324 // with transport. 325 // Step 17: Let writeDatagramsAlgorithm be an action that runs writeDatagrams 326 // with transport. 327 // Step 18: Set up incomingDatagrams with pullAlgorithm set to 328 // pullDatagramsAlgorithm, and highWaterMark set to 0. 329 // Step 19: Set up outgoingDatagrams with writeAlgorithm set to 330 // writeDatagramsAlgorithm. 331 332 // XXX TODO 333 334 // Step 20: Let pullBidirectionalStreamAlgorithm be an action that runs 335 // pullBidirectionalStream with transport. 336 // Step 21: Set up transport.[[IncomingBidirectionalStreams]] with 337 // pullAlgorithm set to pullBidirectionalStreamAlgorithm, and highWaterMark 338 // set to 0. 339 Optional<JS::Handle<JSObject*>> underlying; 340 // Suppress warnings about risk of mGlobal getting nulled during script. 341 // We set the global from the aGlobalObject parameter of the constructor, so 342 // it must still be set here. 343 const nsCOMPtr<nsIGlobalObject> global(mGlobal); 344 345 mIncomingBidirectionalAlgorithm = new WebTransportIncomingStreamsAlgorithms( 346 WebTransportIncomingStreamsAlgorithms::StreamType::Bidirectional, this); 347 348 RefPtr<WebTransportIncomingStreamsAlgorithms> algorithm = 349 mIncomingBidirectionalAlgorithm; 350 mIncomingBidirectionalStreams = ReadableStream::CreateNative( 351 cx, global, *algorithm, Some(0.0), nullptr, aError); 352 if (aError.Failed()) { 353 return; 354 } 355 // Step 22: Let pullUnidirectionalStreamAlgorithm be an action that runs 356 // pullUnidirectionalStream with transport. 357 // Step 23: Set up transport.[[IncomingUnidirectionalStreams]] with 358 // pullAlgorithm set to pullUnidirectionalStreamAlgorithm, and highWaterMark 359 // set to 0. 360 361 mIncomingUnidirectionalAlgorithm = new WebTransportIncomingStreamsAlgorithms( 362 WebTransportIncomingStreamsAlgorithms::StreamType::Unidirectional, this); 363 364 algorithm = mIncomingUnidirectionalAlgorithm; 365 mIncomingUnidirectionalStreams = ReadableStream::CreateNative( 366 cx, global, *algorithm, Some(0.0), nullptr, aError); 367 if (aError.Failed()) { 368 return; 369 } 370 371 // Step 24: Initialize WebTransport over HTTP with transport, parsedURL, 372 // dedicated, requireUnreliable, and congestionControl. 373 LOG(("Connecting WebTransport to parent for %s", 374 NS_ConvertUTF16toUTF8(aURL).get())); 375 376 // https://w3c.github.io/webtransport/#webtransport-constructor Spec 5.2 377 mChild = child; 378 backgroundChild 379 ->SendCreateWebTransportParent( 380 aURL, principal, mBrowsingContextID, ipcClientInfo, dedicated, 381 requireUnreliable, (uint32_t)congestionControl, 382 std::move(aServerCertHashes), std::move(parentEndpoint)) 383 ->Then(GetCurrentSerialEventTarget(), __func__, 384 [self = RefPtr{this}]( 385 PBackgroundChild::CreateWebTransportParentPromise:: 386 ResolveOrRejectValue&& aResult) { 387 // aResult is a std::tuple<nsresult, uint8_t> 388 // TODO: is there a better/more-spec-compliant error in the 389 // reject case? Which begs the question, why would we get a 390 // reject? 391 nsresult rv = aResult.IsReject() 392 ? NS_ERROR_FAILURE 393 : std::get<0>(aResult.ResolveValue()); 394 LOG(("isreject: %d nsresult 0x%x", aResult.IsReject(), 395 (uint32_t)rv)); 396 if (NS_FAILED(rv)) { 397 self->RejectWaitingConnection(rv); 398 } else { 399 // This will process anything waiting for the connection to 400 // complete; 401 402 self->ResolveWaitingConnection( 403 static_cast<WebTransportReliabilityMode>( 404 std::get<1>(aResult.ResolveValue()))); 405 } 406 }); 407 } 408 409 void WebTransport::ResolveWaitingConnection( 410 WebTransportReliabilityMode aReliability) { 411 LOG(("Resolved Connection %p, reliability = %u", this, 412 (unsigned)aReliability)); 413 // https://w3c.github.io/webtransport/#webtransport-constructor 414 // Step 17 of initialize WebTransport over HTTP 415 // Step 17.1 If transport.[[State]] is not "connecting": 416 if (mState != WebTransportState::CONNECTING) { 417 // Step 17.1.1: In parallel, terminate session. 418 // Step 17.1.2: abort these steps 419 // Cleanup should have been called, which means Ready has been rejected 420 return; 421 } 422 423 // Step 17.2: Set transport.[[State]] to "connected". 424 mState = WebTransportState::CONNECTED; 425 // Step 17.3: Set transport.[[Session]] to session. 426 // Step 17.4: Set transport’s [[Reliability]] to "supports-unreliable". 427 mReliability = aReliability; 428 if (NS_IsMainThread()) { 429 nsPIDOMWindowInner* innerWindow = GetParentObject()->GetAsInnerWindow(); 430 if (innerWindow) { 431 mInnerWindowID = innerWindow->WindowID(); 432 } 433 } else { 434 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); 435 if (workerPrivate->IsDedicatedWorker()) { 436 mInnerWindowID = workerPrivate->WindowID(); 437 } 438 } 439 440 mChild->SendGetMaxDatagramSize()->Then( 441 GetCurrentSerialEventTarget(), __func__, 442 [self = RefPtr{this}](uint64_t&& aMaxDatagramSize) { 443 MOZ_ASSERT(self->mDatagrams); 444 self->mDatagrams->SetMaxDatagramSize(aMaxDatagramSize); 445 LOG(("max datagram size for the session is %" PRIu64, 446 aMaxDatagramSize)); 447 }, 448 [](const mozilla::ipc::ResponseRejectReason& aReason) { 449 LOG(("WebTransport fetching maxDatagramSize failed")); 450 }); 451 452 // Step 17.5: Resolve transport.[[Ready]] with undefined. 453 mReady->MaybeResolveWithUndefined(); 454 455 // We can now release any queued datagrams 456 mDatagrams->SetChild(mChild); 457 458 if (mInnerWindowID != 0) { 459 // Get the http chanel created for the web transport session; 460 mChild->SendGetHttpChannelID()->Then( 461 GetCurrentSerialEventTarget(), __func__, 462 [self = RefPtr{this}](uint64_t&& aHttpChannelId) { 463 MOZ_ASSERT(self->mService); 464 self->mHttpChannelID = aHttpChannelId; 465 self->mService->WebTransportSessionCreated(self->mInnerWindowID, 466 aHttpChannelId); 467 }, 468 [](const mozilla::ipc::ResponseRejectReason& aReason) { 469 LOG(("WebTransport fetching the channel information failed ")); 470 }); 471 } 472 } 473 474 void WebTransport::RejectWaitingConnection(nsresult aRv) { 475 LOG(("Rejected connection %p %x", this, (uint32_t)aRv)); 476 // https://w3c.github.io/webtransport/#initialize-webtransport-over-http 477 478 // Step 10: If connection is failure, then abort the remaining steps and 479 // queue a network task with transport to run these steps: 480 // Step 10.1: If transport.[[State]] is "closed" or "failed", then abort 481 // these steps. 482 483 // Step 14: If the previous step fails, abort the remaining steps and 484 // queue a network task with transport to run these steps: 485 // Step 14.1: If transport.[[State]] is "closed" or "failed", then abort 486 // these steps. 487 if (mState == WebTransportState::CLOSED || 488 mState == WebTransportState::FAILED) { 489 if (mChild) { 490 mChild->Shutdown(true); 491 mChild = nullptr; 492 } 493 // Cleanup should have been called, which means Ready has been 494 // rejected and pulls resolved 495 return; 496 } 497 498 // Step 14.2: Let error be the result of creating a WebTransportError with 499 // "session". 500 RefPtr<WebTransportError> error = new WebTransportError( 501 "WebTransport connection rejected"_ns, WebTransportErrorSource::Session); 502 // Step 14.3: Cleanup transport with error. 503 Cleanup(error, nullptr, IgnoreErrors()); 504 505 mChild->Shutdown(true); 506 mChild = nullptr; 507 } 508 509 bool WebTransport::ParseURL(const nsAString& aURL) const { 510 NS_ENSURE_TRUE(!aURL.IsEmpty(), false); 511 512 // 5.4 = https://w3c.github.io/webtransport/#webtransport-constructor 513 // 5.4 #1 and #2 514 nsCOMPtr<nsIURI> uri; 515 nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL); 516 NS_ENSURE_SUCCESS(rv, false); 517 518 // 5.4 #3 519 if (!uri->SchemeIs("https")) { 520 return false; 521 } 522 523 // 5.4 #4 no fragments 524 bool hasRef; 525 rv = uri->GetHasRef(&hasRef); 526 NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !hasRef, false); 527 528 return true; 529 } 530 531 already_AddRefed<Promise> WebTransport::GetStats(ErrorResult& aError) { 532 aError.Throw(NS_ERROR_NOT_IMPLEMENTED); 533 return nullptr; 534 } 535 536 WebTransportReliabilityMode WebTransport::Reliability() { return mReliability; } 537 538 WebTransportCongestionControl WebTransport::CongestionControl() { 539 // XXX not implemented 540 return WebTransportCongestionControl::Default; 541 } 542 543 void WebTransport::RemoteClosed(bool aCleanly, const uint32_t& aCode, 544 const nsACString& aReason) { 545 LOG(("Server closed: cleanly: %d, code %u, reason %s", aCleanly, aCode, 546 PromiseFlatCString(aReason).get())); 547 // Step 2 of https://w3c.github.io/webtransport/#web-transport-termination 548 // We calculate cleanly on the parent 549 // Step 2.1: If transport.[[State]] is "closed" or "failed", abort these 550 // steps. 551 if (mState == WebTransportState::CLOSED || 552 mState == WebTransportState::FAILED) { 553 return; 554 } 555 // Step 2.2: Let error be the result of creating a WebTransportError with 556 // "session". 557 RefPtr<WebTransportError> error = new WebTransportError( 558 "remote WebTransport close"_ns, WebTransportErrorSource::Session); 559 // Step 2.3: If cleanly is false, then cleanup transport with error, and 560 // abort these steps. 561 ErrorResult errorresult; 562 if (!aCleanly) { 563 Cleanup(error, nullptr, errorresult); 564 return; 565 } 566 // Step 2.4: Let closeInfo be a new WebTransportCloseInfo. 567 // Step 2.5: If code is given, set closeInfo’s closeCode to code. 568 // Step 2.6: If reasonBytes is given, set closeInfo’s reason to reasonBytes, 569 // UTF-8 decoded. 570 WebTransportCloseInfo closeinfo; 571 closeinfo.mCloseCode = aCode; 572 closeinfo.mReason = aReason; 573 574 // Step 2.7: Cleanup transport with error and closeInfo. 575 Cleanup(error, &closeinfo, errorresult); 576 } 577 578 template <typename Stream> 579 void WebTransport::PropagateError(Stream* aStream, WebTransportError* aError) { 580 ErrorResult rv; 581 AutoJSAPI jsapi; 582 if (!jsapi.Init(mGlobal)) { 583 rv.ThrowUnknownError("Internal error"); 584 return; 585 } 586 JSContext* cx = jsapi.cx(); 587 JS::Rooted<JS::Value> errorValue(cx); 588 bool ok = ToJSValue(cx, aError, &errorValue); 589 if (!ok) { 590 rv.ThrowUnknownError("Internal error"); 591 return; 592 } 593 594 aStream->ErrorNative(cx, errorValue, IgnoreErrors()); 595 } 596 597 void WebTransport::OnStreamResetOrStopSending( 598 uint64_t aStreamId, const StreamResetOrStopSendingError& aError) { 599 LOG(("WebTransport::OnStreamResetOrStopSending %p id=%" PRIx64, this, 600 aStreamId)); 601 if (aError.type() == StreamResetOrStopSendingError::TStopSendingError) { 602 RefPtr<WebTransportSendStream> stream = mSendStreams.Get(aStreamId); 603 if (!stream) { 604 return; 605 } 606 uint8_t errorCode = net::GetWebTransportErrorFromNSResult( 607 aError.get_StopSendingError().error()); 608 RefPtr<WebTransportError> error = new WebTransportError( 609 "WebTransportStream StopSending"_ns, WebTransportErrorSource::Stream, 610 Nullable<uint8_t>(errorCode)); 611 PropagateError(stream.get(), error); 612 } else if (aError.type() == StreamResetOrStopSendingError::TResetError) { 613 RefPtr<WebTransportReceiveStream> stream = mReceiveStreams.Get(aStreamId); 614 LOG(("WebTransport::OnStreamResetOrStopSending reset %p stream=%p", this, 615 stream.get())); 616 if (!stream) { 617 return; 618 } 619 uint8_t errorCode = 620 net::GetWebTransportErrorFromNSResult(aError.get_ResetError().error()); 621 RefPtr<WebTransportError> error = new WebTransportError( 622 "WebTransportStream Reset"_ns, WebTransportErrorSource::Stream, 623 Nullable<uint8_t>(errorCode)); 624 PropagateError(stream.get(), error); 625 } 626 } 627 628 void WebTransport::Close(const WebTransportCloseInfo& aOptions, 629 ErrorResult& aRv) { 630 LOG(("Close() called")); 631 // https://w3c.github.io/webtransport/#dom-webtransport-close 632 // Step 1 and Step 2: If transport.[[State]] is "closed" or "failed", then 633 // abort these steps. 634 if (mState == WebTransportState::CLOSED || 635 mState == WebTransportState::FAILED) { 636 return; 637 } 638 // Step 3: If transport.[[State]] is "connecting": 639 if (mState == WebTransportState::CONNECTING) { 640 // Step 3.1: Let error be the result of creating a WebTransportError with 641 // "session". 642 RefPtr<WebTransportError> error = new WebTransportError( 643 "close() called on WebTransport while connecting"_ns, 644 WebTransportErrorSource::Session); 645 // Step 3.2: Cleanup transport with error. 646 Cleanup(error, nullptr, aRv); 647 // Step 3.3: Abort these steps. 648 mChild->Shutdown(true); 649 mChild = nullptr; 650 return; 651 } 652 LOG(("Sending Close")); 653 MOZ_ASSERT(mChild); 654 // Step 4: Let session be transport.[[Session]]. 655 // Step 5: Let code be closeInfo.closeCode. 656 // Step 6: "Let reasonString be the maximal code unit prefix of 657 // closeInfo.reason where the length of the UTF-8 encoded prefix 658 // doesn’t exceed 1024." 659 // Take the maximal "code unit prefix" of mReason and limit to 1024 bytes 660 // Step 7: Let reason be reasonString, UTF-8 encoded. 661 // Step 8: In parallel, terminate session with code and reason. 662 if (aOptions.mReason.Length() > 1024u) { 663 // We want to start looking for the previous code point at one past the 664 // limit, since if a code point ends exactly at the specified length, the 665 // next byte will be the start of a new code point. Note 666 // RewindToPriorUTF8Codepoint doesn't reduce the index if it points to the 667 // start of a code point. We know reason[1024] is accessible since 668 // Length() > 1024 669 mChild->SendClose( 670 aOptions.mCloseCode, 671 Substring(aOptions.mReason, 0, 672 RewindToPriorUTF8Codepoint(aOptions.mReason.get(), 1024u))); 673 } else { 674 mChild->SendClose(aOptions.mCloseCode, aOptions.mReason); 675 LOG(("Close sent")); 676 } 677 678 // Step 9: Cleanup transport with AbortError and closeInfo. (sets mState to 679 // Closed) 680 RefPtr<WebTransportError> error = 681 new WebTransportError("close()"_ns, WebTransportErrorSource::Session, 682 DOMException_Binding::ABORT_ERR); 683 Cleanup(error, &aOptions, aRv); 684 LOG(("Cleanup done")); 685 686 // The other side will call `Close()` for us now, make sure we don't call it 687 // in our destructor. 688 mChild->Shutdown(false); 689 mChild = nullptr; 690 LOG(("Close done")); 691 } 692 693 already_AddRefed<WebTransportDatagramDuplexStream> WebTransport::GetDatagrams( 694 ErrorResult& aError) { 695 return do_AddRef(mDatagrams); 696 } 697 698 already_AddRefed<Promise> WebTransport::CreateBidirectionalStream( 699 const WebTransportSendStreamOptions& aOptions, ErrorResult& aRv) { 700 LOG(("CreateBidirectionalStream() called")); 701 // https://w3c.github.io/webtransport/#dom-webtransport-createbidirectionalstream 702 RefPtr<Promise> promise = Promise::CreateInfallible(GetParentObject()); 703 704 // Step 2: If transport.[[State]] is "closed" or "failed", return a new 705 // rejected promise with an InvalidStateError. 706 if (mState == WebTransportState::CLOSED || 707 mState == WebTransportState::FAILED || !mChild) { 708 aRv.ThrowInvalidStateError("WebTransport closed or failed"); 709 return nullptr; 710 } 711 712 // Step 3: Let sendOrder be options's sendOrder. 713 Maybe<int64_t> sendOrder; 714 if (!aOptions.mSendOrder.IsNull()) { 715 sendOrder = Some(aOptions.mSendOrder.Value()); 716 } 717 // Step 4: Let p be a new promise. 718 // Step 5: Run the following steps in parallel, but abort them whenever 719 // transport’s [[State]] becomes "closed" or "failed", and instead queue 720 // a network task with transport to reject p with an InvalidStateError. 721 722 // Ask the parent to create the stream and send us the DataPipeSender/Receiver 723 // pair 724 mChild->SendCreateBidirectionalStream( 725 sendOrder, 726 [self = RefPtr{this}, sendOrder, promise]( 727 BidirectionalStreamResponse&& aPipes) MOZ_CAN_RUN_SCRIPT_BOUNDARY { 728 LOG(("CreateBidirectionalStream response")); 729 if (BidirectionalStreamResponse::Tnsresult == aPipes.type()) { 730 promise->MaybeReject(aPipes.get_nsresult()); 731 return; 732 } 733 // Step 5.2.1: If transport.[[State]] is "closed" or "failed", 734 // reject p with an InvalidStateError and abort these steps. 735 if (BidirectionalStreamResponse::Tnsresult == aPipes.type()) { 736 promise->MaybeReject(aPipes.get_nsresult()); 737 return; 738 } 739 if (self->mState == WebTransportState::CLOSED || 740 self->mState == WebTransportState::FAILED) { 741 promise->MaybeRejectWithInvalidStateError( 742 "Transport close/errored before CreateBidirectional finished"); 743 return; 744 } 745 uint64_t id = aPipes.get_BidirectionalStream().streamId(); 746 LOG(("Create WebTransportBidirectionalStream id=%" PRIx64, id)); 747 ErrorResult error; 748 RefPtr<WebTransportBidirectionalStream> newStream = 749 WebTransportBidirectionalStream::Create( 750 self, self->mGlobal, id, 751 aPipes.get_BidirectionalStream().inStream(), 752 aPipes.get_BidirectionalStream().outStream(), sendOrder, error); 753 LOG(("Returning a bidirectionalStream")); 754 promise->MaybeResolve(newStream); 755 }, 756 [self = RefPtr{this}, promise](mozilla::ipc::ResponseRejectReason) { 757 LOG(("CreateBidirectionalStream reject")); 758 promise->MaybeRejectWithInvalidStateError( 759 "Transport close/errored before CreateBidirectional started"); 760 }); 761 762 // Step 6: return p 763 return promise.forget(); 764 } 765 766 already_AddRefed<ReadableStream> WebTransport::IncomingBidirectionalStreams() { 767 return do_AddRef(mIncomingBidirectionalStreams); 768 } 769 770 already_AddRefed<Promise> WebTransport::CreateUnidirectionalStream( 771 const WebTransportSendStreamOptions& aOptions, ErrorResult& aRv) { 772 LOG(("CreateUnidirectionalStream() called")); 773 // https://w3c.github.io/webtransport/#dom-webtransport-createunidirectionalstream 774 // Step 2: If transport.[[State]] is "closed" or "failed", return a new 775 // rejected promise with an InvalidStateError. 776 if (mState == WebTransportState::CLOSED || 777 mState == WebTransportState::FAILED || !mChild) { 778 aRv.ThrowInvalidStateError("WebTransport closed or failed"); 779 return nullptr; 780 } 781 782 // Step 3: Let sendOrder be options's sendOrder. 783 Maybe<int64_t> sendOrder; 784 if (!aOptions.mSendOrder.IsNull()) { 785 sendOrder = Some(aOptions.mSendOrder.Value()); 786 } 787 // Step 4: Let p be a new promise. 788 RefPtr<Promise> promise = Promise::CreateInfallible(GetParentObject()); 789 790 // Step 5: Run the following steps in parallel, but abort them whenever 791 // transport’s [[State]] becomes "closed" or "failed", and instead queue 792 // a network task with transport to reject p with an InvalidStateError. 793 794 // Ask the parent to create the stream and send us the DataPipeSender 795 mChild->SendCreateUnidirectionalStream( 796 sendOrder, 797 [self = RefPtr{this}, sendOrder, 798 promise](UnidirectionalStreamResponse&& aResponse) 799 MOZ_CAN_RUN_SCRIPT_BOUNDARY { 800 LOG(("CreateUnidirectionalStream response")); 801 if (UnidirectionalStreamResponse::Tnsresult == aResponse.type()) { 802 promise->MaybeReject(aResponse.get_nsresult()); 803 return; 804 } 805 // Step 5.1: Let internalStream be the result of creating an 806 // outgoing unidirectional stream with transport.[[Session]]. 807 // Step 5.2: Queue a network task with transport to run the 808 // following steps: 809 // Step 5.2.1 If transport.[[State]] is "closed" or "failed", 810 // reject p with an InvalidStateError and abort these steps. 811 if (self->mState == WebTransportState::CLOSED || 812 self->mState == WebTransportState::FAILED || 813 aResponse.type() != 814 UnidirectionalStreamResponse::TUnidirectionalStream) { 815 promise->MaybeRejectWithInvalidStateError( 816 "Transport close/errored during CreateUnidirectional"); 817 return; 818 } 819 820 // Step 5.2.2.: Let stream be the result of creating a 821 // WebTransportSendStream with internalStream, transport, and 822 // sendOrder. 823 ErrorResult error; 824 uint64_t id = aResponse.get_UnidirectionalStream().streamId(); 825 LOG(("Create WebTransportSendStream id=%" PRIx64, id)); 826 RefPtr<WebTransportSendStream> writableStream = 827 WebTransportSendStream::Create( 828 self, self->mGlobal, id, 829 aResponse.get_UnidirectionalStream().outStream(), sendOrder, 830 error); 831 if (!writableStream) { 832 promise->MaybeReject(std::move(error)); 833 return; 834 } 835 LOG(("Returning a writableStream")); 836 // Step 5.2.3: Resolve p with stream. 837 promise->MaybeResolve(writableStream); 838 }, 839 [self = RefPtr{this}, promise](mozilla::ipc::ResponseRejectReason) { 840 LOG(("CreateUnidirectionalStream reject")); 841 promise->MaybeRejectWithInvalidStateError( 842 "Transport close/errored during CreateUnidirectional"); 843 }); 844 845 // Step 6: return p 846 return promise.forget(); 847 } 848 849 already_AddRefed<ReadableStream> WebTransport::IncomingUnidirectionalStreams() { 850 return do_AddRef(mIncomingUnidirectionalStreams); 851 } 852 853 // Can be invoked with "error", "error, error, and true/false", or "error and 854 // closeInfo", but reason and abruptly are never used, and it does use closeinfo 855 void WebTransport::Cleanup(WebTransportError* aError, 856 const WebTransportCloseInfo* aCloseInfo, 857 ErrorResult& aRv) { 858 // https://w3c.github.io/webtransport/#webtransport-cleanup 859 // Step 1: Let sendStreams be a copy of transport.[[SendStreams]] 860 // Step 2: Let receiveStreams be a copy of transport.[[ReceiveStreams]] 861 // Step 3: Let ready be transport.[[Ready]] -> (mReady) 862 // Step 4: Let closed be transport.[[Closed]] -> (mClosed) 863 // Step 5: Let incomingBidirectionalStreams be 864 // transport.[[IncomingBidirectionalStreams]]. 865 // Step 6: Let incomingUnidirectionalStreams be 866 // transport.[[IncomingUnidirectionalStreams]]. 867 // Step 7: Set transport.[[SendStreams]] to an empty set. 868 // Step 8: Set transport.[[ReceiveStreams]] to an empty set. 869 LOG(("Cleanup started")); 870 nsTHashMap<uint64_t, RefPtr<WebTransportSendStream>> sendStreams; 871 sendStreams.SwapElements(mSendStreams); 872 nsTHashMap<uint64_t, RefPtr<WebTransportReceiveStream>> receiveStreams; 873 receiveStreams.SwapElements(mReceiveStreams); 874 875 // Step 9: If closeInfo is given, then set transport.[[State]] to "closed". 876 // Otherwise, set transport.[[State]] to "failed". 877 mState = aCloseInfo ? WebTransportState::CLOSED : WebTransportState::FAILED; 878 879 // Notify all the listeners of the closed session 880 if (aCloseInfo && mInnerWindowID != 0) { 881 mService->WebTransportSessionClosed( 882 mInnerWindowID, mHttpChannelID, aCloseInfo->mCloseCode, 883 NS_ConvertUTF8toUTF16(aCloseInfo->mReason)); 884 } 885 886 // Step 10: For each sendStream in sendStreams, error sendStream with error. 887 AutoJSAPI jsapi; 888 if (!jsapi.Init(mGlobal)) { 889 aRv.ThrowUnknownError("Internal error"); 890 return; 891 } 892 JSContext* cx = jsapi.cx(); 893 JS::Rooted<JS::Value> errorValue(cx); 894 bool ok = ToJSValue(cx, aError, &errorValue); 895 if (!ok) { 896 aRv.ThrowUnknownError("Internal error"); 897 return; 898 } 899 900 for (const auto& stream : sendStreams.Values()) { 901 // This MOZ_KnownLive is redundant, see bug 1620312 902 MOZ_KnownLive(stream)->ErrorNative(cx, errorValue, IgnoreErrors()); 903 } 904 // Step 11: For each receiveStream in receiveStreams, error receiveStream with 905 // error. 906 for (const auto& stream : receiveStreams.Values()) { 907 stream->ErrorNative(cx, errorValue, IgnoreErrors()); 908 } 909 // Step 12: 910 if (aCloseInfo) { 911 // 12.1: Resolve closed with closeInfo. 912 LOG(("Resolving mClosed with closeinfo")); 913 mClosed->MaybeResolve(*aCloseInfo); 914 // 12.2: Assert: ready is settled. 915 MOZ_ASSERT(mReady->State() != Promise::PromiseState::Pending); 916 // 12.3: Close incomingBidirectionalStreams 917 // This keeps the clang-plugin happy 918 RefPtr<ReadableStream> stream = mIncomingBidirectionalStreams; 919 stream->CloseNative(cx, IgnoreErrors()); 920 // 12.4: Close incomingUnidirectionalStreams 921 stream = mIncomingUnidirectionalStreams; 922 stream->CloseNative(cx, IgnoreErrors()); 923 } else { 924 // Step 13 925 // 13.1: Reject closed with error 926 LOG(("Rejecting mClosed")); 927 mClosed->MaybeReject(errorValue); 928 // 13.2: Reject ready with error 929 mReady->MaybeReject(errorValue); 930 // 13.3: Error incomingBidirectionalStreams with error 931 mIncomingBidirectionalStreams->ErrorNative(cx, errorValue, IgnoreErrors()); 932 // 13.4: Error incomingUnidirectionalStreams with error 933 mIncomingUnidirectionalStreams->ErrorNative(cx, errorValue, IgnoreErrors()); 934 } 935 // Let go of the algorithms 936 mIncomingBidirectionalAlgorithm = nullptr; 937 mIncomingUnidirectionalAlgorithm = nullptr; 938 939 // We no longer block BFCache 940 NotifyToWindow(false); 941 } 942 943 void WebTransport::SendSetSendOrder(uint64_t aStreamId, 944 Maybe<int64_t> aSendOrder) { 945 if (!mChild || !mChild->CanSend()) { 946 return; 947 } 948 mChild->SendSetSendOrder(aStreamId, aSendOrder); 949 } 950 951 void WebTransport::NotifyBFCacheOnMainThread(nsPIDOMWindowInner* aInner, 952 bool aCreated) { 953 AssertIsOnMainThread(); 954 if (!aInner) { 955 return; 956 } 957 if (aCreated) { 958 aInner->RemoveFromBFCacheSync(); 959 } 960 961 uint32_t count = aInner->UpdateWebTransportCount(aCreated); 962 // It's okay for WindowGlobalChild to not exist, as it should mean it already 963 // is destroyed and can't enter bfcache anyway. 964 if (WindowGlobalChild* child = aInner->GetWindowGlobalChild()) { 965 if (aCreated && count == 1) { 966 // The first WebTransport is active. 967 child->BlockBFCacheFor(BFCacheStatus::ACTIVE_WEBTRANSPORT); 968 } else if (count == 0) { 969 child->UnblockBFCacheFor(BFCacheStatus::ACTIVE_WEBTRANSPORT); 970 } 971 } 972 } 973 974 class BFCacheNotifyWTRunnable final : public WorkerProxyToMainThreadRunnable { 975 public: 976 explicit BFCacheNotifyWTRunnable(bool aCreated) : mCreated(aCreated) {} 977 978 void RunOnMainThread(WorkerPrivate* aWorkerPrivate) override { 979 MOZ_ASSERT(aWorkerPrivate); 980 AssertIsOnMainThread(); 981 if (aWorkerPrivate->IsDedicatedWorker()) { 982 WebTransport::NotifyBFCacheOnMainThread( 983 aWorkerPrivate->GetAncestorWindow(), mCreated); 984 return; 985 } 986 if (aWorkerPrivate->IsSharedWorker()) { 987 aWorkerPrivate->GetRemoteWorkerController()->NotifyWebTransport(mCreated); 988 return; 989 } 990 MOZ_ASSERT_UNREACHABLE("Unexpected worker type"); 991 } 992 993 void RunBackOnWorkerThreadForCleanup(WorkerPrivate* aWorkerPrivate) override { 994 MOZ_ASSERT(aWorkerPrivate); 995 aWorkerPrivate->AssertIsOnWorkerThread(); 996 } 997 998 private: 999 bool mCreated; 1000 }; 1001 1002 void WebTransport::NotifyToWindow(bool aCreated) const { 1003 if (NS_IsMainThread()) { 1004 NotifyBFCacheOnMainThread(GetParentObject()->GetAsInnerWindow(), aCreated); 1005 return; 1006 } 1007 1008 WorkerPrivate* wp = GetCurrentThreadWorkerPrivate(); 1009 if (wp->IsDedicatedWorker() || wp->IsSharedWorker()) { 1010 RefPtr<BFCacheNotifyWTRunnable> runnable = 1011 new BFCacheNotifyWTRunnable(aCreated); 1012 1013 runnable->Dispatch(wp); 1014 } 1015 }; 1016 1017 } // namespace mozilla::dom