Bug 1833854 - Part 4: Move all code that deals with maintaining invariants into a...
[gecko.git] / dom / fetch / FetchUtil.cpp
blob48281df32196f46f6c363c79dde9b79276bbdb5f
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/. */
7 #include "FetchUtil.h"
9 #include "zlib.h"
11 #include "js/friend/ErrorMessages.h" // JSMSG_*
12 #include "nsCRT.h"
13 #include "nsError.h"
14 #include "nsIAsyncInputStream.h"
15 #include "nsICloneableInputStream.h"
16 #include "nsIHttpChannel.h"
17 #include "nsNetUtil.h"
18 #include "nsStreamUtils.h"
19 #include "nsString.h"
20 #include "js/BuildId.h"
21 #include "mozilla/dom/Document.h"
23 #include "mozilla/ClearOnShutdown.h"
24 #include "mozilla/dom/DOMException.h"
25 #include "mozilla/dom/InternalRequest.h"
26 #include "mozilla/dom/Response.h"
27 #include "mozilla/dom/ReferrerInfo.h"
28 #include "mozilla/dom/WorkerRef.h"
30 namespace mozilla::dom {
32 // static
33 nsresult FetchUtil::GetValidRequestMethod(const nsACString& aMethod,
34 nsCString& outMethod) {
35 nsAutoCString upperCaseMethod(aMethod);
36 ToUpperCase(upperCaseMethod);
37 if (!NS_IsValidHTTPToken(aMethod)) {
38 outMethod.SetIsVoid(true);
39 return NS_ERROR_DOM_SYNTAX_ERR;
42 if (upperCaseMethod.EqualsLiteral("CONNECT") ||
43 upperCaseMethod.EqualsLiteral("TRACE") ||
44 upperCaseMethod.EqualsLiteral("TRACK")) {
45 outMethod.SetIsVoid(true);
46 return NS_ERROR_DOM_SECURITY_ERR;
49 if (upperCaseMethod.EqualsLiteral("DELETE") ||
50 upperCaseMethod.EqualsLiteral("GET") ||
51 upperCaseMethod.EqualsLiteral("HEAD") ||
52 upperCaseMethod.EqualsLiteral("OPTIONS") ||
53 upperCaseMethod.EqualsLiteral("POST") ||
54 upperCaseMethod.EqualsLiteral("PUT")) {
55 outMethod = upperCaseMethod;
56 } else {
57 outMethod = aMethod; // Case unchanged for non-standard methods
59 return NS_OK;
62 static bool FindCRLF(nsACString::const_iterator& aStart,
63 nsACString::const_iterator& aEnd) {
64 nsACString::const_iterator end(aEnd);
65 return FindInReadable("\r\n"_ns, aStart, end);
68 // Reads over a CRLF and positions start after it.
69 static bool PushOverLine(nsACString::const_iterator& aStart,
70 const nsACString::const_iterator& aEnd) {
71 if (*aStart == nsCRT::CR && (aEnd - aStart > 1) && *(++aStart) == nsCRT::LF) {
72 ++aStart; // advance to after CRLF
73 return true;
76 return false;
79 // static
80 bool FetchUtil::ExtractHeader(nsACString::const_iterator& aStart,
81 nsACString::const_iterator& aEnd,
82 nsCString& aHeaderName, nsCString& aHeaderValue,
83 bool* aWasEmptyHeader) {
84 MOZ_ASSERT(aWasEmptyHeader);
85 // Set it to a valid value here so we don't forget later.
86 *aWasEmptyHeader = false;
88 const char* beginning = aStart.get();
89 nsACString::const_iterator end(aEnd);
90 if (!FindCRLF(aStart, end)) {
91 return false;
94 if (aStart.get() == beginning) {
95 *aWasEmptyHeader = true;
96 return true;
99 nsAutoCString header(beginning, aStart.get() - beginning);
101 nsACString::const_iterator headerStart, iter, headerEnd;
102 header.BeginReading(headerStart);
103 header.EndReading(headerEnd);
104 iter = headerStart;
105 if (!FindCharInReadable(':', iter, headerEnd)) {
106 return false;
109 aHeaderName.Assign(StringHead(header, iter - headerStart));
110 aHeaderName.CompressWhitespace();
111 if (!NS_IsValidHTTPToken(aHeaderName)) {
112 return false;
115 aHeaderValue.Assign(Substring(++iter, headerEnd));
116 if (!NS_IsReasonableHTTPHeaderValue(aHeaderValue)) {
117 return false;
119 aHeaderValue.CompressWhitespace();
121 return PushOverLine(aStart, aEnd);
124 // static
125 nsresult FetchUtil::SetRequestReferrer(nsIPrincipal* aPrincipal, Document* aDoc,
126 nsIHttpChannel* aChannel,
127 InternalRequest& aRequest) {
128 MOZ_ASSERT(NS_IsMainThread());
130 nsresult rv = NS_OK;
131 nsAutoString referrer;
132 aRequest.GetReferrer(referrer);
134 ReferrerPolicy policy = aRequest.ReferrerPolicy_();
135 nsCOMPtr<nsIReferrerInfo> referrerInfo;
136 if (referrer.IsEmpty()) {
137 // This is the case request’s referrer is "no-referrer"
138 referrerInfo = new ReferrerInfo(nullptr, ReferrerPolicy::No_referrer);
139 } else if (referrer.EqualsLiteral(kFETCH_CLIENT_REFERRER_STR)) {
140 referrerInfo = ReferrerInfo::CreateForFetch(aPrincipal, aDoc);
141 // In the first step, we should use referrer info from requetInit
142 referrerInfo = static_cast<ReferrerInfo*>(referrerInfo.get())
143 ->CloneWithNewPolicy(policy);
144 } else {
145 // From "Determine request's Referrer" step 3
146 // "If request's referrer is a URL, let referrerSource be request's
147 // referrer."
148 nsCOMPtr<nsIURI> referrerURI;
149 rv = NS_NewURI(getter_AddRefs(referrerURI), referrer);
150 NS_ENSURE_SUCCESS(rv, rv);
151 referrerInfo = new ReferrerInfo(referrerURI, policy);
154 rv = aChannel->SetReferrerInfoWithoutClone(referrerInfo);
155 NS_ENSURE_SUCCESS(rv, rv);
157 nsAutoString computedReferrerSpec;
158 referrerInfo = aChannel->GetReferrerInfo();
159 if (referrerInfo) {
160 Unused << referrerInfo->GetComputedReferrerSpec(computedReferrerSpec);
163 // Step 8 https://fetch.spec.whatwg.org/#main-fetch
164 // If request’s referrer is not "no-referrer", set request’s referrer to
165 // the result of invoking determine request’s referrer.
166 aRequest.SetReferrer(computedReferrerSpec);
168 return NS_OK;
171 class StoreOptimizedEncodingRunnable final : public Runnable {
172 nsMainThreadPtrHandle<nsICacheInfoChannel> mCache;
173 Vector<uint8_t> mBytes;
175 public:
176 StoreOptimizedEncodingRunnable(
177 nsMainThreadPtrHandle<nsICacheInfoChannel>&& aCache,
178 Vector<uint8_t>&& aBytes)
179 : Runnable("StoreOptimizedEncodingRunnable"),
180 mCache(std::move(aCache)),
181 mBytes(std::move(aBytes)) {}
183 NS_IMETHOD Run() override {
184 nsresult rv;
186 nsCOMPtr<nsIAsyncOutputStream> stream;
187 rv = mCache->OpenAlternativeOutputStream(FetchUtil::WasmAltDataType,
188 int64_t(mBytes.length()),
189 getter_AddRefs(stream));
190 if (NS_FAILED(rv)) {
191 return rv;
194 auto closeStream = MakeScopeExit([&]() { stream->CloseWithStatus(rv); });
196 uint32_t written;
197 rv = stream->Write((char*)mBytes.begin(), mBytes.length(), &written);
198 if (NS_FAILED(rv)) {
199 return rv;
202 MOZ_RELEASE_ASSERT(mBytes.length() == written);
203 return NS_OK;
207 class WindowStreamOwner final : public nsIObserver,
208 public nsSupportsWeakReference {
209 // Read from any thread but only set/cleared on the main thread. The lifecycle
210 // of WindowStreamOwner prevents concurrent read/clear.
211 nsCOMPtr<nsIAsyncInputStream> mStream;
213 nsCOMPtr<nsIGlobalObject> mGlobal;
215 ~WindowStreamOwner() {
216 MOZ_ASSERT(NS_IsMainThread());
218 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
219 if (obs) {
220 obs->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC);
224 public:
225 NS_DECL_ISUPPORTS
227 WindowStreamOwner(nsIAsyncInputStream* aStream, nsIGlobalObject* aGlobal)
228 : mStream(aStream), mGlobal(aGlobal) {
229 MOZ_DIAGNOSTIC_ASSERT(mGlobal);
230 MOZ_ASSERT(NS_IsMainThread());
233 static already_AddRefed<WindowStreamOwner> Create(
234 nsIAsyncInputStream* aStream, nsIGlobalObject* aGlobal) {
235 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
236 if (NS_WARN_IF(!os)) {
237 return nullptr;
240 RefPtr<WindowStreamOwner> self = new WindowStreamOwner(aStream, aGlobal);
242 // Holds nsIWeakReference to self.
243 nsresult rv = os->AddObserver(self, DOM_WINDOW_DESTROYED_TOPIC, true);
244 if (NS_WARN_IF(NS_FAILED(rv))) {
245 return nullptr;
248 return self.forget();
251 // nsIObserver:
253 NS_IMETHOD
254 Observe(nsISupports* aSubject, const char* aTopic,
255 const char16_t* aData) override {
256 MOZ_ASSERT(NS_IsMainThread());
257 MOZ_DIAGNOSTIC_ASSERT(strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) == 0);
259 if (!mStream) {
260 return NS_OK;
263 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mGlobal);
264 if (!SameCOMIdentity(aSubject, window)) {
265 return NS_OK;
268 // mStream->Close() will call JSStreamConsumer::OnInputStreamReady which may
269 // then destory itself, dropping the last reference to 'this'.
270 RefPtr<WindowStreamOwner> keepAlive(this);
272 mStream->Close();
273 mStream = nullptr;
274 mGlobal = nullptr;
275 return NS_OK;
279 NS_IMPL_ISUPPORTS(WindowStreamOwner, nsIObserver, nsISupportsWeakReference)
281 inline nsISupports* ToSupports(WindowStreamOwner* aObj) {
282 return static_cast<nsIObserver*>(aObj);
285 class WorkerStreamOwner final {
286 public:
287 NS_INLINE_DECL_REFCOUNTING(WorkerStreamOwner)
289 explicit WorkerStreamOwner(nsIAsyncInputStream* aStream,
290 nsCOMPtr<nsIEventTarget>&& target)
291 : mStream(aStream), mOwningEventTarget(std::move(target)) {}
293 static already_AddRefed<WorkerStreamOwner> Create(
294 nsIAsyncInputStream* aStream, WorkerPrivate* aWorker,
295 nsCOMPtr<nsIEventTarget>&& target) {
296 RefPtr<WorkerStreamOwner> self =
297 new WorkerStreamOwner(aStream, std::move(target));
299 self->mWorkerRef =
300 StrongWorkerRef::Create(aWorker, "JSStreamConsumer", [self]() {
301 if (self->mStream) {
302 // If this Close() calls JSStreamConsumer::OnInputStreamReady and
303 // drops the last reference to the JSStreamConsumer, 'this' will not
304 // be destroyed since ~JSStreamConsumer() only enqueues a release
305 // proxy.
306 self->mStream->Close();
307 self->mStream = nullptr;
311 if (!self->mWorkerRef) {
312 return nullptr;
315 return self.forget();
318 static void ProxyRelease(already_AddRefed<WorkerStreamOwner> aDoomed) {
319 RefPtr<WorkerStreamOwner> doomed = aDoomed;
320 nsIEventTarget* target = doomed->mOwningEventTarget;
321 NS_ProxyRelease("WorkerStreamOwner", target, doomed.forget(),
322 /* aAlwaysProxy = */ true);
325 private:
326 ~WorkerStreamOwner() = default;
328 // Read from any thread but only set/cleared on the worker thread. The
329 // lifecycle of WorkerStreamOwner prevents concurrent read/clear.
330 nsCOMPtr<nsIAsyncInputStream> mStream;
331 RefPtr<StrongWorkerRef> mWorkerRef;
332 nsCOMPtr<nsIEventTarget> mOwningEventTarget;
335 class JSStreamConsumer final : public nsIInputStreamCallback,
336 public JS::OptimizedEncodingListener {
337 // A LengthPrefixType is stored at the start of the compressed optimized
338 // encoding, allowing the decompressed buffer to be allocated to exactly
339 // the right size.
340 using LengthPrefixType = uint32_t;
341 static const unsigned PrefixBytes = sizeof(LengthPrefixType);
343 RefPtr<WindowStreamOwner> mWindowStreamOwner;
344 RefPtr<WorkerStreamOwner> mWorkerStreamOwner;
345 nsMainThreadPtrHandle<nsICacheInfoChannel> mCache;
346 const bool mOptimizedEncoding;
347 z_stream mZStream;
348 bool mZStreamInitialized;
349 Vector<uint8_t> mOptimizedEncodingBytes;
350 JS::StreamConsumer* mConsumer;
351 bool mConsumerAborted;
353 JSStreamConsumer(already_AddRefed<WindowStreamOwner> aWindowStreamOwner,
354 nsIGlobalObject* aGlobal, JS::StreamConsumer* aConsumer,
355 nsMainThreadPtrHandle<nsICacheInfoChannel>&& aCache,
356 bool aOptimizedEncoding)
357 : mWindowStreamOwner(aWindowStreamOwner),
358 mCache(std::move(aCache)),
359 mOptimizedEncoding(aOptimizedEncoding),
360 mZStreamInitialized(false),
361 mConsumer(aConsumer),
362 mConsumerAborted(false) {
363 MOZ_DIAGNOSTIC_ASSERT(mWindowStreamOwner);
364 MOZ_DIAGNOSTIC_ASSERT(mConsumer);
367 JSStreamConsumer(RefPtr<WorkerStreamOwner> aWorkerStreamOwner,
368 nsIGlobalObject* aGlobal, JS::StreamConsumer* aConsumer,
369 nsMainThreadPtrHandle<nsICacheInfoChannel>&& aCache,
370 bool aOptimizedEncoding)
371 : mWorkerStreamOwner(std::move(aWorkerStreamOwner)),
372 mCache(std::move(aCache)),
373 mOptimizedEncoding(aOptimizedEncoding),
374 mZStreamInitialized(false),
375 mConsumer(aConsumer),
376 mConsumerAborted(false) {
377 MOZ_DIAGNOSTIC_ASSERT(mWorkerStreamOwner);
378 MOZ_DIAGNOSTIC_ASSERT(mConsumer);
381 ~JSStreamConsumer() {
382 if (mZStreamInitialized) {
383 inflateEnd(&mZStream);
386 // Both WindowStreamOwner and WorkerStreamOwner need to be destroyed on
387 // their global's event target thread.
389 if (mWindowStreamOwner) {
390 MOZ_DIAGNOSTIC_ASSERT(!mWorkerStreamOwner);
391 NS_ReleaseOnMainThread("JSStreamConsumer::mWindowStreamOwner",
392 mWindowStreamOwner.forget(),
393 /* aAlwaysProxy = */ true);
394 } else {
395 MOZ_DIAGNOSTIC_ASSERT(mWorkerStreamOwner);
396 WorkerStreamOwner::ProxyRelease(mWorkerStreamOwner.forget());
399 // Bug 1733674: these annotations currently do nothing, because they are
400 // member variables and the annotation mechanism only applies to locals. But
401 // the analysis could be extended so that these could replace the big-hammer
402 // ~JSStreamConsumer annotation and thus the analysis could check that
403 // nothing is added that might GC for a different reason.
404 JS_HAZ_VALUE_IS_GC_SAFE(mWindowStreamOwner);
405 JS_HAZ_VALUE_IS_GC_SAFE(mWorkerStreamOwner);
408 static nsresult WriteSegment(nsIInputStream* aStream, void* aClosure,
409 const char* aFromSegment, uint32_t aToOffset,
410 uint32_t aCount, uint32_t* aWriteCount) {
411 JSStreamConsumer* self = reinterpret_cast<JSStreamConsumer*>(aClosure);
412 MOZ_DIAGNOSTIC_ASSERT(!self->mConsumerAborted);
414 if (self->mOptimizedEncoding) {
415 if (!self->mZStreamInitialized) {
416 // mOptimizedEncodingBytes is used as temporary storage until we have
417 // the full prefix.
418 MOZ_ASSERT(self->mOptimizedEncodingBytes.length() < PrefixBytes);
419 uint32_t remain = PrefixBytes - self->mOptimizedEncodingBytes.length();
420 uint32_t consume = std::min(remain, aCount);
422 if (!self->mOptimizedEncodingBytes.append(aFromSegment, consume)) {
423 return NS_ERROR_UNEXPECTED;
426 if (consume == remain) {
427 // Initialize zlib once all prefix bytes are loaded.
428 LengthPrefixType length;
429 memcpy(&length, self->mOptimizedEncodingBytes.begin(), PrefixBytes);
431 if (!self->mOptimizedEncodingBytes.resizeUninitialized(length)) {
432 return NS_ERROR_UNEXPECTED;
435 memset(&self->mZStream, 0, sizeof(self->mZStream));
436 self->mZStream.avail_out = length;
437 self->mZStream.next_out = self->mOptimizedEncodingBytes.begin();
439 if (inflateInit(&self->mZStream) != Z_OK) {
440 return NS_ERROR_UNEXPECTED;
442 self->mZStreamInitialized = true;
445 *aWriteCount = consume;
446 return NS_OK;
449 // Zlib is initialized, overwrite the prefix with the inflated data.
451 MOZ_DIAGNOSTIC_ASSERT(aCount > 0);
452 self->mZStream.avail_in = aCount;
453 self->mZStream.next_in = (uint8_t*)aFromSegment;
455 int ret = inflate(&self->mZStream, Z_NO_FLUSH);
457 MOZ_DIAGNOSTIC_ASSERT(ret == Z_OK || ret == Z_STREAM_END,
458 "corrupt optimized wasm cache file: data");
459 MOZ_DIAGNOSTIC_ASSERT(self->mZStream.avail_in == 0,
460 "corrupt optimized wasm cache file: input");
461 MOZ_DIAGNOSTIC_ASSERT_IF(ret == Z_STREAM_END,
462 self->mZStream.avail_out == 0);
463 // Gracefully handle corruption in release.
464 bool ok =
465 (ret == Z_OK || ret == Z_STREAM_END) && self->mZStream.avail_in == 0;
466 if (!ok) {
467 return NS_ERROR_UNEXPECTED;
469 } else {
470 // This callback can be called on any thread which is explicitly allowed
471 // by this particular JS API call.
472 if (!self->mConsumer->consumeChunk((const uint8_t*)aFromSegment,
473 aCount)) {
474 self->mConsumerAborted = true;
475 return NS_ERROR_UNEXPECTED;
479 *aWriteCount = aCount;
480 return NS_OK;
483 public:
484 NS_DECL_THREADSAFE_ISUPPORTS
486 static bool Start(nsCOMPtr<nsIInputStream> aStream, nsIGlobalObject* aGlobal,
487 WorkerPrivate* aMaybeWorker, JS::StreamConsumer* aConsumer,
488 nsMainThreadPtrHandle<nsICacheInfoChannel>&& aCache,
489 bool aOptimizedEncoding) {
490 nsCOMPtr<nsIAsyncInputStream> asyncStream;
491 nsresult rv = NS_MakeAsyncNonBlockingInputStream(
492 aStream.forget(), getter_AddRefs(asyncStream));
493 if (NS_WARN_IF(NS_FAILED(rv))) {
494 return false;
497 RefPtr<JSStreamConsumer> consumer;
498 if (aMaybeWorker) {
499 RefPtr<WorkerStreamOwner> owner = WorkerStreamOwner::Create(
500 asyncStream, aMaybeWorker,
501 aGlobal->EventTargetFor(TaskCategory::Other));
502 if (!owner) {
503 return false;
506 consumer = new JSStreamConsumer(std::move(owner), aGlobal, aConsumer,
507 std::move(aCache), aOptimizedEncoding);
508 } else {
509 RefPtr<WindowStreamOwner> owner =
510 WindowStreamOwner::Create(asyncStream, aGlobal);
511 if (!owner) {
512 return false;
515 consumer = new JSStreamConsumer(owner.forget(), aGlobal, aConsumer,
516 std::move(aCache), aOptimizedEncoding);
519 // This AsyncWait() creates a ref-cycle between asyncStream and consumer:
521 // asyncStream -> consumer -> (Window|Worker)StreamOwner -> asyncStream
523 // The cycle is broken when the stream completes or errors out and
524 // asyncStream drops its reference to consumer.
525 return NS_SUCCEEDED(asyncStream->AsyncWait(consumer, 0, 0, nullptr));
528 // nsIInputStreamCallback:
530 NS_IMETHOD
531 OnInputStreamReady(nsIAsyncInputStream* aStream) override {
532 // Can be called on any stream. The JS API calls made below explicitly
533 // support being called from any thread.
534 MOZ_DIAGNOSTIC_ASSERT(!mConsumerAborted);
536 nsresult rv;
538 uint64_t available = 0;
539 rv = aStream->Available(&available);
540 if (NS_SUCCEEDED(rv) && available == 0) {
541 rv = NS_BASE_STREAM_CLOSED;
544 if (rv == NS_BASE_STREAM_CLOSED) {
545 if (mOptimizedEncoding) {
546 // Gracefully handle corruption of compressed data stream in release.
547 // From on investigations in bug 1738987, the incomplete data cases
548 // mostly happen during shutdown. Some corruptions in the cache entry
549 // can still happen and will be handled in the WriteSegment above.
550 bool ok = mZStreamInitialized && mZStream.avail_out == 0;
551 if (!ok) {
552 mConsumer->streamError(size_t(NS_ERROR_UNEXPECTED));
553 return NS_OK;
556 mConsumer->consumeOptimizedEncoding(mOptimizedEncodingBytes.begin(),
557 mOptimizedEncodingBytes.length());
558 } else {
559 // If there is cache entry associated with this stream, then listen for
560 // an optimized encoding so we can store it in the alt data. By JS API
561 // contract, the compilation process will hold a refcount to 'this'
562 // until it's done, optionally calling storeOptimizedEncoding().
563 mConsumer->streamEnd(mCache ? this : nullptr);
565 return NS_OK;
568 if (NS_FAILED(rv)) {
569 mConsumer->streamError(size_t(rv));
570 return NS_OK;
573 // Check mConsumerAborted before NS_FAILED to avoid calling streamError()
574 // if consumeChunk() returned false per JS API contract.
575 uint32_t written = 0;
576 rv = aStream->ReadSegments(WriteSegment, this, available, &written);
577 if (mConsumerAborted) {
578 return NS_OK;
580 if (NS_WARN_IF(NS_FAILED(rv))) {
581 mConsumer->streamError(size_t(rv));
582 return NS_OK;
585 rv = aStream->AsyncWait(this, 0, 0, nullptr);
586 if (NS_WARN_IF(NS_FAILED(rv))) {
587 mConsumer->streamError(size_t(rv));
588 return NS_OK;
591 return NS_OK;
594 // JS::OptimizedEncodingListener
596 void storeOptimizedEncoding(const uint8_t* aSrcBytes,
597 size_t aSrcLength) override {
598 MOZ_ASSERT(mCache, "we only listen if there's a cache entry");
600 z_stream zstream;
601 memset(&zstream, 0, sizeof(zstream));
602 zstream.avail_in = aSrcLength;
603 zstream.next_in = (uint8_t*)aSrcBytes;
605 // The wins from increasing compression levels are tiny, while the time
606 // to compress increases drastically. For example, for a 148mb alt-data
607 // produced by a 40mb .wasm file, the level 2 takes 2.5s to get a 3.7x size
608 // reduction while level 9 takes 22.5s to get a 4x size reduction. Read-time
609 // wins from smaller compressed cache files are not found to be
610 // significant, thus the fastest compression level is used. (On test
611 // workloads, level 2 actually was faster *and* smaller than level 1.)
612 const int COMPRESSION = 2;
613 if (deflateInit(&zstream, COMPRESSION) != Z_OK) {
614 return;
616 auto autoDestroy = MakeScopeExit([&]() { deflateEnd(&zstream); });
618 Vector<uint8_t> dstBytes;
619 if (!dstBytes.resizeUninitialized(PrefixBytes +
620 deflateBound(&zstream, aSrcLength))) {
621 return;
624 MOZ_RELEASE_ASSERT(LengthPrefixType(aSrcLength) == aSrcLength);
625 LengthPrefixType srcLength = aSrcLength;
626 memcpy(dstBytes.begin(), &srcLength, PrefixBytes);
628 uint8_t* compressBegin = dstBytes.begin() + PrefixBytes;
629 zstream.next_out = compressBegin;
630 zstream.avail_out = dstBytes.length() - PrefixBytes;
632 int ret = deflate(&zstream, Z_FINISH);
633 if (ret == Z_MEM_ERROR) {
634 return;
636 MOZ_RELEASE_ASSERT(ret == Z_STREAM_END);
638 dstBytes.shrinkTo(zstream.next_out - dstBytes.begin());
640 NS_DispatchToMainThread(new StoreOptimizedEncodingRunnable(
641 std::move(mCache), std::move(dstBytes)));
645 NS_IMPL_ISUPPORTS(JSStreamConsumer, nsIInputStreamCallback)
647 // static
648 const nsCString FetchUtil::WasmAltDataType;
650 // static
651 void FetchUtil::InitWasmAltDataType() {
652 nsCString& type = const_cast<nsCString&>(WasmAltDataType);
653 MOZ_ASSERT(type.IsEmpty());
655 RunOnShutdown([]() {
656 // Avoid nsStringBuffer leak tests failures.
657 const_cast<nsCString&>(WasmAltDataType).Truncate();
660 type.Append(nsLiteralCString("wasm-"));
662 JS::BuildIdCharVector buildId;
663 if (!JS::GetOptimizedEncodingBuildId(&buildId)) {
664 MOZ_CRASH("build id oom");
667 type.Append(buildId.begin(), buildId.length());
670 static bool ThrowException(JSContext* aCx, unsigned errorNumber) {
671 JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr, errorNumber);
672 return false;
675 // static
676 bool FetchUtil::StreamResponseToJS(JSContext* aCx, JS::Handle<JSObject*> aObj,
677 JS::MimeType aMimeType,
678 JS::StreamConsumer* aConsumer,
679 WorkerPrivate* aMaybeWorker) {
680 MOZ_ASSERT(!WasmAltDataType.IsEmpty());
681 MOZ_ASSERT(!aMaybeWorker == NS_IsMainThread());
683 RefPtr<Response> response;
684 nsresult rv = UNWRAP_OBJECT(Response, aObj, response);
685 if (NS_FAILED(rv)) {
686 return ThrowException(aCx, JSMSG_WASM_BAD_RESPONSE_VALUE);
689 const char* requiredMimeType = nullptr;
690 switch (aMimeType) {
691 case JS::MimeType::Wasm:
692 requiredMimeType = WASM_CONTENT_TYPE;
693 break;
696 nsAutoCString mimeType;
697 nsAutoCString mixedCaseMimeType; // unused
698 response->GetMimeType(mimeType, mixedCaseMimeType);
700 if (!mimeType.EqualsASCII(requiredMimeType)) {
701 JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr,
702 JSMSG_WASM_BAD_RESPONSE_MIME_TYPE, mimeType.get(),
703 requiredMimeType);
704 return false;
707 if (response->Type() != ResponseType::Basic &&
708 response->Type() != ResponseType::Cors &&
709 response->Type() != ResponseType::Default) {
710 return ThrowException(aCx, JSMSG_WASM_BAD_RESPONSE_CORS_SAME_ORIGIN);
713 if (!response->Ok()) {
714 return ThrowException(aCx, JSMSG_WASM_BAD_RESPONSE_STATUS);
717 if (response->BodyUsed()) {
718 return ThrowException(aCx, JSMSG_WASM_RESPONSE_ALREADY_CONSUMED);
721 switch (aMimeType) {
722 case JS::MimeType::Wasm:
723 nsAutoString url;
724 response->GetUrl(url);
726 IgnoredErrorResult result;
727 nsCString sourceMapUrl;
728 response->GetInternalHeaders()->Get("SourceMap"_ns, sourceMapUrl, result);
729 if (NS_WARN_IF(result.Failed())) {
730 return ThrowException(aCx, JSMSG_WASM_ERROR_CONSUMING_RESPONSE);
732 NS_ConvertUTF16toUTF8 urlUTF8(url);
733 aConsumer->noteResponseURLs(
734 urlUTF8.get(), sourceMapUrl.IsVoid() ? nullptr : sourceMapUrl.get());
735 break;
738 SafeRefPtr<InternalResponse> ir = response->GetInternalResponse();
739 if (NS_WARN_IF(!ir)) {
740 return ThrowException(aCx, JSMSG_OUT_OF_MEMORY);
743 nsCOMPtr<nsIInputStream> stream;
745 nsMainThreadPtrHandle<nsICacheInfoChannel> cache;
746 bool optimizedEncoding = false;
747 if (ir->HasCacheInfoChannel()) {
748 cache = ir->TakeCacheInfoChannel();
750 nsAutoCString altDataType;
751 if (NS_SUCCEEDED(cache->GetAlternativeDataType(altDataType)) &&
752 WasmAltDataType.Equals(altDataType)) {
753 optimizedEncoding = true;
754 rv = cache->GetAlternativeDataInputStream(getter_AddRefs(stream));
755 if (NS_WARN_IF(NS_FAILED(rv))) {
756 return ThrowException(aCx, JSMSG_OUT_OF_MEMORY);
758 if (ir->HasBeenCloned()) {
759 // If `Response` is cloned, clone alternative data stream instance.
760 // The cache entry does not clone automatically, and multiple
761 // JSStreamConsumer instances will collide during read if not cloned.
762 nsCOMPtr<nsICloneableInputStream> original = do_QueryInterface(stream);
763 if (NS_WARN_IF(!original)) {
764 return ThrowException(aCx, JSMSG_OUT_OF_MEMORY);
766 rv = original->Clone(getter_AddRefs(stream));
767 if (NS_WARN_IF(NS_FAILED(rv))) {
768 return ThrowException(aCx, JSMSG_OUT_OF_MEMORY);
774 if (!optimizedEncoding) {
775 ir->GetUnfilteredBody(getter_AddRefs(stream));
776 if (!stream) {
777 aConsumer->streamEnd();
778 return true;
782 MOZ_ASSERT(stream);
784 IgnoredErrorResult error;
785 response->SetBodyUsed(aCx, error);
786 if (NS_WARN_IF(error.Failed())) {
787 return ThrowException(aCx, JSMSG_WASM_ERROR_CONSUMING_RESPONSE);
790 nsIGlobalObject* global = xpc::NativeGlobal(js::UncheckedUnwrap(aObj));
792 if (!JSStreamConsumer::Start(stream, global, aMaybeWorker, aConsumer,
793 std::move(cache), optimizedEncoding)) {
794 return ThrowException(aCx, JSMSG_OUT_OF_MEMORY);
797 return true;
800 // static
801 void FetchUtil::ReportJSStreamError(JSContext* aCx, size_t aErrorCode) {
802 // For now, convert *all* errors into AbortError.
804 RefPtr<DOMException> e = DOMException::Create(NS_ERROR_DOM_ABORT_ERR);
806 JS::Rooted<JS::Value> value(aCx);
807 if (!GetOrCreateDOMReflector(aCx, e, &value)) {
808 return;
811 JS_SetPendingException(aCx, value);
814 } // namespace mozilla::dom