Bug 1857841 - pt 3. Add a new page kind named "fresh" r=glandium
[gecko.git] / dom / fetch / FetchUtil.cpp
blob040e23e4dc034686a3efb4d7429772924f21f599
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, aGlobal->SerialEventTarget());
501 if (!owner) {
502 return false;
505 consumer = new JSStreamConsumer(std::move(owner), aGlobal, aConsumer,
506 std::move(aCache), aOptimizedEncoding);
507 } else {
508 RefPtr<WindowStreamOwner> owner =
509 WindowStreamOwner::Create(asyncStream, aGlobal);
510 if (!owner) {
511 return false;
514 consumer = new JSStreamConsumer(owner.forget(), aGlobal, aConsumer,
515 std::move(aCache), aOptimizedEncoding);
518 // This AsyncWait() creates a ref-cycle between asyncStream and consumer:
520 // asyncStream -> consumer -> (Window|Worker)StreamOwner -> asyncStream
522 // The cycle is broken when the stream completes or errors out and
523 // asyncStream drops its reference to consumer.
524 return NS_SUCCEEDED(asyncStream->AsyncWait(consumer, 0, 0, nullptr));
527 // nsIInputStreamCallback:
529 NS_IMETHOD
530 OnInputStreamReady(nsIAsyncInputStream* aStream) override {
531 // Can be called on any stream. The JS API calls made below explicitly
532 // support being called from any thread.
533 MOZ_DIAGNOSTIC_ASSERT(!mConsumerAborted);
535 nsresult rv;
537 uint64_t available = 0;
538 rv = aStream->Available(&available);
539 if (NS_SUCCEEDED(rv) && available == 0) {
540 rv = NS_BASE_STREAM_CLOSED;
543 if (rv == NS_BASE_STREAM_CLOSED) {
544 if (mOptimizedEncoding) {
545 // Gracefully handle corruption of compressed data stream in release.
546 // From on investigations in bug 1738987, the incomplete data cases
547 // mostly happen during shutdown. Some corruptions in the cache entry
548 // can still happen and will be handled in the WriteSegment above.
549 bool ok = mZStreamInitialized && mZStream.avail_out == 0;
550 if (!ok) {
551 mConsumer->streamError(size_t(NS_ERROR_UNEXPECTED));
552 return NS_OK;
555 mConsumer->consumeOptimizedEncoding(mOptimizedEncodingBytes.begin(),
556 mOptimizedEncodingBytes.length());
557 } else {
558 // If there is cache entry associated with this stream, then listen for
559 // an optimized encoding so we can store it in the alt data. By JS API
560 // contract, the compilation process will hold a refcount to 'this'
561 // until it's done, optionally calling storeOptimizedEncoding().
562 mConsumer->streamEnd(mCache ? this : nullptr);
564 return NS_OK;
567 if (NS_FAILED(rv)) {
568 mConsumer->streamError(size_t(rv));
569 return NS_OK;
572 // Check mConsumerAborted before NS_FAILED to avoid calling streamError()
573 // if consumeChunk() returned false per JS API contract.
574 uint32_t written = 0;
575 rv = aStream->ReadSegments(WriteSegment, this, available, &written);
576 if (mConsumerAborted) {
577 return NS_OK;
579 if (NS_WARN_IF(NS_FAILED(rv))) {
580 mConsumer->streamError(size_t(rv));
581 return NS_OK;
584 rv = aStream->AsyncWait(this, 0, 0, nullptr);
585 if (NS_WARN_IF(NS_FAILED(rv))) {
586 mConsumer->streamError(size_t(rv));
587 return NS_OK;
590 return NS_OK;
593 // JS::OptimizedEncodingListener
595 void storeOptimizedEncoding(const uint8_t* aSrcBytes,
596 size_t aSrcLength) override {
597 MOZ_ASSERT(mCache, "we only listen if there's a cache entry");
599 z_stream zstream;
600 memset(&zstream, 0, sizeof(zstream));
601 zstream.avail_in = aSrcLength;
602 zstream.next_in = (uint8_t*)aSrcBytes;
604 // The wins from increasing compression levels are tiny, while the time
605 // to compress increases drastically. For example, for a 148mb alt-data
606 // produced by a 40mb .wasm file, the level 2 takes 2.5s to get a 3.7x size
607 // reduction while level 9 takes 22.5s to get a 4x size reduction. Read-time
608 // wins from smaller compressed cache files are not found to be
609 // significant, thus the fastest compression level is used. (On test
610 // workloads, level 2 actually was faster *and* smaller than level 1.)
611 const int COMPRESSION = 2;
612 if (deflateInit(&zstream, COMPRESSION) != Z_OK) {
613 return;
615 auto autoDestroy = MakeScopeExit([&]() { deflateEnd(&zstream); });
617 Vector<uint8_t> dstBytes;
618 if (!dstBytes.resizeUninitialized(PrefixBytes +
619 deflateBound(&zstream, aSrcLength))) {
620 return;
623 MOZ_RELEASE_ASSERT(LengthPrefixType(aSrcLength) == aSrcLength);
624 LengthPrefixType srcLength = aSrcLength;
625 memcpy(dstBytes.begin(), &srcLength, PrefixBytes);
627 uint8_t* compressBegin = dstBytes.begin() + PrefixBytes;
628 zstream.next_out = compressBegin;
629 zstream.avail_out = dstBytes.length() - PrefixBytes;
631 int ret = deflate(&zstream, Z_FINISH);
632 if (ret == Z_MEM_ERROR) {
633 return;
635 MOZ_RELEASE_ASSERT(ret == Z_STREAM_END);
637 dstBytes.shrinkTo(zstream.next_out - dstBytes.begin());
639 NS_DispatchToMainThread(new StoreOptimizedEncodingRunnable(
640 std::move(mCache), std::move(dstBytes)));
644 NS_IMPL_ISUPPORTS(JSStreamConsumer, nsIInputStreamCallback)
646 // static
647 const nsCString FetchUtil::WasmAltDataType;
649 // static
650 void FetchUtil::InitWasmAltDataType() {
651 nsCString& type = const_cast<nsCString&>(WasmAltDataType);
652 MOZ_ASSERT(type.IsEmpty());
654 RunOnShutdown([]() {
655 // Avoid nsStringBuffer leak tests failures.
656 const_cast<nsCString&>(WasmAltDataType).Truncate();
659 type.Append(nsLiteralCString("wasm-"));
661 JS::BuildIdCharVector buildId;
662 if (!JS::GetOptimizedEncodingBuildId(&buildId)) {
663 MOZ_CRASH("build id oom");
666 type.Append(buildId.begin(), buildId.length());
669 static bool ThrowException(JSContext* aCx, unsigned errorNumber) {
670 JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr, errorNumber);
671 return false;
674 // static
675 bool FetchUtil::StreamResponseToJS(JSContext* aCx, JS::Handle<JSObject*> aObj,
676 JS::MimeType aMimeType,
677 JS::StreamConsumer* aConsumer,
678 WorkerPrivate* aMaybeWorker) {
679 MOZ_ASSERT(!WasmAltDataType.IsEmpty());
680 MOZ_ASSERT(!aMaybeWorker == NS_IsMainThread());
682 RefPtr<Response> response;
683 nsresult rv = UNWRAP_OBJECT(Response, aObj, response);
684 if (NS_FAILED(rv)) {
685 return ThrowException(aCx, JSMSG_WASM_BAD_RESPONSE_VALUE);
688 const char* requiredMimeType = nullptr;
689 switch (aMimeType) {
690 case JS::MimeType::Wasm:
691 requiredMimeType = WASM_CONTENT_TYPE;
692 break;
695 nsAutoCString mimeType;
696 nsAutoCString mixedCaseMimeType; // unused
697 response->GetMimeType(mimeType, mixedCaseMimeType);
699 if (!mimeType.EqualsASCII(requiredMimeType)) {
700 JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr,
701 JSMSG_WASM_BAD_RESPONSE_MIME_TYPE, mimeType.get(),
702 requiredMimeType);
703 return false;
706 if (response->Type() != ResponseType::Basic &&
707 response->Type() != ResponseType::Cors &&
708 response->Type() != ResponseType::Default) {
709 return ThrowException(aCx, JSMSG_WASM_BAD_RESPONSE_CORS_SAME_ORIGIN);
712 if (!response->Ok()) {
713 return ThrowException(aCx, JSMSG_WASM_BAD_RESPONSE_STATUS);
716 if (response->BodyUsed()) {
717 return ThrowException(aCx, JSMSG_WASM_RESPONSE_ALREADY_CONSUMED);
720 switch (aMimeType) {
721 case JS::MimeType::Wasm:
722 nsAutoString url;
723 response->GetUrl(url);
725 IgnoredErrorResult result;
726 nsCString sourceMapUrl;
727 response->GetInternalHeaders()->Get("SourceMap"_ns, sourceMapUrl, result);
728 if (NS_WARN_IF(result.Failed())) {
729 return ThrowException(aCx, JSMSG_WASM_ERROR_CONSUMING_RESPONSE);
731 NS_ConvertUTF16toUTF8 urlUTF8(url);
732 aConsumer->noteResponseURLs(
733 urlUTF8.get(), sourceMapUrl.IsVoid() ? nullptr : sourceMapUrl.get());
734 break;
737 SafeRefPtr<InternalResponse> ir = response->GetInternalResponse();
738 if (NS_WARN_IF(!ir)) {
739 return ThrowException(aCx, JSMSG_OUT_OF_MEMORY);
742 nsCOMPtr<nsIInputStream> stream;
744 nsMainThreadPtrHandle<nsICacheInfoChannel> cache;
745 bool optimizedEncoding = false;
746 if (ir->HasCacheInfoChannel()) {
747 cache = ir->TakeCacheInfoChannel();
749 nsAutoCString altDataType;
750 if (NS_SUCCEEDED(cache->GetAlternativeDataType(altDataType)) &&
751 WasmAltDataType.Equals(altDataType)) {
752 optimizedEncoding = true;
753 rv = cache->GetAlternativeDataInputStream(getter_AddRefs(stream));
754 if (NS_WARN_IF(NS_FAILED(rv))) {
755 return ThrowException(aCx, JSMSG_OUT_OF_MEMORY);
757 if (ir->HasBeenCloned()) {
758 // If `Response` is cloned, clone alternative data stream instance.
759 // The cache entry does not clone automatically, and multiple
760 // JSStreamConsumer instances will collide during read if not cloned.
761 nsCOMPtr<nsICloneableInputStream> original = do_QueryInterface(stream);
762 if (NS_WARN_IF(!original)) {
763 return ThrowException(aCx, JSMSG_OUT_OF_MEMORY);
765 rv = original->Clone(getter_AddRefs(stream));
766 if (NS_WARN_IF(NS_FAILED(rv))) {
767 return ThrowException(aCx, JSMSG_OUT_OF_MEMORY);
773 if (!optimizedEncoding) {
774 ir->GetUnfilteredBody(getter_AddRefs(stream));
775 if (!stream) {
776 aConsumer->streamEnd();
777 return true;
781 MOZ_ASSERT(stream);
783 IgnoredErrorResult error;
784 response->SetBodyUsed(aCx, error);
785 if (NS_WARN_IF(error.Failed())) {
786 return ThrowException(aCx, JSMSG_WASM_ERROR_CONSUMING_RESPONSE);
789 nsIGlobalObject* global = xpc::NativeGlobal(js::UncheckedUnwrap(aObj));
791 if (!JSStreamConsumer::Start(stream, global, aMaybeWorker, aConsumer,
792 std::move(cache), optimizedEncoding)) {
793 return ThrowException(aCx, JSMSG_OUT_OF_MEMORY);
796 return true;
799 // static
800 void FetchUtil::ReportJSStreamError(JSContext* aCx, size_t aErrorCode) {
801 // For now, convert *all* errors into AbortError.
803 RefPtr<DOMException> e = DOMException::Create(NS_ERROR_DOM_ABORT_ERR);
805 JS::Rooted<JS::Value> value(aCx);
806 if (!GetOrCreateDOMReflector(aCx, e, &value)) {
807 return;
810 JS_SetPendingException(aCx, value);
813 } // namespace mozilla::dom