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/. */
11 #include "js/friend/ErrorMessages.h" // JSMSG_*
14 #include "nsIAsyncInputStream.h"
15 #include "nsICloneableInputStream.h"
16 #include "nsIHttpChannel.h"
17 #include "nsNetUtil.h"
18 #include "nsStreamUtils.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
{
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
;
57 outMethod
= aMethod
; // Case unchanged for non-standard methods
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
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
)) {
94 if (aStart
.get() == beginning
) {
95 *aWasEmptyHeader
= true;
99 nsAutoCString
header(beginning
, aStart
.get() - beginning
);
101 nsACString::const_iterator headerStart
, iter
, headerEnd
;
102 header
.BeginReading(headerStart
);
103 header
.EndReading(headerEnd
);
105 if (!FindCharInReadable(':', iter
, headerEnd
)) {
109 aHeaderName
.Assign(StringHead(header
, iter
- headerStart
));
110 aHeaderName
.CompressWhitespace();
111 if (!NS_IsValidHTTPToken(aHeaderName
)) {
115 aHeaderValue
.Assign(Substring(++iter
, headerEnd
));
116 if (!NS_IsReasonableHTTPHeaderValue(aHeaderValue
)) {
119 aHeaderValue
.CompressWhitespace();
121 return PushOverLine(aStart
, aEnd
);
125 nsresult
FetchUtil::SetRequestReferrer(nsIPrincipal
* aPrincipal
, Document
* aDoc
,
126 nsIHttpChannel
* aChannel
,
127 InternalRequest
& aRequest
) {
128 MOZ_ASSERT(NS_IsMainThread());
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
);
145 // From "Determine request's Referrer" step 3
146 // "If request's referrer is a URL, let referrerSource be request's
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();
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
);
171 class StoreOptimizedEncodingRunnable final
: public Runnable
{
172 nsMainThreadPtrHandle
<nsICacheInfoChannel
> mCache
;
173 Vector
<uint8_t> mBytes
;
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
{
186 nsCOMPtr
<nsIAsyncOutputStream
> stream
;
187 rv
= mCache
->OpenAlternativeOutputStream(FetchUtil::WasmAltDataType
,
188 int64_t(mBytes
.length()),
189 getter_AddRefs(stream
));
194 auto closeStream
= MakeScopeExit([&]() { stream
->CloseWithStatus(rv
); });
197 rv
= stream
->Write((char*)mBytes
.begin(), mBytes
.length(), &written
);
202 MOZ_RELEASE_ASSERT(mBytes
.length() == written
);
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();
220 obs
->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC
);
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
)) {
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
))) {
248 return self
.forget();
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);
263 nsCOMPtr
<nsPIDOMWindowInner
> window
= do_QueryInterface(mGlobal
);
264 if (!SameCOMIdentity(aSubject
, window
)) {
268 // mStream->Close() will call JSStreamConsumer::OnInputStreamReady which may
269 // then destory itself, dropping the last reference to 'this'.
270 RefPtr
<WindowStreamOwner
> keepAlive(this);
279 NS_IMPL_ISUPPORTS(WindowStreamOwner
, nsIObserver
, nsISupportsWeakReference
)
281 inline nsISupports
* ToSupports(WindowStreamOwner
* aObj
) {
282 return static_cast<nsIObserver
*>(aObj
);
285 class WorkerStreamOwner final
{
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
));
300 StrongWorkerRef::Create(aWorker
, "JSStreamConsumer", [self
]() {
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
306 self
->mStream
->Close();
307 self
->mStream
= nullptr;
311 if (!self
->mWorkerRef
) {
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);
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
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
;
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);
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
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
;
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.
465 (ret
== Z_OK
|| ret
== Z_STREAM_END
) && self
->mZStream
.avail_in
== 0;
467 return NS_ERROR_UNEXPECTED
;
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
,
474 self
->mConsumerAborted
= true;
475 return NS_ERROR_UNEXPECTED
;
479 *aWriteCount
= aCount
;
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
))) {
497 RefPtr
<JSStreamConsumer
> consumer
;
499 RefPtr
<WorkerStreamOwner
> owner
= WorkerStreamOwner::Create(
500 asyncStream
, aMaybeWorker
, aGlobal
->SerialEventTarget());
505 consumer
= new JSStreamConsumer(std::move(owner
), aGlobal
, aConsumer
,
506 std::move(aCache
), aOptimizedEncoding
);
508 RefPtr
<WindowStreamOwner
> owner
=
509 WindowStreamOwner::Create(asyncStream
, aGlobal
);
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:
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
);
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;
551 mConsumer
->streamError(size_t(NS_ERROR_UNEXPECTED
));
555 mConsumer
->consumeOptimizedEncoding(mOptimizedEncodingBytes
.begin(),
556 mOptimizedEncodingBytes
.length());
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);
568 mConsumer
->streamError(size_t(rv
));
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
) {
579 if (NS_WARN_IF(NS_FAILED(rv
))) {
580 mConsumer
->streamError(size_t(rv
));
584 rv
= aStream
->AsyncWait(this, 0, 0, nullptr);
585 if (NS_WARN_IF(NS_FAILED(rv
))) {
586 mConsumer
->streamError(size_t(rv
));
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");
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
) {
615 auto autoDestroy
= MakeScopeExit([&]() { deflateEnd(&zstream
); });
617 Vector
<uint8_t> dstBytes
;
618 if (!dstBytes
.resizeUninitialized(PrefixBytes
+
619 deflateBound(&zstream
, aSrcLength
))) {
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
) {
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
)
647 const nsCString
FetchUtil::WasmAltDataType
;
650 void FetchUtil::InitWasmAltDataType() {
651 nsCString
& type
= const_cast<nsCString
&>(WasmAltDataType
);
652 MOZ_ASSERT(type
.IsEmpty());
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
);
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
);
685 return ThrowException(aCx
, JSMSG_WASM_BAD_RESPONSE_VALUE
);
688 const char* requiredMimeType
= nullptr;
690 case JS::MimeType::Wasm
:
691 requiredMimeType
= WASM_CONTENT_TYPE
;
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(),
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
);
721 case JS::MimeType::Wasm
:
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());
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
));
776 aConsumer
->streamEnd();
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
);
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
)) {
810 JS_SetPendingException(aCx
, value
);
813 } // namespace mozilla::dom