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 "ScriptLoader.h"
8 
9 #include <algorithm>
10 #include <type_traits>
11 
12 #include "nsIChannel.h"
13 #include "nsIContentPolicy.h"
14 #include "nsIContentSecurityPolicy.h"
15 #include "nsICookieJarSettings.h"
16 #include "nsIDocShell.h"
17 #include "nsIHttpChannel.h"
18 #include "nsIHttpChannelInternal.h"
19 #include "nsIInputStreamPump.h"
20 #include "nsIIOService.h"
21 #include "nsIOService.h"
22 #include "nsIPrincipal.h"
23 #include "nsIProtocolHandler.h"
24 #include "nsIScriptError.h"
25 #include "nsIScriptSecurityManager.h"
26 #include "nsIStreamLoader.h"
27 #include "nsIStreamListenerTee.h"
28 #include "nsIThreadRetargetableRequest.h"
29 #include "nsIURI.h"
30 #include "nsIXPConnect.h"
31 
32 #include "jsapi.h"
33 #include "jsfriendapi.h"
34 #include "js/CompilationAndEvaluation.h"
35 #include "js/Exception.h"
36 #include "js/SourceText.h"
37 #include "nsError.h"
38 #include "nsComponentManagerUtils.h"
39 #include "nsContentPolicyUtils.h"
40 #include "nsContentUtils.h"
41 #include "nsDocShellCID.h"
42 #include "nsJSEnvironment.h"
43 #include "nsNetUtil.h"
44 #include "nsIPipe.h"
45 #include "nsIOutputStream.h"
46 #include "nsPrintfCString.h"
47 #include "nsString.h"
48 #include "nsStreamUtils.h"
49 #include "nsTArray.h"
50 #include "nsThreadUtils.h"
51 #include "nsXPCOM.h"
52 #include "xpcpublic.h"
53 
54 #include "mozilla/ArrayAlgorithm.h"
55 #include "mozilla/Assertions.h"
56 #include "mozilla/LoadContext.h"
57 #include "mozilla/Maybe.h"
58 #include "mozilla/ipc/BackgroundUtils.h"
59 #include "mozilla/dom/BlobURLProtocolHandler.h"
60 #include "mozilla/dom/CacheBinding.h"
61 #include "mozilla/dom/cache/CacheTypes.h"
62 #include "mozilla/dom/cache/Cache.h"
63 #include "mozilla/dom/cache/CacheStorage.h"
64 #include "mozilla/dom/ChannelInfo.h"
65 #include "mozilla/dom/ClientChannelHelper.h"
66 #include "mozilla/dom/ClientInfo.h"
67 #include "mozilla/dom/Exceptions.h"
68 #include "mozilla/dom/InternalResponse.h"
69 #include "mozilla/dom/nsCSPService.h"
70 #include "mozilla/dom/nsCSPUtils.h"
71 #include "mozilla/dom/PerformanceStorage.h"
72 #include "mozilla/dom/Promise.h"
73 #include "mozilla/dom/PromiseNativeHandler.h"
74 #include "mozilla/dom/Response.h"
75 #include "mozilla/dom/ScriptLoader.h"
76 #include "mozilla/dom/ScriptSettings.h"
77 #include "mozilla/dom/SerializedStackHolder.h"
78 #include "mozilla/dom/SRILogHelper.h"
79 #include "mozilla/dom/SerializedStackHolder.h"
80 #include "mozilla/dom/ServiceWorkerBinding.h"
81 #include "mozilla/dom/ServiceWorkerManager.h"
82 #include "mozilla/Result.h"
83 #include "mozilla/ResultExtensions.h"
84 #include "mozilla/StaticPrefs_browser.h"
85 #include "mozilla/StaticPrefs_dom.h"
86 #include "mozilla/StaticPrefs_security.h"
87 #include "mozilla/UniquePtr.h"
88 #include "Principal.h"
89 #include "WorkerPrivate.h"
90 #include "WorkerRunnable.h"
91 #include "WorkerScope.h"
92 
93 #define MAX_CONCURRENT_SCRIPTS 1000
94 
95 using mozilla::dom::cache::Cache;
96 using mozilla::dom::cache::CacheStorage;
97 using mozilla::ipc::PrincipalInfo;
98 
99 namespace mozilla {
100 namespace dom {
101 
102 namespace {
103 
GetBaseURI(bool aIsMainScript,WorkerPrivate * aWorkerPrivate)104 nsIURI* GetBaseURI(bool aIsMainScript, WorkerPrivate* aWorkerPrivate) {
105   MOZ_ASSERT(aWorkerPrivate);
106   nsIURI* baseURI;
107   WorkerPrivate* parentWorker = aWorkerPrivate->GetParent();
108   if (aIsMainScript) {
109     if (parentWorker) {
110       baseURI = parentWorker->GetBaseURI();
111       NS_ASSERTION(baseURI, "Should have been set already!");
112     } else {
113       // May be null.
114       baseURI = aWorkerPrivate->GetBaseURI();
115     }
116   } else {
117     baseURI = aWorkerPrivate->GetBaseURI();
118     NS_ASSERTION(baseURI, "Should have been set already!");
119   }
120 
121   return baseURI;
122 }
123 
ConstructURI(const nsAString & aScriptURL,nsIURI * baseURI,Document * parentDoc,bool aDefaultURIEncoding,nsIURI ** aResult)124 nsresult ConstructURI(const nsAString& aScriptURL, nsIURI* baseURI,
125                       Document* parentDoc, bool aDefaultURIEncoding,
126                       nsIURI** aResult) {
127   nsresult rv;
128   if (aDefaultURIEncoding) {
129     rv = NS_NewURI(aResult, aScriptURL, nullptr, baseURI);
130   } else {
131     rv = nsContentUtils::NewURIWithDocumentCharset(aResult, aScriptURL,
132                                                    parentDoc, baseURI);
133   }
134 
135   if (NS_FAILED(rv)) {
136     return NS_ERROR_DOM_SYNTAX_ERR;
137   }
138   return NS_OK;
139 }
140 
ChannelFromScriptURL(nsIPrincipal * principal,Document * parentDoc,WorkerPrivate * aWorkerPrivate,nsILoadGroup * loadGroup,nsIIOService * ios,nsIScriptSecurityManager * secMan,nsIURI * aScriptURL,const Maybe<ClientInfo> & aClientInfo,const Maybe<ServiceWorkerDescriptor> & aController,bool aIsMainScript,WorkerScriptType aWorkerScriptType,nsContentPolicyType aMainScriptContentPolicyType,nsLoadFlags aLoadFlags,nsICookieJarSettings * aCookieJarSettings,nsIReferrerInfo * aReferrerInfo,nsIChannel ** aChannel)141 nsresult ChannelFromScriptURL(
142     nsIPrincipal* principal, Document* parentDoc, WorkerPrivate* aWorkerPrivate,
143     nsILoadGroup* loadGroup, nsIIOService* ios,
144     nsIScriptSecurityManager* secMan, nsIURI* aScriptURL,
145     const Maybe<ClientInfo>& aClientInfo,
146     const Maybe<ServiceWorkerDescriptor>& aController, bool aIsMainScript,
147     WorkerScriptType aWorkerScriptType,
148     nsContentPolicyType aMainScriptContentPolicyType, nsLoadFlags aLoadFlags,
149     nsICookieJarSettings* aCookieJarSettings, nsIReferrerInfo* aReferrerInfo,
150     nsIChannel** aChannel) {
151   AssertIsOnMainThread();
152 
153   nsresult rv;
154   nsCOMPtr<nsIURI> uri = aScriptURL;
155 
156   // If we have the document, use it. Unfortunately, for dedicated workers
157   // 'parentDoc' ends up being the parent document, which is not the document
158   // that we want to use. So make sure to avoid using 'parentDoc' in that
159   // situation.
160   if (parentDoc && parentDoc->NodePrincipal() != principal) {
161     parentDoc = nullptr;
162   }
163 
164   uint32_t secFlags =
165       aIsMainScript ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
166                     : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT;
167 
168   bool inheritAttrs = nsContentUtils::ChannelShouldInheritPrincipal(
169       principal, uri, true /* aInheritForAboutBlank */,
170       false /* aForceInherit */);
171 
172   bool isData = uri->SchemeIs("data");
173   if (inheritAttrs && !isData) {
174     secFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
175   }
176 
177   if (aWorkerScriptType == DebuggerScript) {
178     // A DebuggerScript needs to be a local resource like chrome: or resource:
179     bool isUIResource = false;
180     rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_UI_RESOURCE,
181                              &isUIResource);
182     if (NS_WARN_IF(NS_FAILED(rv))) {
183       return rv;
184     }
185 
186     if (!isUIResource) {
187       return NS_ERROR_DOM_SECURITY_ERR;
188     }
189 
190     secFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
191   }
192 
193   // Note: this is for backwards compatibility and goes against spec.
194   // We should find a better solution.
195   if (aIsMainScript && isData) {
196     secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
197   }
198 
199   nsContentPolicyType contentPolicyType =
200       aIsMainScript ? aMainScriptContentPolicyType
201                     : nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS;
202 
203   // The main service worker script should never be loaded over the network
204   // in this path.  It should always be offlined by ServiceWorkerScriptCache.
205   // We assert here since this error should also be caught by the runtime
206   // check in CacheScriptLoader.
207   //
208   // Note, if we ever allow service worker scripts to be loaded from network
209   // here we need to configure the channel properly.  For example, it must
210   // not allow redirects.
211   MOZ_DIAGNOSTIC_ASSERT(contentPolicyType !=
212                         nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER);
213 
214   nsCOMPtr<nsIChannel> channel;
215   if (parentDoc) {
216     rv = NS_NewChannel(getter_AddRefs(channel), uri, parentDoc, secFlags,
217                        contentPolicyType,
218                        nullptr,  // aPerformanceStorage
219                        loadGroup,
220                        nullptr,  // aCallbacks
221                        aLoadFlags, ios);
222     NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_ERR);
223   } else {
224     // We must have a loadGroup with a load context for the principal to
225     // traverse the channel correctly.
226     MOZ_ASSERT(loadGroup);
227     MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(loadGroup, principal));
228 
229     RefPtr<PerformanceStorage> performanceStorage;
230     nsCOMPtr<nsICSPEventListener> cspEventListener;
231     if (aWorkerPrivate && !aIsMainScript) {
232       performanceStorage = aWorkerPrivate->GetPerformanceStorage();
233       cspEventListener = aWorkerPrivate->CSPEventListener();
234     }
235 
236     if (aClientInfo.isSome()) {
237       rv = NS_NewChannel(getter_AddRefs(channel), uri, principal,
238                          aClientInfo.ref(), aController, secFlags,
239                          contentPolicyType, aCookieJarSettings,
240                          performanceStorage, loadGroup, nullptr,  // aCallbacks
241                          aLoadFlags, ios);
242     } else {
243       rv = NS_NewChannel(getter_AddRefs(channel), uri, principal, secFlags,
244                          contentPolicyType, aCookieJarSettings,
245                          performanceStorage, loadGroup, nullptr,  // aCallbacks
246                          aLoadFlags, ios);
247     }
248 
249     NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_ERR);
250 
251     if (cspEventListener) {
252       nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
253       rv = loadInfo->SetCspEventListener(cspEventListener);
254       NS_ENSURE_SUCCESS(rv, rv);
255     }
256   }
257 
258   if (aReferrerInfo) {
259     nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
260     if (httpChannel) {
261       rv = httpChannel->SetReferrerInfo(aReferrerInfo);
262       if (NS_WARN_IF(NS_FAILED(rv))) {
263         return rv;
264       }
265     }
266   }
267 
268   channel.forget(aChannel);
269   return rv;
270 }
271 
272 struct ScriptLoadInfo {
ScriptLoadInfomozilla::dom::__anon58a9f5d30111::ScriptLoadInfo273   ScriptLoadInfo() {
274     MOZ_ASSERT(mScriptIsUTF8 == false, "set by member initializer");
275     MOZ_ASSERT(mScriptLength == 0, "set by member initializer");
276     mScript.mUTF16 = nullptr;
277   }
278 
~ScriptLoadInfomozilla::dom::__anon58a9f5d30111::ScriptLoadInfo279   ~ScriptLoadInfo() {
280     if (void* data = mScriptIsUTF8 ? static_cast<void*>(mScript.mUTF8)
281                                    : static_cast<void*>(mScript.mUTF16)) {
282       js_free(data);
283     }
284   }
285 
286   nsString mURL;
287 
288   // This full URL string is populated only if this object is used in a
289   // ServiceWorker.
290   nsString mFullURL;
291 
292   // This promise is set only when the script is for a ServiceWorker but
293   // it's not in the cache yet. The promise is resolved when the full body is
294   // stored into the cache.  mCachePromise will be set to nullptr after
295   // resolution.
296   RefPtr<Promise> mCachePromise;
297 
298   // The reader stream the cache entry should be filled from, for those cases
299   // when we're going to have an mCachePromise.
300   nsCOMPtr<nsIInputStream> mCacheReadStream;
301 
302   nsCOMPtr<nsIChannel> mChannel;
303   Maybe<ClientInfo> mReservedClientInfo;
304   nsresult mLoadResult = NS_ERROR_NOT_INITIALIZED;
305 
306   // If |mScriptIsUTF8|, then |mUTF8| is active, otherwise |mUTF16| is active.
307   union {
308     char16_t* mUTF16;
309     Utf8Unit* mUTF8;
310   } mScript;
311   size_t mScriptLength = 0;  // in code units
312   bool mScriptIsUTF8 = false;
313 
ScriptTextIsNullmozilla::dom::__anon58a9f5d30111::ScriptLoadInfo314   bool ScriptTextIsNull() const {
315     return mScriptIsUTF8 ? mScript.mUTF8 == nullptr : mScript.mUTF16 == nullptr;
316   }
317 
InitUTF8Scriptmozilla::dom::__anon58a9f5d30111::ScriptLoadInfo318   void InitUTF8Script() {
319     MOZ_ASSERT(ScriptTextIsNull());
320     MOZ_ASSERT(mScriptLength == 0);
321 
322     mScriptIsUTF8 = true;
323     mScript.mUTF8 = nullptr;
324     mScriptLength = 0;
325   }
326 
InitUTF16Scriptmozilla::dom::__anon58a9f5d30111::ScriptLoadInfo327   void InitUTF16Script() {
328     MOZ_ASSERT(ScriptTextIsNull());
329     MOZ_ASSERT(mScriptLength == 0);
330 
331     mScriptIsUTF8 = false;
332     mScript.mUTF16 = nullptr;
333     mScriptLength = 0;
334   }
335 
336   bool mLoadingFinished = false;
337   bool mExecutionScheduled = false;
338   bool mExecutionResult = false;
339 
340   Maybe<nsString> mSourceMapURL;
341 
342   enum CacheStatus {
343     // By default a normal script is just loaded from the network. But for
344     // ServiceWorkers, we have to check if the cache contains the script and
345     // load it from the cache.
346     Uncached,
347 
348     WritingToCache,
349 
350     ReadingFromCache,
351 
352     // This script has been loaded from the ServiceWorker cache.
353     Cached,
354 
355     // This script must be stored in the ServiceWorker cache.
356     ToBeCached,
357 
358     // Something went wrong or the worker went away.
359     Cancel
360   };
361 
362   CacheStatus mCacheStatus = Uncached;
363 
364   nsLoadFlags mLoadFlags = nsIRequest::LOAD_NORMAL;
365 
366   Maybe<bool> mMutedErrorFlag;
367 
Finishedmozilla::dom::__anon58a9f5d30111::ScriptLoadInfo368   bool Finished() const {
369     return mLoadingFinished && !mCachePromise && !mChannel;
370   }
371 };
372 
373 class ScriptLoaderRunnable;
374 
375 class ScriptExecutorRunnable final : public MainThreadWorkerSyncRunnable {
376   ScriptLoaderRunnable& mScriptLoader;
377   const bool mIsWorkerScript;
378   const Span<ScriptLoadInfo> mLoadInfosAlreadyExecuted, mLoadInfosToExecute;
379 
380  public:
381   ScriptExecutorRunnable(ScriptLoaderRunnable& aScriptLoader,
382                          nsIEventTarget* aSyncLoopTarget, bool aIsWorkerScript,
383                          Span<ScriptLoadInfo> aLoadInfosAlreadyExecuted,
384                          Span<ScriptLoadInfo> aLoadInfosToExecute);
385 
386  private:
387   ~ScriptExecutorRunnable() = default;
388 
389   virtual bool IsDebuggerRunnable() const override;
390 
391   virtual bool PreRun(WorkerPrivate* aWorkerPrivate) override;
392 
393   virtual bool WorkerRun(JSContext* aCx,
394                          WorkerPrivate* aWorkerPrivate) override;
395 
396   virtual void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
397                        bool aRunResult) override;
398 
399   nsresult Cancel() override;
400 
401   void ShutdownScriptLoader(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
402                             bool aResult, bool aMutedError);
403 
404   void LogExceptionToConsole(JSContext* aCx, WorkerPrivate* WorkerPrivate);
405 
406   bool AllScriptsExecutable() const;
407 };
408 
409 class CacheScriptLoader;
410 
411 class CacheCreator final : public PromiseNativeHandler {
412  public:
413   NS_DECL_ISUPPORTS
414 
CacheCreator(WorkerPrivate * aWorkerPrivate)415   explicit CacheCreator(WorkerPrivate* aWorkerPrivate)
416       : mCacheName(aWorkerPrivate->ServiceWorkerCacheName()),
417         mOriginAttributes(aWorkerPrivate->GetOriginAttributes()) {
418     MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
419     AssertIsOnMainThread();
420   }
421 
AddLoader(MovingNotNull<RefPtr<CacheScriptLoader>> aLoader)422   void AddLoader(MovingNotNull<RefPtr<CacheScriptLoader>> aLoader) {
423     AssertIsOnMainThread();
424     MOZ_ASSERT(!mCacheStorage);
425     mLoaders.AppendElement(std::move(aLoader));
426   }
427 
428   virtual void ResolvedCallback(JSContext* aCx,
429                                 JS::Handle<JS::Value> aValue) override;
430 
431   virtual void RejectedCallback(JSContext* aCx,
432                                 JS::Handle<JS::Value> aValue) override;
433 
434   // Try to load from cache with aPrincipal used for cache access.
435   nsresult Load(nsIPrincipal* aPrincipal);
436 
Cache_() const437   Cache* Cache_() const {
438     AssertIsOnMainThread();
439     MOZ_ASSERT(mCache);
440     return mCache;
441   }
442 
Global() const443   nsIGlobalObject* Global() const {
444     AssertIsOnMainThread();
445     MOZ_ASSERT(mSandboxGlobalObject);
446     return mSandboxGlobalObject;
447   }
448 
449   void DeleteCache();
450 
451  private:
452   ~CacheCreator() = default;
453 
454   nsresult CreateCacheStorage(nsIPrincipal* aPrincipal);
455 
456   void FailLoaders(nsresult aRv);
457 
458   RefPtr<Cache> mCache;
459   RefPtr<CacheStorage> mCacheStorage;
460   nsCOMPtr<nsIGlobalObject> mSandboxGlobalObject;
461   nsTArray<NotNull<RefPtr<CacheScriptLoader>>> mLoaders;
462 
463   nsString mCacheName;
464   OriginAttributes mOriginAttributes;
465 };
466 
467 NS_IMPL_ISUPPORTS0(CacheCreator)
468 
469 class CacheScriptLoader final : public PromiseNativeHandler,
470                                 public nsIStreamLoaderObserver {
471  public:
472   NS_DECL_ISUPPORTS
473   NS_DECL_NSISTREAMLOADEROBSERVER
474 
CacheScriptLoader(WorkerPrivate * aWorkerPrivate,ScriptLoadInfo & aLoadInfo,bool aIsWorkerScript,ScriptLoaderRunnable * aRunnable)475   CacheScriptLoader(WorkerPrivate* aWorkerPrivate, ScriptLoadInfo& aLoadInfo,
476                     bool aIsWorkerScript, ScriptLoaderRunnable* aRunnable)
477       : mLoadInfo(aLoadInfo),
478         mRunnable(aRunnable),
479         mIsWorkerScript(aIsWorkerScript),
480         mFailed(false),
481         mState(aWorkerPrivate->GetServiceWorkerDescriptor().State()) {
482     MOZ_ASSERT(aWorkerPrivate);
483     MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
484     mMainThreadEventTarget = aWorkerPrivate->MainThreadEventTarget();
485     MOZ_ASSERT(mMainThreadEventTarget);
486     mBaseURI = GetBaseURI(mIsWorkerScript, aWorkerPrivate);
487     AssertIsOnMainThread();
488   }
489 
490   void Fail(nsresult aRv);
491 
492   void Load(Cache* aCache);
493 
494   virtual void ResolvedCallback(JSContext* aCx,
495                                 JS::Handle<JS::Value> aValue) override;
496 
497   virtual void RejectedCallback(JSContext* aCx,
498                                 JS::Handle<JS::Value> aValue) override;
499 
500  private:
~CacheScriptLoader()501   ~CacheScriptLoader() { AssertIsOnMainThread(); }
502 
503   ScriptLoadInfo& mLoadInfo;
504   const RefPtr<ScriptLoaderRunnable> mRunnable;
505   const bool mIsWorkerScript;
506   bool mFailed;
507   const ServiceWorkerState mState;
508   nsCOMPtr<nsIInputStreamPump> mPump;
509   nsCOMPtr<nsIURI> mBaseURI;
510   mozilla::dom::ChannelInfo mChannelInfo;
511   UniquePtr<PrincipalInfo> mPrincipalInfo;
512   nsCString mCSPHeaderValue;
513   nsCString mCSPReportOnlyHeaderValue;
514   nsCString mReferrerPolicyHeaderValue;
515   nsCOMPtr<nsIEventTarget> mMainThreadEventTarget;
516 };
517 
518 NS_IMPL_ISUPPORTS(CacheScriptLoader, nsIStreamLoaderObserver)
519 
520 class CachePromiseHandler final : public PromiseNativeHandler {
521  public:
522   NS_DECL_ISUPPORTS
523 
CachePromiseHandler(ScriptLoaderRunnable * aRunnable,ScriptLoadInfo & aLoadInfo)524   CachePromiseHandler(ScriptLoaderRunnable* aRunnable,
525                       ScriptLoadInfo& aLoadInfo)
526       : mRunnable(aRunnable), mLoadInfo(aLoadInfo) {
527     AssertIsOnMainThread();
528     MOZ_ASSERT(mRunnable);
529   }
530 
531   virtual void ResolvedCallback(JSContext* aCx,
532                                 JS::Handle<JS::Value> aValue) override;
533 
534   virtual void RejectedCallback(JSContext* aCx,
535                                 JS::Handle<JS::Value> aValue) override;
536 
537  private:
~CachePromiseHandler()538   ~CachePromiseHandler() { AssertIsOnMainThread(); }
539 
540   RefPtr<ScriptLoaderRunnable> mRunnable;
541   ScriptLoadInfo& mLoadInfo;
542 };
543 
544 NS_IMPL_ISUPPORTS0(CachePromiseHandler)
545 
546 class LoaderListener final : public nsIStreamLoaderObserver,
547                              public nsIRequestObserver {
548  public:
549   NS_DECL_ISUPPORTS
550 
LoaderListener(ScriptLoaderRunnable * aRunnable,ScriptLoadInfo & aLoadInfo)551   LoaderListener(ScriptLoaderRunnable* aRunnable, ScriptLoadInfo& aLoadInfo)
552       : mRunnable(aRunnable), mLoadInfo(aLoadInfo) {
553     MOZ_ASSERT(mRunnable);
554   }
555 
556   NS_IMETHOD
557   OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
558                    nsresult aStatus, uint32_t aStringLen,
559                    const uint8_t* aString) override;
560 
561   NS_IMETHOD
562   OnStartRequest(nsIRequest* aRequest) override;
563 
564   NS_IMETHOD
OnStopRequest(nsIRequest * aRequest,nsresult aStatusCode)565   OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) override {
566     // Nothing to do here!
567     return NS_OK;
568   }
569 
570  private:
571   ~LoaderListener() = default;
572 
573   RefPtr<ScriptLoaderRunnable> mRunnable;
574   ScriptLoadInfo& mLoadInfo;
575 };
576 
577 NS_IMPL_ISUPPORTS(LoaderListener, nsIStreamLoaderObserver, nsIRequestObserver)
578 
579 class ScriptResponseHeaderProcessor final : public nsIRequestObserver {
580  public:
581   NS_DECL_ISUPPORTS
582 
ScriptResponseHeaderProcessor(WorkerPrivate * aWorkerPrivate,bool aIsMainScript)583   ScriptResponseHeaderProcessor(WorkerPrivate* aWorkerPrivate,
584                                 bool aIsMainScript)
585       : mWorkerPrivate(aWorkerPrivate), mIsMainScript(aIsMainScript) {
586     AssertIsOnMainThread();
587   }
588 
OnStartRequest(nsIRequest * aRequest)589   NS_IMETHOD OnStartRequest(nsIRequest* aRequest) override {
590     if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
591       return NS_OK;
592     }
593 
594     nsresult rv = ProcessCrossOriginEmbedderPolicyHeader(aRequest);
595 
596     if (NS_WARN_IF(NS_FAILED(rv))) {
597       aRequest->Cancel(rv);
598     }
599 
600     return rv;
601   }
602 
OnStopRequest(nsIRequest * aRequest,nsresult aStatusCode)603   NS_IMETHOD OnStopRequest(nsIRequest* aRequest,
604                            nsresult aStatusCode) override {
605     return NS_OK;
606   }
607 
ProcessCrossOriginEmbedderPolicyHeader(WorkerPrivate * aWorkerPrivate,nsILoadInfo::CrossOriginEmbedderPolicy aPolicy,bool aIsMainScript)608   static nsresult ProcessCrossOriginEmbedderPolicyHeader(
609       WorkerPrivate* aWorkerPrivate,
610       nsILoadInfo::CrossOriginEmbedderPolicy aPolicy, bool aIsMainScript) {
611     MOZ_ASSERT(aWorkerPrivate);
612 
613     if (aIsMainScript) {
614       MOZ_TRY(aWorkerPrivate->SetEmbedderPolicy(aPolicy));
615     } else {
616       // NOTE: Spec doesn't mention non-main scripts must match COEP header with
617       // the main script, but it must pass CORP checking.
618       // see: wpt window-simple-success.https.html, the worker import script
619       // test-incrementer.js without coep header.
620       Unused << NS_WARN_IF(!aWorkerPrivate->MatchEmbedderPolicy(aPolicy));
621     }
622 
623     return NS_OK;
624   }
625 
626  private:
627   ~ScriptResponseHeaderProcessor() = default;
628 
ProcessCrossOriginEmbedderPolicyHeader(nsIRequest * aRequest)629   nsresult ProcessCrossOriginEmbedderPolicyHeader(nsIRequest* aRequest) {
630     nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(aRequest);
631 
632     // NOTE: the spec doesn't say what to do with non-HTTP workers.
633     // See: https://github.com/whatwg/html/issues/4916
634     if (!httpChannel) {
635       if (mIsMainScript) {
636         mWorkerPrivate->InheritOwnerEmbedderPolicyOrNull(aRequest);
637       }
638 
639       return NS_OK;
640     }
641 
642     nsILoadInfo::CrossOriginEmbedderPolicy coep;
643     MOZ_TRY(httpChannel->GetResponseEmbedderPolicy(&coep));
644 
645     return ProcessCrossOriginEmbedderPolicyHeader(mWorkerPrivate, coep,
646                                                   mIsMainScript);
647   }
648 
649   WorkerPrivate* const mWorkerPrivate;
650   const bool mIsMainScript;
651 };
652 
653 NS_IMPL_ISUPPORTS(ScriptResponseHeaderProcessor, nsIRequestObserver);
654 
655 class ScriptLoaderRunnable final : public nsIRunnable, public nsINamed {
656   friend class ScriptExecutorRunnable;
657   friend class CachePromiseHandler;
658   friend class CacheScriptLoader;
659   friend class LoaderListener;
660 
661   WorkerPrivate* const mWorkerPrivate;
662   UniquePtr<SerializedStackHolder> mOriginStack;
663   nsString mOriginStackJSON;
664   nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
665   nsTArrayView<ScriptLoadInfo> mLoadInfos;
666   RefPtr<CacheCreator> mCacheCreator;
667   Maybe<ClientInfo> mClientInfo;
668   Maybe<ServiceWorkerDescriptor> mController;
669   const bool mIsMainScript;
670   WorkerScriptType mWorkerScriptType;
671   bool mCanceledMainThread;
672   ErrorResult& mRv;
673 
674  public:
675   NS_DECL_THREADSAFE_ISUPPORTS
676 
ScriptLoaderRunnable(WorkerPrivate * aWorkerPrivate,UniquePtr<SerializedStackHolder> aOriginStack,nsIEventTarget * aSyncLoopTarget,nsTArray<ScriptLoadInfo> aLoadInfos,const Maybe<ClientInfo> & aClientInfo,const Maybe<ServiceWorkerDescriptor> & aController,bool aIsMainScript,WorkerScriptType aWorkerScriptType,ErrorResult & aRv)677   ScriptLoaderRunnable(WorkerPrivate* aWorkerPrivate,
678                        UniquePtr<SerializedStackHolder> aOriginStack,
679                        nsIEventTarget* aSyncLoopTarget,
680                        nsTArray<ScriptLoadInfo> aLoadInfos,
681                        const Maybe<ClientInfo>& aClientInfo,
682                        const Maybe<ServiceWorkerDescriptor>& aController,
683                        bool aIsMainScript, WorkerScriptType aWorkerScriptType,
684                        ErrorResult& aRv)
685       : mWorkerPrivate(aWorkerPrivate),
686         mOriginStack(std::move(aOriginStack)),
687         mSyncLoopTarget(aSyncLoopTarget),
688         mLoadInfos(std::move(aLoadInfos)),
689         mClientInfo(aClientInfo),
690         mController(aController),
691         mIsMainScript(aIsMainScript),
692         mWorkerScriptType(aWorkerScriptType),
693         mCanceledMainThread(false),
694         mRv(aRv) {
695     aWorkerPrivate->AssertIsOnWorkerThread();
696     MOZ_ASSERT(aSyncLoopTarget);
697     MOZ_ASSERT_IF(aIsMainScript, mLoadInfos.Length() == 1);
698   }
699 
CancelMainThreadWithBindingAborted()700   void CancelMainThreadWithBindingAborted() {
701     CancelMainThread(NS_BINDING_ABORTED);
702   }
703 
704  private:
705   ~ScriptLoaderRunnable() = default;
706 
707   NS_IMETHOD
Run()708   Run() override {
709     AssertIsOnMainThread();
710 
711     nsresult rv = RunInternal();
712     if (NS_WARN_IF(NS_FAILED(rv))) {
713       CancelMainThread(rv);
714     }
715 
716     return NS_OK;
717   }
718 
719   NS_IMETHOD
GetName(nsACString & aName)720   GetName(nsACString& aName) override {
721     aName.AssignLiteral("ScriptLoaderRunnable");
722     return NS_OK;
723   }
724 
LoadingFinished(ScriptLoadInfo & aLoadInfo,nsresult aRv)725   void LoadingFinished(ScriptLoadInfo& aLoadInfo, nsresult aRv) {
726     AssertIsOnMainThread();
727 
728     aLoadInfo.mLoadResult = aRv;
729 
730     MOZ_ASSERT(!aLoadInfo.mLoadingFinished);
731     aLoadInfo.mLoadingFinished = true;
732 
733     if (IsMainWorkerScript() && NS_SUCCEEDED(aRv)) {
734       MOZ_DIAGNOSTIC_ASSERT(mWorkerPrivate->PrincipalURIMatchesScriptURL());
735     }
736 
737     MaybeExecuteFinishedScripts(aLoadInfo);
738   }
739 
MaybeExecuteFinishedScripts(const ScriptLoadInfo & aLoadInfo)740   void MaybeExecuteFinishedScripts(const ScriptLoadInfo& aLoadInfo) {
741     AssertIsOnMainThread();
742 
743     // We execute the last step if we don't have a pending operation with the
744     // cache and the loading is completed.
745     if (aLoadInfo.Finished()) {
746       ExecuteFinishedScripts();
747     }
748   }
749 
OnStreamComplete(nsIStreamLoader * aLoader,ScriptLoadInfo & aLoadInfo,nsresult aStatus,uint32_t aStringLen,const uint8_t * aString)750   nsresult OnStreamComplete(nsIStreamLoader* aLoader, ScriptLoadInfo& aLoadInfo,
751                             nsresult aStatus, uint32_t aStringLen,
752                             const uint8_t* aString) {
753     AssertIsOnMainThread();
754 
755     nsresult rv = OnStreamCompleteInternal(aLoader, aStatus, aStringLen,
756                                            aString, aLoadInfo);
757     LoadingFinished(aLoadInfo, rv);
758     return NS_OK;
759   }
760 
OnStartRequest(nsIRequest * aRequest,ScriptLoadInfo & aLoadInfo)761   nsresult OnStartRequest(nsIRequest* aRequest, ScriptLoadInfo& aLoadInfo) {
762     nsresult rv = OnStartRequestInternal(aRequest, aLoadInfo);
763 
764     if (NS_WARN_IF(NS_FAILED(rv))) {
765       aRequest->Cancel(rv);
766     }
767 
768     return rv;
769   }
770 
OnStartRequestInternal(nsIRequest * aRequest,ScriptLoadInfo & aLoadInfo)771   nsresult OnStartRequestInternal(nsIRequest* aRequest,
772                                   ScriptLoadInfo& aLoadInfo) {
773     AssertIsOnMainThread();
774 
775     // If one load info cancels or hits an error, it can race with the start
776     // callback coming from another load info.
777     if (mCanceledMainThread || !mCacheCreator) {
778       return NS_ERROR_FAILURE;
779     }
780 
781     nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
782 
783     // Checking the MIME type is only required for ServiceWorkers'
784     // importScripts, per step 10 of
785     // https://w3c.github.io/ServiceWorker/#importscripts
786     //
787     // "Extract a MIME type from the response’s header list. If this MIME type
788     // (ignoring parameters) is not a JavaScript MIME type, return a network
789     // error."
790     if (mWorkerPrivate->IsServiceWorker()) {
791       nsAutoCString mimeType;
792       channel->GetContentType(mimeType);
793 
794       if (!nsContentUtils::IsJavascriptMIMEType(
795               NS_ConvertUTF8toUTF16(mimeType))) {
796         const nsCString& scope =
797             mWorkerPrivate->GetServiceWorkerRegistrationDescriptor().Scope();
798 
799         ServiceWorkerManager::LocalizeAndReportToAllClients(
800             scope, "ServiceWorkerRegisterMimeTypeError2",
801             nsTArray<nsString>{NS_ConvertUTF8toUTF16(scope),
802                                NS_ConvertUTF8toUTF16(mimeType),
803                                aLoadInfo.mURL});
804 
805         return NS_ERROR_DOM_NETWORK_ERR;
806       }
807     }
808 
809     // Note that importScripts() can redirect.  In theory the main
810     // script could also encounter an internal redirect, but currently
811     // the assert does not allow that.
812     MOZ_ASSERT_IF(mIsMainScript, channel == aLoadInfo.mChannel);
813     aLoadInfo.mChannel = channel;
814 
815     // We synthesize the result code, but its never exposed to content.
816     RefPtr<mozilla::dom::InternalResponse> ir =
817         new mozilla::dom::InternalResponse(200, "OK"_ns);
818     ir->SetBody(aLoadInfo.mCacheReadStream,
819                 InternalResponse::UNKNOWN_BODY_SIZE);
820 
821     // Drop our reference to the stream now that we've passed it along, so it
822     // doesn't hang around once the cache is done with it and keep data alive.
823     aLoadInfo.mCacheReadStream = nullptr;
824 
825     // Set the channel info of the channel on the response so that it's
826     // saved in the cache.
827     ir->InitChannelInfo(channel);
828 
829     // Save the principal of the channel since its URI encodes the script URI
830     // rather than the ServiceWorkerRegistrationInfo URI.
831     nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
832     NS_ASSERTION(ssm, "Should never be null!");
833 
834     nsCOMPtr<nsIPrincipal> channelPrincipal;
835     MOZ_TRY(ssm->GetChannelResultPrincipal(channel,
836                                            getter_AddRefs(channelPrincipal)));
837 
838     UniquePtr<PrincipalInfo> principalInfo(new PrincipalInfo());
839     MOZ_TRY(PrincipalToPrincipalInfo(channelPrincipal, principalInfo.get()));
840 
841     ir->SetPrincipalInfo(std::move(principalInfo));
842     ir->Headers()->FillResponseHeaders(aLoadInfo.mChannel);
843 
844     RefPtr<mozilla::dom::Response> response =
845         new mozilla::dom::Response(mCacheCreator->Global(), ir, nullptr);
846 
847     mozilla::dom::RequestOrUSVString request;
848 
849     MOZ_ASSERT(!aLoadInfo.mFullURL.IsEmpty());
850     request.SetAsUSVString().ShareOrDependUpon(aLoadInfo.mFullURL);
851 
852     // This JSContext will not end up executing JS code because here there are
853     // no ReadableStreams involved.
854     AutoJSAPI jsapi;
855     jsapi.Init();
856 
857     ErrorResult error;
858     RefPtr<Promise> cachePromise =
859         mCacheCreator->Cache_()->Put(jsapi.cx(), request, *response, error);
860     error.WouldReportJSException();
861     if (NS_WARN_IF(error.Failed())) {
862       return error.StealNSResult();
863     }
864 
865     RefPtr<CachePromiseHandler> promiseHandler =
866         new CachePromiseHandler(this, aLoadInfo);
867     cachePromise->AppendNativeHandler(promiseHandler);
868 
869     aLoadInfo.mCachePromise.swap(cachePromise);
870     aLoadInfo.mCacheStatus = ScriptLoadInfo::WritingToCache;
871 
872     return NS_OK;
873   }
874 
IsMainWorkerScript() const875   bool IsMainWorkerScript() const {
876     return mIsMainScript && mWorkerScriptType == WorkerScript;
877   }
878 
IsDebuggerScript() const879   bool IsDebuggerScript() const { return mWorkerScriptType == DebuggerScript; }
880 
CancelMainThread(nsresult aCancelResult)881   void CancelMainThread(nsresult aCancelResult) {
882     AssertIsOnMainThread();
883 
884     if (mCanceledMainThread) {
885       return;
886     }
887 
888     mCanceledMainThread = true;
889 
890     if (mCacheCreator) {
891       MOZ_ASSERT(mWorkerPrivate->IsServiceWorker());
892       DeleteCache();
893     }
894 
895     // Cancel all the channels that were already opened.
896     for (ScriptLoadInfo& loadInfo : mLoadInfos) {
897       // If promise or channel is non-null, their failures will lead to
898       // LoadingFinished being called.
899       bool callLoadingFinished = true;
900 
901       if (loadInfo.mCachePromise) {
902         MOZ_ASSERT(mWorkerPrivate->IsServiceWorker());
903         loadInfo.mCachePromise->MaybeReject(aCancelResult);
904         loadInfo.mCachePromise = nullptr;
905         callLoadingFinished = false;
906       }
907 
908       if (loadInfo.mChannel) {
909         if (NS_SUCCEEDED(loadInfo.mChannel->Cancel(aCancelResult))) {
910           callLoadingFinished = false;
911         } else {
912           NS_WARNING("Failed to cancel channel!");
913         }
914       }
915 
916       if (callLoadingFinished && !loadInfo.Finished()) {
917         LoadingFinished(loadInfo, aCancelResult);
918       }
919     }
920 
921     ExecuteFinishedScripts();
922   }
923 
DeleteCache()924   void DeleteCache() {
925     AssertIsOnMainThread();
926 
927     if (!mCacheCreator) {
928       return;
929     }
930 
931     mCacheCreator->DeleteCache();
932     mCacheCreator = nullptr;
933   }
934 
RunInternal()935   nsresult RunInternal() {
936     AssertIsOnMainThread();
937 
938     if (IsMainWorkerScript()) {
939       mWorkerPrivate->SetLoadingWorkerScript(true);
940     }
941 
942     // Convert the origin stack to JSON (which must be done on the main
943     // thread) explicitly, so that we can use the stack to notify the net
944     // monitor about every script we load.
945     if (mOriginStack) {
946       ConvertSerializedStackToJSON(std::move(mOriginStack), mOriginStackJSON);
947     }
948 
949     if (!mWorkerPrivate->IsServiceWorker() || IsDebuggerScript()) {
950       for (ScriptLoadInfo& loadInfo : mLoadInfos) {
951         nsresult rv = LoadScript(loadInfo);
952         if (NS_WARN_IF(NS_FAILED(rv))) {
953           LoadingFinished(loadInfo, rv);
954           return rv;
955         }
956       }
957 
958       return NS_OK;
959     }
960 
961     MOZ_ASSERT(!mCacheCreator);
962     mCacheCreator = new CacheCreator(mWorkerPrivate);
963 
964     for (ScriptLoadInfo& loadInfo : mLoadInfos) {
965       mCacheCreator->AddLoader(MakeNotNull<RefPtr<CacheScriptLoader>>(
966           mWorkerPrivate, loadInfo, IsMainWorkerScript(), this));
967     }
968 
969     // The worker may have a null principal on first load, but in that case its
970     // parent definitely will have one.
971     nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
972     if (!principal) {
973       WorkerPrivate* parentWorker = mWorkerPrivate->GetParent();
974       MOZ_ASSERT(parentWorker, "Must have a parent!");
975       principal = parentWorker->GetPrincipal();
976     }
977 
978     nsresult rv = mCacheCreator->Load(principal);
979     if (NS_WARN_IF(NS_FAILED(rv))) {
980       return rv;
981     }
982 
983     return NS_OK;
984   }
985 
LoadScript(ScriptLoadInfo & aLoadInfo)986   nsresult LoadScript(ScriptLoadInfo& aLoadInfo) {
987     AssertIsOnMainThread();
988     MOZ_ASSERT_IF(IsMainWorkerScript(), mWorkerScriptType != DebuggerScript);
989 
990     WorkerPrivate* parentWorker = mWorkerPrivate->GetParent();
991 
992     // For JavaScript debugging, the devtools server must run on the same
993     // thread as the debuggee, indicating the worker uses content principal.
994     // However, in Bug 863246, web content will no longer be able to load
995     // resource:// URIs by default, so we need system principal to load
996     // debugger scripts.
997     nsIPrincipal* principal = (mWorkerScriptType == DebuggerScript)
998                                   ? nsContentUtils::GetSystemPrincipal()
999                                   : mWorkerPrivate->GetPrincipal();
1000 
1001     nsCOMPtr<nsILoadGroup> loadGroup = mWorkerPrivate->GetLoadGroup();
1002     MOZ_DIAGNOSTIC_ASSERT(principal);
1003 
1004     NS_ENSURE_TRUE(NS_LoadGroupMatchesPrincipal(loadGroup, principal),
1005                    NS_ERROR_FAILURE);
1006 
1007     // Figure out our base URI.
1008     nsCOMPtr<nsIURI> baseURI = GetBaseURI(mIsMainScript, mWorkerPrivate);
1009 
1010     // May be null.
1011     nsCOMPtr<Document> parentDoc = mWorkerPrivate->GetDocument();
1012 
1013     nsCOMPtr<nsIChannel> channel;
1014     if (IsMainWorkerScript()) {
1015       // May be null.
1016       channel = mWorkerPrivate->ForgetWorkerChannel();
1017     }
1018 
1019     nsCOMPtr<nsIIOService> ios(do_GetIOService());
1020 
1021     nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
1022     NS_ASSERTION(secMan, "This should never be null!");
1023 
1024     nsresult& rv = aLoadInfo.mLoadResult;
1025 
1026     nsLoadFlags loadFlags = aLoadInfo.mLoadFlags;
1027 
1028     // Get the top-level worker.
1029     WorkerPrivate* topWorkerPrivate = mWorkerPrivate;
1030     WorkerPrivate* parent = topWorkerPrivate->GetParent();
1031     while (parent) {
1032       topWorkerPrivate = parent;
1033       parent = topWorkerPrivate->GetParent();
1034     }
1035 
1036     // If the top-level worker is a dedicated worker and has a window, and the
1037     // window has a docshell, the caching behavior of this worker should match
1038     // that of that docshell.
1039     if (topWorkerPrivate->IsDedicatedWorker()) {
1040       nsCOMPtr<nsPIDOMWindowInner> window = topWorkerPrivate->GetWindow();
1041       if (window) {
1042         nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
1043         if (docShell) {
1044           nsresult rv = docShell->GetDefaultLoadFlags(&loadFlags);
1045           NS_ENSURE_SUCCESS(rv, rv);
1046         }
1047       }
1048     }
1049 
1050     if (!channel) {
1051       // Only top level workers' main script use the document charset for the
1052       // script uri encoding. Otherwise, default encoding (UTF-8) is applied.
1053       bool useDefaultEncoding = !(!parentWorker && IsMainWorkerScript());
1054       nsCOMPtr<nsIURI> url;
1055       rv = ConstructURI(aLoadInfo.mURL, baseURI, parentDoc, useDefaultEncoding,
1056                         getter_AddRefs(url));
1057       if (NS_FAILED(rv)) {
1058         return rv;
1059       }
1060 
1061       nsCOMPtr<nsIReferrerInfo> referrerInfo =
1062           ReferrerInfo::CreateForFetch(principal, nullptr);
1063       if (parentWorker && !IsMainWorkerScript()) {
1064         referrerInfo =
1065             static_cast<ReferrerInfo*>(referrerInfo.get())
1066                 ->CloneWithNewPolicy(parentWorker->GetReferrerPolicy());
1067       }
1068 
1069       rv = ChannelFromScriptURL(principal, parentDoc, mWorkerPrivate, loadGroup,
1070                                 ios, secMan, url, mClientInfo, mController,
1071                                 IsMainWorkerScript(), mWorkerScriptType,
1072                                 mWorkerPrivate->ContentPolicyType(), loadFlags,
1073                                 mWorkerPrivate->CookieJarSettings(),
1074                                 referrerInfo, getter_AddRefs(channel));
1075       if (NS_WARN_IF(NS_FAILED(rv))) {
1076         return rv;
1077       }
1078     }
1079 
1080     // Associate any originating stack with the channel.
1081     if (!mOriginStackJSON.IsEmpty()) {
1082       NotifyNetworkMonitorAlternateStack(channel, mOriginStackJSON);
1083     }
1084 
1085     // We need to know which index we're on in OnStreamComplete so we know
1086     // where to put the result.
1087     RefPtr<LoaderListener> listener = new LoaderListener(this, aLoadInfo);
1088 
1089     RefPtr<ScriptResponseHeaderProcessor> headerProcessor = nullptr;
1090 
1091     // For each debugger script, a non-debugger script load of the same script
1092     // should have occured prior that processed the headers.
1093     if (!IsDebuggerScript()) {
1094       headerProcessor = MakeRefPtr<ScriptResponseHeaderProcessor>(
1095           mWorkerPrivate, mIsMainScript);
1096     }
1097 
1098     nsCOMPtr<nsIStreamLoader> loader;
1099     rv = NS_NewStreamLoader(getter_AddRefs(loader), listener, headerProcessor);
1100     if (NS_WARN_IF(NS_FAILED(rv))) {
1101       return rv;
1102     }
1103 
1104     if (IsMainWorkerScript()) {
1105       MOZ_DIAGNOSTIC_ASSERT(aLoadInfo.mReservedClientInfo.isSome());
1106       rv = AddClientChannelHelper(
1107           channel, std::move(aLoadInfo.mReservedClientInfo),
1108           Maybe<ClientInfo>(), mWorkerPrivate->HybridEventTarget());
1109       if (NS_WARN_IF(NS_FAILED(rv))) {
1110         return rv;
1111       }
1112     }
1113 
1114     if (StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
1115       nsILoadInfo::CrossOriginEmbedderPolicy respectedCOEP =
1116           mWorkerPrivate->GetEmbedderPolicy();
1117       if (mWorkerPrivate->IsDedicatedWorker() &&
1118           respectedCOEP == nsILoadInfo::EMBEDDER_POLICY_NULL) {
1119         respectedCOEP = mWorkerPrivate->GetOwnerEmbedderPolicy();
1120       }
1121 
1122       nsCOMPtr<nsILoadInfo> channelLoadInfo = channel->LoadInfo();
1123       channelLoadInfo->SetLoadingEmbedderPolicy(respectedCOEP);
1124     }
1125 
1126     if (aLoadInfo.mCacheStatus != ScriptLoadInfo::ToBeCached) {
1127       rv = channel->AsyncOpen(loader);
1128       if (NS_WARN_IF(NS_FAILED(rv))) {
1129         return rv;
1130       }
1131     } else {
1132       nsCOMPtr<nsIOutputStream> writer;
1133 
1134       // In case we return early.
1135       aLoadInfo.mCacheStatus = ScriptLoadInfo::Cancel;
1136 
1137       rv = NS_NewPipe(
1138           getter_AddRefs(aLoadInfo.mCacheReadStream), getter_AddRefs(writer), 0,
1139           UINT32_MAX,    // unlimited size to avoid writer WOULD_BLOCK case
1140           true, false);  // non-blocking reader, blocking writer
1141       if (NS_WARN_IF(NS_FAILED(rv))) {
1142         return rv;
1143       }
1144 
1145       nsCOMPtr<nsIStreamListenerTee> tee =
1146           do_CreateInstance(NS_STREAMLISTENERTEE_CONTRACTID);
1147       rv = tee->Init(loader, writer, listener);
1148       if (NS_WARN_IF(NS_FAILED(rv))) {
1149         return rv;
1150       }
1151 
1152       nsresult rv = channel->AsyncOpen(tee);
1153       if (NS_WARN_IF(NS_FAILED(rv))) {
1154         return rv;
1155       }
1156     }
1157 
1158     aLoadInfo.mChannel.swap(channel);
1159 
1160     return NS_OK;
1161   }
1162 
OnStreamCompleteInternal(nsIStreamLoader * aLoader,nsresult aStatus,uint32_t aStringLen,const uint8_t * aString,ScriptLoadInfo & aLoadInfo)1163   nsresult OnStreamCompleteInternal(nsIStreamLoader* aLoader, nsresult aStatus,
1164                                     uint32_t aStringLen, const uint8_t* aString,
1165                                     ScriptLoadInfo& aLoadInfo) {
1166     AssertIsOnMainThread();
1167 
1168     if (!aLoadInfo.mChannel) {
1169       return NS_BINDING_ABORTED;
1170     }
1171 
1172     aLoadInfo.mChannel = nullptr;
1173 
1174     if (NS_FAILED(aStatus)) {
1175       return aStatus;
1176     }
1177 
1178     NS_ASSERTION(aString, "This should never be null!");
1179 
1180     nsCOMPtr<nsIRequest> request;
1181     nsresult rv = aLoader->GetRequest(getter_AddRefs(request));
1182     NS_ENSURE_SUCCESS(rv, rv);
1183 
1184     nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
1185     MOZ_ASSERT(channel);
1186 
1187     nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
1188     NS_ASSERTION(ssm, "Should never be null!");
1189 
1190     nsCOMPtr<nsIPrincipal> channelPrincipal;
1191     rv = ssm->GetChannelResultPrincipal(channel,
1192                                         getter_AddRefs(channelPrincipal));
1193     if (NS_WARN_IF(NS_FAILED(rv))) {
1194       return rv;
1195     }
1196 
1197     nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
1198     if (!principal) {
1199       WorkerPrivate* parentWorker = mWorkerPrivate->GetParent();
1200       MOZ_ASSERT(parentWorker, "Must have a parent!");
1201       principal = parentWorker->GetPrincipal();
1202     }
1203 
1204 #ifdef DEBUG
1205     if (IsMainWorkerScript()) {
1206       nsCOMPtr<nsIPrincipal> loadingPrincipal =
1207           mWorkerPrivate->GetLoadingPrincipal();
1208       // if we are not in a ServiceWorker, and the principal is not null, then
1209       // the loading principal must subsume the worker principal if it is not a
1210       // nullPrincipal (sandbox).
1211       MOZ_ASSERT(!loadingPrincipal || loadingPrincipal->GetIsNullPrincipal() ||
1212                  principal->GetIsNullPrincipal() ||
1213                  loadingPrincipal->Subsumes(principal));
1214     }
1215 #endif
1216 
1217     // We don't mute the main worker script becase we've already done
1218     // same-origin checks on them so we should be able to see their errors.
1219     // Note that for data: url, where we allow it through the same-origin check
1220     // but then give it a different origin.
1221     aLoadInfo.mMutedErrorFlag.emplace(!IsMainWorkerScript() &&
1222                                       !principal->Subsumes(channelPrincipal));
1223 
1224     // Make sure we're not seeing the result of a 404 or something by checking
1225     // the 'requestSucceeded' attribute on the http channel.
1226     nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request);
1227     nsAutoCString tCspHeaderValue, tCspROHeaderValue, tRPHeaderCValue;
1228 
1229     if (httpChannel) {
1230       bool requestSucceeded;
1231       rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
1232       NS_ENSURE_SUCCESS(rv, rv);
1233 
1234       if (!requestSucceeded) {
1235         return NS_ERROR_NOT_AVAILABLE;
1236       }
1237 
1238       Unused << httpChannel->GetResponseHeader("content-security-policy"_ns,
1239                                                tCspHeaderValue);
1240 
1241       Unused << httpChannel->GetResponseHeader(
1242           "content-security-policy-report-only"_ns, tCspROHeaderValue);
1243 
1244       Unused << httpChannel->GetResponseHeader("referrer-policy"_ns,
1245                                                tRPHeaderCValue);
1246 
1247       nsAutoCString sourceMapURL;
1248       if (nsContentUtils::GetSourceMapURL(httpChannel, sourceMapURL)) {
1249         aLoadInfo.mSourceMapURL = Some(NS_ConvertUTF8toUTF16(sourceMapURL));
1250       }
1251     }
1252 
1253     // May be null.
1254     Document* parentDoc = mWorkerPrivate->GetDocument();
1255 
1256     // Use the regular ScriptLoader for this grunt work! Should be just fine
1257     // because we're running on the main thread.
1258     // Worker scripts are always decoded as UTF-8 per spec. Passing null for a
1259     // channel and UTF-8 for the hint will always interpret |aString| as UTF-8.
1260     if (StaticPrefs::dom_worker_script_loader_utf8_parsing_enabled()) {
1261       aLoadInfo.InitUTF8Script();
1262       rv = ScriptLoader::ConvertToUTF8(
1263           nullptr, aString, aStringLen, u"UTF-8"_ns, parentDoc,
1264           aLoadInfo.mScript.mUTF8, aLoadInfo.mScriptLength);
1265     } else {
1266       aLoadInfo.InitUTF16Script();
1267       rv = ScriptLoader::ConvertToUTF16(
1268           nullptr, aString, aStringLen, u"UTF-8"_ns, parentDoc,
1269           aLoadInfo.mScript.mUTF16, aLoadInfo.mScriptLength);
1270     }
1271     if (NS_FAILED(rv)) {
1272       return rv;
1273     }
1274 
1275     if (aLoadInfo.ScriptTextIsNull()) {
1276       if (aLoadInfo.mScriptLength != 0) {
1277         return NS_ERROR_FAILURE;
1278       }
1279 
1280       nsContentUtils::ReportToConsole(
1281           nsIScriptError::warningFlag, "DOM"_ns, parentDoc,
1282           nsContentUtils::eDOM_PROPERTIES, "EmptyWorkerSourceWarning");
1283     }
1284 
1285     // Figure out what we actually loaded.
1286     nsCOMPtr<nsIURI> finalURI;
1287     rv = NS_GetFinalChannelURI(channel, getter_AddRefs(finalURI));
1288     NS_ENSURE_SUCCESS(rv, rv);
1289 
1290     bool isSameOrigin = false;
1291     rv = principal->IsSameOrigin(finalURI, false, &isSameOrigin);
1292     NS_ENSURE_SUCCESS(rv, rv);
1293 
1294     if (isSameOrigin) {
1295       nsCString filename;
1296       rv = finalURI->GetSpec(filename);
1297       NS_ENSURE_SUCCESS(rv, rv);
1298 
1299       if (!filename.IsEmpty()) {
1300         // This will help callers figure out what their script url resolved to
1301         // in case of errors.
1302         aLoadInfo.mURL.Assign(NS_ConvertUTF8toUTF16(filename));
1303       }
1304     }
1305 
1306     // Update the principal of the worker and its base URI if we just loaded the
1307     // worker's primary script.
1308     if (IsMainWorkerScript()) {
1309       // Take care of the base URI first.
1310       mWorkerPrivate->SetBaseURI(finalURI);
1311 
1312       // Store the channel info if needed.
1313       mWorkerPrivate->InitChannelInfo(channel);
1314 
1315       // Our final channel principal should match the loading principal
1316       // in terms of the origin.  This used to be an assert, but it seems
1317       // there are some rare cases where this check can fail in practice.
1318       // Perhaps some browser script setting nsIChannel.owner, etc.
1319       NS_ENSURE_TRUE(mWorkerPrivate->FinalChannelPrincipalIsValid(channel),
1320                      NS_ERROR_FAILURE);
1321 
1322       // However, we must still override the principal since the nsIPrincipal
1323       // URL may be different due to same-origin redirects.  Unfortunately this
1324       // URL must exactly match the final worker script URL in order to
1325       // properly set the referrer header on fetch/xhr requests.  If bug 1340694
1326       // is ever fixed this can be removed.
1327       rv = mWorkerPrivate->SetPrincipalsAndCSPFromChannel(channel);
1328       NS_ENSURE_SUCCESS(rv, rv);
1329 
1330       nsCOMPtr<nsIContentSecurityPolicy> csp = mWorkerPrivate->GetCSP();
1331       // We did inherit CSP in bug 1223647. If we do not already have a CSP, we
1332       // should get it from the HTTP headers on the worker script.
1333       if (StaticPrefs::security_csp_enable()) {
1334         if (!csp) {
1335           rv = mWorkerPrivate->SetCSPFromHeaderValues(tCspHeaderValue,
1336                                                       tCspROHeaderValue);
1337           NS_ENSURE_SUCCESS(rv, rv);
1338         } else {
1339           csp->EnsureEventTarget(mWorkerPrivate->MainThreadEventTarget());
1340         }
1341       }
1342 
1343       mWorkerPrivate->UpdateReferrerInfoFromHeader(tRPHeaderCValue);
1344 
1345       WorkerPrivate* parent = mWorkerPrivate->GetParent();
1346       if (parent) {
1347         // XHR Params Allowed
1348         mWorkerPrivate->SetXHRParamsAllowed(parent->XHRParamsAllowed());
1349       }
1350 
1351       nsCOMPtr<nsILoadInfo> chanLoadInfo = channel->LoadInfo();
1352       if (chanLoadInfo) {
1353         mController = chanLoadInfo->GetController();
1354       }
1355 
1356       // If we are loading a blob URL we must inherit the controller
1357       // from the parent.  This is a bit odd as the blob URL may have
1358       // been created in a different context with a different controller.
1359       // For now, though, this is what the spec says.  See:
1360       //
1361       // https://github.com/w3c/ServiceWorker/issues/1261
1362       //
1363       if (IsBlobURI(mWorkerPrivate->GetBaseURI())) {
1364         MOZ_DIAGNOSTIC_ASSERT(mController.isNothing());
1365         mController = mWorkerPrivate->GetParentController();
1366       }
1367     }
1368 
1369     return NS_OK;
1370   }
1371 
DataReceivedFromCache(ScriptLoadInfo & aLoadInfo,const uint8_t * aString,uint32_t aStringLen,const mozilla::dom::ChannelInfo & aChannelInfo,UniquePtr<PrincipalInfo> aPrincipalInfo,const nsACString & aCSPHeaderValue,const nsACString & aCSPReportOnlyHeaderValue,const nsACString & aReferrerPolicyHeaderValue)1372   void DataReceivedFromCache(ScriptLoadInfo& aLoadInfo, const uint8_t* aString,
1373                              uint32_t aStringLen,
1374                              const mozilla::dom::ChannelInfo& aChannelInfo,
1375                              UniquePtr<PrincipalInfo> aPrincipalInfo,
1376                              const nsACString& aCSPHeaderValue,
1377                              const nsACString& aCSPReportOnlyHeaderValue,
1378                              const nsACString& aReferrerPolicyHeaderValue) {
1379     AssertIsOnMainThread();
1380     MOZ_ASSERT(aLoadInfo.mCacheStatus == ScriptLoadInfo::Cached);
1381 
1382     auto responsePrincipalOrErr = PrincipalInfoToPrincipal(*aPrincipalInfo);
1383     MOZ_DIAGNOSTIC_ASSERT(responsePrincipalOrErr.isOk());
1384 
1385     nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
1386     if (!principal) {
1387       WorkerPrivate* parentWorker = mWorkerPrivate->GetParent();
1388       MOZ_ASSERT(parentWorker, "Must have a parent!");
1389       principal = parentWorker->GetPrincipal();
1390     }
1391 
1392     nsCOMPtr<nsIPrincipal> responsePrincipal = responsePrincipalOrErr.unwrap();
1393 
1394     aLoadInfo.mMutedErrorFlag.emplace(!principal->Subsumes(responsePrincipal));
1395 
1396     // May be null.
1397     Document* parentDoc = mWorkerPrivate->GetDocument();
1398 
1399     MOZ_ASSERT(aLoadInfo.ScriptTextIsNull());
1400 
1401     nsresult rv;
1402     if (StaticPrefs::dom_worker_script_loader_utf8_parsing_enabled()) {
1403       aLoadInfo.InitUTF8Script();
1404       rv = ScriptLoader::ConvertToUTF8(
1405           nullptr, aString, aStringLen, u"UTF-8"_ns, parentDoc,
1406           aLoadInfo.mScript.mUTF8, aLoadInfo.mScriptLength);
1407     } else {
1408       aLoadInfo.InitUTF16Script();
1409       rv = ScriptLoader::ConvertToUTF16(
1410           nullptr, aString, aStringLen, u"UTF-8"_ns, parentDoc,
1411           aLoadInfo.mScript.mUTF16, aLoadInfo.mScriptLength);
1412     }
1413     if (NS_SUCCEEDED(rv) && IsMainWorkerScript()) {
1414       nsCOMPtr<nsIURI> finalURI;
1415       rv = NS_NewURI(getter_AddRefs(finalURI), aLoadInfo.mFullURL);
1416       if (NS_SUCCEEDED(rv)) {
1417         mWorkerPrivate->SetBaseURI(finalURI);
1418       }
1419 
1420 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1421       nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
1422       MOZ_DIAGNOSTIC_ASSERT(principal);
1423 
1424       bool equal = false;
1425       MOZ_ALWAYS_SUCCEEDS(responsePrincipal->Equals(principal, &equal));
1426       MOZ_DIAGNOSTIC_ASSERT(equal);
1427 
1428       nsCOMPtr<nsIContentSecurityPolicy> csp;
1429       if (parentDoc) {
1430         csp = parentDoc->GetCsp();
1431       }
1432       MOZ_DIAGNOSTIC_ASSERT(!csp);
1433 #endif
1434 
1435       mWorkerPrivate->InitChannelInfo(aChannelInfo);
1436 
1437       nsILoadGroup* loadGroup = mWorkerPrivate->GetLoadGroup();
1438       MOZ_DIAGNOSTIC_ASSERT(loadGroup);
1439 
1440       // Override the principal on the WorkerPrivate.  This is only necessary
1441       // in order to get a principal with exactly the correct URL.  The fetch
1442       // referrer logic depends on the WorkerPrivate principal having a URL
1443       // that matches the worker script URL.  If bug 1340694 is ever fixed
1444       // this can be removed.
1445       // XXX: force the partitionedPrincipal to be equal to the response one.
1446       // This is OK for now because we don't want to expose partitionedPrincipal
1447       // functionality in ServiceWorkers yet.
1448       rv = mWorkerPrivate->SetPrincipalsAndCSPOnMainThread(
1449           responsePrincipal, responsePrincipal, loadGroup, nullptr);
1450       MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
1451 
1452       rv = mWorkerPrivate->SetCSPFromHeaderValues(aCSPHeaderValue,
1453                                                   aCSPReportOnlyHeaderValue);
1454       MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
1455 
1456       mWorkerPrivate->UpdateReferrerInfoFromHeader(aReferrerPolicyHeaderValue);
1457     }
1458 
1459     if (NS_SUCCEEDED(rv)) {
1460       DataReceived();
1461     }
1462 
1463     LoadingFinished(aLoadInfo, rv);
1464   }
1465 
DataReceived()1466   void DataReceived() {
1467     if (IsMainWorkerScript()) {
1468       WorkerPrivate* parent = mWorkerPrivate->GetParent();
1469 
1470       if (parent) {
1471         // XHR Params Allowed
1472         mWorkerPrivate->SetXHRParamsAllowed(parent->XHRParamsAllowed());
1473 
1474         // Set Eval and ContentSecurityPolicy
1475         mWorkerPrivate->SetCSP(parent->GetCSP());
1476         mWorkerPrivate->SetEvalAllowed(parent->IsEvalAllowed());
1477       }
1478     }
1479   }
1480 
ExecuteFinishedScripts()1481   void ExecuteFinishedScripts() {
1482     AssertIsOnMainThread();
1483 
1484     if (IsMainWorkerScript()) {
1485       mWorkerPrivate->WorkerScriptLoaded();
1486     }
1487 
1488     const auto begin = mLoadInfos.begin();
1489     const auto end = mLoadInfos.end();
1490     using Iterator = decltype(begin);
1491     const auto maybeRangeToExecute =
1492         [begin, end]() -> Maybe<std::pair<Iterator, Iterator>> {
1493       // firstItToExecute is the first loadInfo where mExecutionScheduled is
1494       // unset.
1495       auto firstItToExecute =
1496           std::find_if(begin, end, [](const ScriptLoadInfo& loadInfo) {
1497             return !loadInfo.mExecutionScheduled;
1498           });
1499 
1500       if (firstItToExecute == end) {
1501         return Nothing();
1502       }
1503 
1504       // firstItUnexecutable is the first loadInfo that is not yet finished.
1505       // Update mExecutionScheduled on the ones we're about to schedule for
1506       // execution.
1507       const auto firstItUnexecutable =
1508           std::find_if(firstItToExecute, end, [](ScriptLoadInfo& loadInfo) {
1509             if (!loadInfo.Finished()) {
1510               return true;
1511             }
1512 
1513             // We can execute this one.
1514             loadInfo.mExecutionScheduled = true;
1515 
1516             return false;
1517           });
1518 
1519       return firstItUnexecutable == firstItToExecute
1520                  ? Nothing()
1521                  : Some(std::pair(firstItToExecute, firstItUnexecutable));
1522     }();
1523 
1524     // If there are no unexecutable load infos, we can unuse things before the
1525     // execution of the scripts and the stopping of the sync loop.
1526     if (maybeRangeToExecute) {
1527       if (maybeRangeToExecute->second == end) {
1528         mCacheCreator = nullptr;
1529       }
1530 
1531       RefPtr<ScriptExecutorRunnable> runnable = new ScriptExecutorRunnable(
1532           *this, mSyncLoopTarget, IsMainWorkerScript(),
1533           Span{begin, maybeRangeToExecute->first},
1534           Span{maybeRangeToExecute->first, maybeRangeToExecute->second});
1535       if (!runnable->Dispatch()) {
1536         MOZ_ASSERT(false, "This should never fail!");
1537       }
1538     }
1539   }
1540 };
1541 
NS_IMPL_ISUPPORTS(ScriptLoaderRunnable,nsIRunnable,nsINamed)1542 NS_IMPL_ISUPPORTS(ScriptLoaderRunnable, nsIRunnable, nsINamed)
1543 
1544 NS_IMETHODIMP
1545 LoaderListener::OnStreamComplete(nsIStreamLoader* aLoader,
1546                                  nsISupports* aContext, nsresult aStatus,
1547                                  uint32_t aStringLen, const uint8_t* aString) {
1548   return mRunnable->OnStreamComplete(aLoader, mLoadInfo, aStatus, aStringLen,
1549                                      aString);
1550 }
1551 
1552 NS_IMETHODIMP
OnStartRequest(nsIRequest * aRequest)1553 LoaderListener::OnStartRequest(nsIRequest* aRequest) {
1554   return mRunnable->OnStartRequest(aRequest, mLoadInfo);
1555 }
1556 
ResolvedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue)1557 void CachePromiseHandler::ResolvedCallback(JSContext* aCx,
1558                                            JS::Handle<JS::Value> aValue) {
1559   AssertIsOnMainThread();
1560   // May already have been canceled by CacheScriptLoader::Fail from
1561   // CancelMainThread.
1562   MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::WritingToCache ||
1563              mLoadInfo.mCacheStatus == ScriptLoadInfo::Cancel);
1564   MOZ_ASSERT_IF(mLoadInfo.mCacheStatus == ScriptLoadInfo::Cancel,
1565                 !mLoadInfo.mCachePromise);
1566 
1567   if (mLoadInfo.mCachePromise) {
1568     mLoadInfo.mCacheStatus = ScriptLoadInfo::Cached;
1569     mLoadInfo.mCachePromise = nullptr;
1570     mRunnable->MaybeExecuteFinishedScripts(mLoadInfo);
1571   }
1572 }
1573 
RejectedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue)1574 void CachePromiseHandler::RejectedCallback(JSContext* aCx,
1575                                            JS::Handle<JS::Value> aValue) {
1576   AssertIsOnMainThread();
1577   // May already have been canceled by CacheScriptLoader::Fail from
1578   // CancelMainThread.
1579   MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::WritingToCache ||
1580              mLoadInfo.mCacheStatus == ScriptLoadInfo::Cancel);
1581   mLoadInfo.mCacheStatus = ScriptLoadInfo::Cancel;
1582 
1583   mLoadInfo.mCachePromise = nullptr;
1584 
1585   // This will delete the cache object and will call LoadingFinished() with an
1586   // error for each ongoing operation.
1587   mRunnable->DeleteCache();
1588 }
1589 
CreateCacheStorage(nsIPrincipal * aPrincipal)1590 nsresult CacheCreator::CreateCacheStorage(nsIPrincipal* aPrincipal) {
1591   AssertIsOnMainThread();
1592   MOZ_ASSERT(!mCacheStorage);
1593   MOZ_ASSERT(aPrincipal);
1594 
1595   nsIXPConnect* xpc = nsContentUtils::XPConnect();
1596   MOZ_ASSERT(xpc, "This should never be null!");
1597 
1598   AutoJSAPI jsapi;
1599   jsapi.Init();
1600   JSContext* cx = jsapi.cx();
1601   JS::Rooted<JSObject*> sandbox(cx);
1602   nsresult rv = xpc->CreateSandbox(cx, aPrincipal, sandbox.address());
1603   if (NS_WARN_IF(NS_FAILED(rv))) {
1604     return rv;
1605   }
1606 
1607   // The JSContext is not in a realm, so CreateSandbox returned an unwrapped
1608   // global.
1609   MOZ_ASSERT(JS_IsGlobalObject(sandbox));
1610 
1611   mSandboxGlobalObject = xpc::NativeGlobal(sandbox);
1612   if (NS_WARN_IF(!mSandboxGlobalObject)) {
1613     return NS_ERROR_FAILURE;
1614   }
1615 
1616   // If we're in private browsing mode, don't even try to create the
1617   // CacheStorage.  Instead, just fail immediately to terminate the
1618   // ServiceWorker load.
1619   if (NS_WARN_IF(mOriginAttributes.mPrivateBrowsingId > 0)) {
1620     return NS_ERROR_DOM_SECURITY_ERR;
1621   }
1622 
1623   // Create a CacheStorage bypassing its trusted origin checks.  The
1624   // ServiceWorker has already performed its own checks before getting
1625   // to this point.
1626   ErrorResult error;
1627   mCacheStorage = CacheStorage::CreateOnMainThread(
1628       mozilla::dom::cache::CHROME_ONLY_NAMESPACE, mSandboxGlobalObject,
1629       aPrincipal, true /* force trusted origin */, error);
1630   if (NS_WARN_IF(error.Failed())) {
1631     return error.StealNSResult();
1632   }
1633 
1634   return NS_OK;
1635 }
1636 
Load(nsIPrincipal * aPrincipal)1637 nsresult CacheCreator::Load(nsIPrincipal* aPrincipal) {
1638   AssertIsOnMainThread();
1639   MOZ_ASSERT(!mLoaders.IsEmpty());
1640 
1641   nsresult rv = CreateCacheStorage(aPrincipal);
1642   if (NS_WARN_IF(NS_FAILED(rv))) {
1643     return rv;
1644   }
1645 
1646   ErrorResult error;
1647   MOZ_ASSERT(!mCacheName.IsEmpty());
1648   RefPtr<Promise> promise = mCacheStorage->Open(mCacheName, error);
1649   if (NS_WARN_IF(error.Failed())) {
1650     return error.StealNSResult();
1651   }
1652 
1653   promise->AppendNativeHandler(this);
1654   return NS_OK;
1655 }
1656 
FailLoaders(nsresult aRv)1657 void CacheCreator::FailLoaders(nsresult aRv) {
1658   AssertIsOnMainThread();
1659 
1660   // Fail() can call LoadingFinished() which may call ExecuteFinishedScripts()
1661   // which sets mCacheCreator to null, so hold a ref.
1662   RefPtr<CacheCreator> kungfuDeathGrip = this;
1663 
1664   for (uint32_t i = 0, len = mLoaders.Length(); i < len; ++i) {
1665     mLoaders[i]->Fail(aRv);
1666   }
1667 
1668   mLoaders.Clear();
1669 }
1670 
RejectedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue)1671 void CacheCreator::RejectedCallback(JSContext* aCx,
1672                                     JS::Handle<JS::Value> aValue) {
1673   AssertIsOnMainThread();
1674   FailLoaders(NS_ERROR_FAILURE);
1675 }
1676 
ResolvedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue)1677 void CacheCreator::ResolvedCallback(JSContext* aCx,
1678                                     JS::Handle<JS::Value> aValue) {
1679   AssertIsOnMainThread();
1680 
1681   if (!aValue.isObject()) {
1682     FailLoaders(NS_ERROR_FAILURE);
1683     return;
1684   }
1685 
1686   JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
1687   Cache* cache = nullptr;
1688   nsresult rv = UNWRAP_OBJECT(Cache, &obj, cache);
1689   if (NS_WARN_IF(NS_FAILED(rv))) {
1690     FailLoaders(NS_ERROR_FAILURE);
1691     return;
1692   }
1693 
1694   mCache = cache;
1695   MOZ_DIAGNOSTIC_ASSERT(mCache);
1696 
1697   // If the worker is canceled, CancelMainThread() will have cleared the
1698   // loaders via DeleteCache().
1699   for (uint32_t i = 0, len = mLoaders.Length(); i < len; ++i) {
1700     mLoaders[i]->Load(cache);
1701   }
1702 }
1703 
DeleteCache()1704 void CacheCreator::DeleteCache() {
1705   AssertIsOnMainThread();
1706 
1707   // This is called when the load is canceled which can occur before
1708   // mCacheStorage is initialized.
1709   if (mCacheStorage) {
1710     // It's safe to do this while Cache::Match() and Cache::Put() calls are
1711     // running.
1712     RefPtr<Promise> promise = mCacheStorage->Delete(mCacheName, IgnoreErrors());
1713 
1714     // We don't care to know the result of the promise object.
1715   }
1716 
1717   // Always call this here to ensure the loaders array is cleared.
1718   FailLoaders(NS_ERROR_FAILURE);
1719 }
1720 
Fail(nsresult aRv)1721 void CacheScriptLoader::Fail(nsresult aRv) {
1722   AssertIsOnMainThread();
1723   MOZ_ASSERT(NS_FAILED(aRv));
1724 
1725   if (mFailed) {
1726     return;
1727   }
1728 
1729   mFailed = true;
1730 
1731   if (mPump) {
1732     MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::ReadingFromCache);
1733     mPump->Cancel(aRv);
1734     mPump = nullptr;
1735   }
1736 
1737   mLoadInfo.mCacheStatus = ScriptLoadInfo::Cancel;
1738 
1739   // Stop if the load was aborted on the main thread.
1740   // Can't use Finished() because mCachePromise may still be true.
1741   if (mLoadInfo.mLoadingFinished) {
1742     MOZ_ASSERT(!mLoadInfo.mChannel);
1743     MOZ_ASSERT_IF(mLoadInfo.mCachePromise,
1744                   mLoadInfo.mCacheStatus == ScriptLoadInfo::WritingToCache ||
1745                       mLoadInfo.mCacheStatus == ScriptLoadInfo::Cancel);
1746     return;
1747   }
1748 
1749   mRunnable->LoadingFinished(mLoadInfo, aRv);
1750 }
1751 
Load(Cache * aCache)1752 void CacheScriptLoader::Load(Cache* aCache) {
1753   AssertIsOnMainThread();
1754   MOZ_ASSERT(aCache);
1755 
1756   nsCOMPtr<nsIURI> uri;
1757   nsresult rv =
1758       NS_NewURI(getter_AddRefs(uri), mLoadInfo.mURL, nullptr, mBaseURI);
1759   if (NS_WARN_IF(NS_FAILED(rv))) {
1760     Fail(rv);
1761     return;
1762   }
1763 
1764   nsAutoCString spec;
1765   rv = uri->GetSpec(spec);
1766   if (NS_WARN_IF(NS_FAILED(rv))) {
1767     Fail(rv);
1768     return;
1769   }
1770 
1771   MOZ_ASSERT(mLoadInfo.mFullURL.IsEmpty());
1772   CopyUTF8toUTF16(spec, mLoadInfo.mFullURL);
1773 
1774   mozilla::dom::RequestOrUSVString request;
1775   request.SetAsUSVString().ShareOrDependUpon(mLoadInfo.mFullURL);
1776 
1777   mozilla::dom::CacheQueryOptions params;
1778 
1779   // This JSContext will not end up executing JS code because here there are
1780   // no ReadableStreams involved.
1781   AutoJSAPI jsapi;
1782   jsapi.Init();
1783 
1784   ErrorResult error;
1785   RefPtr<Promise> promise = aCache->Match(jsapi.cx(), request, params, error);
1786   if (NS_WARN_IF(error.Failed())) {
1787     Fail(error.StealNSResult());
1788     return;
1789   }
1790 
1791   promise->AppendNativeHandler(this);
1792 }
1793 
RejectedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue)1794 void CacheScriptLoader::RejectedCallback(JSContext* aCx,
1795                                          JS::Handle<JS::Value> aValue) {
1796   AssertIsOnMainThread();
1797   MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::Uncached);
1798   Fail(NS_ERROR_FAILURE);
1799 }
1800 
ResolvedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue)1801 void CacheScriptLoader::ResolvedCallback(JSContext* aCx,
1802                                          JS::Handle<JS::Value> aValue) {
1803   AssertIsOnMainThread();
1804   // If we have already called 'Fail', we should not proceed.
1805   if (mFailed) {
1806     return;
1807   }
1808 
1809   MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::Uncached);
1810 
1811   nsresult rv;
1812 
1813   // The ServiceWorkerScriptCache will store data for any scripts it
1814   // it knows about.  This is always at least the top level script.
1815   // Depending on if a previous version of the service worker has
1816   // been installed or not it may also know about importScripts().  We
1817   // must handle loading and offlining new importScripts() here, however.
1818   if (aValue.isUndefined()) {
1819     // If this is the main script or we're not loading a new service worker
1820     // then this is an error.  This can happen for internal reasons, like
1821     // storage was probably wiped without removing the service worker
1822     // registration.  It can also happen for exposed reasons like the
1823     // service worker script calling importScripts() after install.
1824     if (NS_WARN_IF(mIsWorkerScript ||
1825                    (mState != ServiceWorkerState::Parsed &&
1826                     mState != ServiceWorkerState::Installing))) {
1827       Fail(NS_ERROR_DOM_INVALID_STATE_ERR);
1828       return;
1829     }
1830 
1831     mLoadInfo.mCacheStatus = ScriptLoadInfo::ToBeCached;
1832     rv = mRunnable->LoadScript(mLoadInfo);
1833     if (NS_WARN_IF(NS_FAILED(rv))) {
1834       Fail(rv);
1835     }
1836     return;
1837   }
1838 
1839   MOZ_ASSERT(aValue.isObject());
1840 
1841   JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
1842   mozilla::dom::Response* response = nullptr;
1843   rv = UNWRAP_OBJECT(Response, &obj, response);
1844   if (NS_WARN_IF(NS_FAILED(rv))) {
1845     Fail(rv);
1846     return;
1847   }
1848 
1849   InternalHeaders* headers = response->GetInternalHeaders();
1850 
1851   headers->Get("content-security-policy"_ns, mCSPHeaderValue, IgnoreErrors());
1852   headers->Get("content-security-policy-report-only"_ns,
1853                mCSPReportOnlyHeaderValue, IgnoreErrors());
1854   headers->Get("referrer-policy"_ns, mReferrerPolicyHeaderValue,
1855                IgnoreErrors());
1856 
1857   nsAutoCString coepHeader;
1858   headers->Get("cross-origin-embedder-policy"_ns, coepHeader, IgnoreErrors());
1859 
1860   nsILoadInfo::CrossOriginEmbedderPolicy coep =
1861       NS_GetCrossOriginEmbedderPolicyFromHeader(coepHeader);
1862 
1863   rv = ScriptResponseHeaderProcessor::ProcessCrossOriginEmbedderPolicyHeader(
1864       mRunnable->mWorkerPrivate, coep, mRunnable->mIsMainScript);
1865 
1866   if (NS_WARN_IF(NS_FAILED(rv))) {
1867     Fail(rv);
1868     return;
1869   }
1870 
1871   nsCOMPtr<nsIInputStream> inputStream;
1872   response->GetBody(getter_AddRefs(inputStream));
1873   mChannelInfo = response->GetChannelInfo();
1874   const UniquePtr<PrincipalInfo>& pInfo = response->GetPrincipalInfo();
1875   if (pInfo) {
1876     mPrincipalInfo = mozilla::MakeUnique<PrincipalInfo>(*pInfo);
1877   }
1878 
1879   if (!inputStream) {
1880     mLoadInfo.mCacheStatus = ScriptLoadInfo::Cached;
1881     mRunnable->DataReceivedFromCache(
1882         mLoadInfo, (uint8_t*)"", 0, mChannelInfo, std::move(mPrincipalInfo),
1883         mCSPHeaderValue, mCSPReportOnlyHeaderValue, mReferrerPolicyHeaderValue);
1884     return;
1885   }
1886 
1887   MOZ_ASSERT(!mPump);
1888   rv = NS_NewInputStreamPump(getter_AddRefs(mPump), inputStream.forget(),
1889                              0,     /* default segsize */
1890                              0,     /* default segcount */
1891                              false, /* default closeWhenDone */
1892                              mMainThreadEventTarget);
1893   if (NS_WARN_IF(NS_FAILED(rv))) {
1894     Fail(rv);
1895     return;
1896   }
1897 
1898   nsCOMPtr<nsIStreamLoader> loader;
1899   rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
1900   if (NS_WARN_IF(NS_FAILED(rv))) {
1901     Fail(rv);
1902     return;
1903   }
1904 
1905   rv = mPump->AsyncRead(loader);
1906   if (NS_WARN_IF(NS_FAILED(rv))) {
1907     mPump = nullptr;
1908     Fail(rv);
1909     return;
1910   }
1911 
1912   nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(mPump);
1913   if (rr) {
1914     nsCOMPtr<nsIEventTarget> sts =
1915         do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
1916     rv = rr->RetargetDeliveryTo(sts);
1917     if (NS_FAILED(rv)) {
1918       NS_WARNING("Failed to dispatch the nsIInputStreamPump to a IO thread.");
1919     }
1920   }
1921 
1922   mLoadInfo.mCacheStatus = ScriptLoadInfo::ReadingFromCache;
1923 }
1924 
1925 NS_IMETHODIMP
OnStreamComplete(nsIStreamLoader * aLoader,nsISupports * aContext,nsresult aStatus,uint32_t aStringLen,const uint8_t * aString)1926 CacheScriptLoader::OnStreamComplete(nsIStreamLoader* aLoader,
1927                                     nsISupports* aContext, nsresult aStatus,
1928                                     uint32_t aStringLen,
1929                                     const uint8_t* aString) {
1930   AssertIsOnMainThread();
1931 
1932   mPump = nullptr;
1933 
1934   if (NS_FAILED(aStatus)) {
1935     MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::ReadingFromCache ||
1936                mLoadInfo.mCacheStatus == ScriptLoadInfo::Cancel);
1937     Fail(aStatus);
1938     return NS_OK;
1939   }
1940 
1941   MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::ReadingFromCache);
1942   mLoadInfo.mCacheStatus = ScriptLoadInfo::Cached;
1943 
1944   MOZ_ASSERT(mPrincipalInfo);
1945   mRunnable->DataReceivedFromCache(
1946       mLoadInfo, aString, aStringLen, mChannelInfo, std::move(mPrincipalInfo),
1947       mCSPHeaderValue, mCSPReportOnlyHeaderValue, mReferrerPolicyHeaderValue);
1948   return NS_OK;
1949 }
1950 
1951 class ChannelGetterRunnable final : public WorkerMainThreadRunnable {
1952   const nsAString& mScriptURL;
1953   const ClientInfo mClientInfo;
1954   WorkerLoadInfo& mLoadInfo;
1955   nsresult mResult;
1956 
1957  public:
ChannelGetterRunnable(WorkerPrivate * aParentWorker,const nsAString & aScriptURL,WorkerLoadInfo & aLoadInfo)1958   ChannelGetterRunnable(WorkerPrivate* aParentWorker,
1959                         const nsAString& aScriptURL, WorkerLoadInfo& aLoadInfo)
1960       : WorkerMainThreadRunnable(aParentWorker,
1961                                  "ScriptLoader :: ChannelGetter"_ns),
1962         mScriptURL(aScriptURL)
1963         // ClientInfo should always be present since this should not be called
1964         // if parent's status is greater than Running.
1965         ,
1966         mClientInfo(aParentWorker->GlobalScope()->GetClientInfo().ref()),
1967         mLoadInfo(aLoadInfo),
1968         mResult(NS_ERROR_FAILURE) {
1969     MOZ_ASSERT(aParentWorker);
1970     aParentWorker->AssertIsOnWorkerThread();
1971   }
1972 
MainThreadRun()1973   virtual bool MainThreadRun() override {
1974     AssertIsOnMainThread();
1975 
1976     // Initialize the WorkerLoadInfo principal to our triggering principal
1977     // before doing anything else.  Normally we do this in the WorkerPrivate
1978     // Constructor, but we can't do so off the main thread when creating
1979     // a nested worker.  So do it here instead.
1980     mLoadInfo.mLoadingPrincipal = mWorkerPrivate->GetPrincipal();
1981     MOZ_DIAGNOSTIC_ASSERT(mLoadInfo.mLoadingPrincipal);
1982 
1983     mLoadInfo.mPrincipal = mLoadInfo.mLoadingPrincipal;
1984 
1985     // Figure out our base URI.
1986     nsCOMPtr<nsIURI> baseURI = mWorkerPrivate->GetBaseURI();
1987     MOZ_ASSERT(baseURI);
1988 
1989     // May be null.
1990     nsCOMPtr<Document> parentDoc = mWorkerPrivate->GetDocument();
1991 
1992     mLoadInfo.mLoadGroup = mWorkerPrivate->GetLoadGroup();
1993     mLoadInfo.mCookieJarSettings = mWorkerPrivate->CookieJarSettings();
1994 
1995     // Nested workers use default uri encoding.
1996     nsCOMPtr<nsIURI> url;
1997     mResult =
1998         ConstructURI(mScriptURL, baseURI, parentDoc, true, getter_AddRefs(url));
1999     NS_ENSURE_SUCCESS(mResult, true);
2000 
2001     Maybe<ClientInfo> clientInfo;
2002     clientInfo.emplace(mClientInfo);
2003 
2004     nsCOMPtr<nsIChannel> channel;
2005     nsCOMPtr<nsIReferrerInfo> referrerInfo =
2006         ReferrerInfo::CreateForFetch(mLoadInfo.mLoadingPrincipal, nullptr);
2007     mLoadInfo.mReferrerInfo =
2008         static_cast<ReferrerInfo*>(referrerInfo.get())
2009             ->CloneWithNewPolicy(mWorkerPrivate->GetReferrerPolicy());
2010 
2011     mResult = workerinternals::ChannelFromScriptURLMainThread(
2012         mLoadInfo.mLoadingPrincipal, parentDoc, mLoadInfo.mLoadGroup, url,
2013         clientInfo,
2014         // Nested workers are always dedicated.
2015         nsIContentPolicy::TYPE_INTERNAL_WORKER, mLoadInfo.mCookieJarSettings,
2016         mLoadInfo.mReferrerInfo, getter_AddRefs(channel));
2017     NS_ENSURE_SUCCESS(mResult, true);
2018 
2019     mResult = mLoadInfo.SetPrincipalsAndCSPFromChannel(channel);
2020     NS_ENSURE_SUCCESS(mResult, true);
2021 
2022     mLoadInfo.mChannel = std::move(channel);
2023     return true;
2024   }
2025 
GetResult() const2026   nsresult GetResult() const { return mResult; }
2027 
2028  private:
2029   virtual ~ChannelGetterRunnable() = default;
2030 };
2031 
ScriptExecutorRunnable(ScriptLoaderRunnable & aScriptLoader,nsIEventTarget * aSyncLoopTarget,bool aIsWorkerScript,Span<ScriptLoadInfo> aLoadInfosAlreadyExecuted,Span<ScriptLoadInfo> aLoadInfosToExecute)2032 ScriptExecutorRunnable::ScriptExecutorRunnable(
2033     ScriptLoaderRunnable& aScriptLoader, nsIEventTarget* aSyncLoopTarget,
2034     bool aIsWorkerScript, Span<ScriptLoadInfo> aLoadInfosAlreadyExecuted,
2035     Span<ScriptLoadInfo> aLoadInfosToExecute)
2036     : MainThreadWorkerSyncRunnable(aScriptLoader.mWorkerPrivate,
2037                                    aSyncLoopTarget),
2038       mScriptLoader(aScriptLoader),
2039       mIsWorkerScript(aIsWorkerScript),
2040       mLoadInfosAlreadyExecuted(aLoadInfosAlreadyExecuted),
2041       mLoadInfosToExecute(aLoadInfosToExecute) {
2042   // If there are load infos for scripts that have already been executed, the
2043   // load infos for the scripts to execute must immediate follow them.
2044   MOZ_ASSERT_IF(mLoadInfosAlreadyExecuted.Length(),
2045                 mLoadInfosAlreadyExecuted.Elements() +
2046                         mLoadInfosAlreadyExecuted.Length() ==
2047                     mLoadInfosToExecute.Elements());
2048 }
2049 
IsDebuggerRunnable() const2050 bool ScriptExecutorRunnable::IsDebuggerRunnable() const {
2051   // ScriptExecutorRunnable is used to execute both worker and debugger scripts.
2052   // In the latter case, the runnable needs to be dispatched to the debugger
2053   // queue.
2054   return mScriptLoader.mWorkerScriptType == DebuggerScript;
2055 }
2056 
PreRun(WorkerPrivate * aWorkerPrivate)2057 bool ScriptExecutorRunnable::PreRun(WorkerPrivate* aWorkerPrivate) {
2058   aWorkerPrivate->AssertIsOnWorkerThread();
2059 
2060   if (!mIsWorkerScript) {
2061     return true;
2062   }
2063 
2064   if (!aWorkerPrivate->GetJSContext()) {
2065     return false;
2066   }
2067 
2068   MOZ_ASSERT(mLoadInfosAlreadyExecuted.Length() == 0);
2069   MOZ_ASSERT(!mScriptLoader.mRv.Failed());
2070 
2071   // Move the CSP from the workerLoadInfo in the corresponding Client
2072   // where the CSP code expects it!
2073   aWorkerPrivate->StoreCSPOnClient();
2074 
2075   return true;
2076 }
2077 
2078 template <typename Unit>
EvaluateScriptData(JSContext * aCx,const JS::CompileOptions & aOptions,Unit * & aScriptData,size_t aScriptLength)2079 static bool EvaluateScriptData(JSContext* aCx,
2080                                const JS::CompileOptions& aOptions,
2081                                Unit*& aScriptData, size_t aScriptLength) {
2082   static_assert(std::is_same<Unit, char16_t>::value ||
2083                     std::is_same<Unit, Utf8Unit>::value,
2084                 "inferred units must be UTF-8 or UTF-16");
2085 
2086   // Transfer script data to a local variable.
2087   Unit* script = nullptr;
2088   std::swap(script, aScriptData);
2089 
2090   // Transfer the local to appropriate |SourceText|.
2091   JS::SourceText<Unit> srcBuf;
2092   if (!srcBuf.init(aCx, script, aScriptLength,
2093                    JS::SourceOwnership::TakeOwnership)) {
2094     return false;
2095   }
2096 
2097   JS::Rooted<JS::Value> unused(aCx);
2098   return Evaluate(aCx, aOptions, srcBuf, &unused);
2099 }
2100 
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)2101 bool ScriptExecutorRunnable::WorkerRun(JSContext* aCx,
2102                                        WorkerPrivate* aWorkerPrivate) {
2103   aWorkerPrivate->AssertIsOnWorkerThread();
2104 
2105   // Don't run if something else has already failed.
2106   if (std::any_of(
2107           mLoadInfosAlreadyExecuted.cbegin(), mLoadInfosAlreadyExecuted.cend(),
2108           [](const ScriptLoadInfo& loadInfo) {
2109             NS_ASSERTION(!loadInfo.mChannel,
2110                          "Should no longer have a channel!");
2111             NS_ASSERTION(loadInfo.mExecutionScheduled, "Should be scheduled!");
2112 
2113             return !loadInfo.mExecutionResult;
2114           })) {
2115     return true;
2116   }
2117 
2118   // If nothing else has failed, our ErrorResult better not be a failure either.
2119   MOZ_ASSERT(!mScriptLoader.mRv.Failed(), "Who failed it and why?");
2120 
2121   // Slightly icky action at a distance, but there's no better place to stash
2122   // this value, really.
2123   JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
2124   MOZ_ASSERT(global);
2125 
2126   for (ScriptLoadInfo& loadInfo : mLoadInfosToExecute) {
2127     NS_ASSERTION(!loadInfo.mChannel, "Should no longer have a channel!");
2128     NS_ASSERTION(loadInfo.mExecutionScheduled, "Should be scheduled!");
2129     NS_ASSERTION(!loadInfo.mExecutionResult, "Should not have executed yet!");
2130 
2131     MOZ_ASSERT(!mScriptLoader.mRv.Failed(), "Who failed it and why?");
2132     mScriptLoader.mRv.MightThrowJSException();
2133     if (NS_FAILED(loadInfo.mLoadResult)) {
2134       workerinternals::ReportLoadError(mScriptLoader.mRv, loadInfo.mLoadResult,
2135                                        loadInfo.mURL);
2136       return true;
2137     }
2138 
2139     // If this is a top level script that succeeded, then mark the
2140     // Client execution ready and possible controlled by a service worker.
2141     if (mIsWorkerScript) {
2142       if (mScriptLoader.mController.isSome()) {
2143         MOZ_ASSERT(mScriptLoader.mWorkerScriptType == WorkerScript,
2144                    "Debugger clients can't be controlled.");
2145         aWorkerPrivate->GlobalScope()->Control(mScriptLoader.mController.ref());
2146       }
2147       aWorkerPrivate->ExecutionReady();
2148     }
2149 
2150     NS_ConvertUTF16toUTF8 filename(loadInfo.mURL);
2151 
2152     JS::CompileOptions options(aCx);
2153     options.setFileAndLine(filename.get(), 1).setNoScriptRval(true);
2154 
2155     MOZ_ASSERT(loadInfo.mMutedErrorFlag.isSome());
2156     options.setMutedErrors(loadInfo.mMutedErrorFlag.valueOr(true));
2157 
2158     if (loadInfo.mSourceMapURL) {
2159       options.setSourceMapURL(loadInfo.mSourceMapURL->get());
2160     }
2161 
2162     // Our ErrorResult still shouldn't be a failure.
2163     MOZ_ASSERT(!mScriptLoader.mRv.Failed(), "Who failed it and why?");
2164 
2165     // Transfer script length to a local variable, encoding-agnostically.
2166     size_t scriptLength = 0;
2167     std::swap(scriptLength, loadInfo.mScriptLength);
2168 
2169     // This transfers script data out of the active arm of |loadInfo.mScript|.
2170     bool successfullyEvaluated =
2171         loadInfo.mScriptIsUTF8
2172             ? EvaluateScriptData(aCx, options, loadInfo.mScript.mUTF8,
2173                                  scriptLength)
2174             : EvaluateScriptData(aCx, options, loadInfo.mScript.mUTF16,
2175                                  scriptLength);
2176     MOZ_ASSERT(loadInfo.ScriptTextIsNull());
2177     if (!successfullyEvaluated) {
2178       mScriptLoader.mRv.StealExceptionFromJSContext(aCx);
2179       return true;
2180     }
2181 
2182     loadInfo.mExecutionResult = true;
2183   }
2184 
2185   return true;
2186 }
2187 
PostRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate,bool aRunResult)2188 void ScriptExecutorRunnable::PostRun(JSContext* aCx,
2189                                      WorkerPrivate* aWorkerPrivate,
2190                                      bool aRunResult) {
2191   aWorkerPrivate->AssertIsOnWorkerThread();
2192   MOZ_ASSERT(!JS_IsExceptionPending(aCx), "Who left an exception on there?");
2193 
2194   if (AllScriptsExecutable()) {
2195     // All done. If anything failed then return false.
2196     bool result = true;
2197     bool mutedError = false;
2198     for (const auto& loadInfo : mScriptLoader.mLoadInfos) {
2199       if (!loadInfo.mExecutionResult) {
2200         mutedError = loadInfo.mMutedErrorFlag.valueOr(true);
2201         result = false;
2202         break;
2203       }
2204     }
2205 
2206     // The only way we can get here with "result" false but without
2207     // mScriptLoader.mRv being a failure is if we're loading the main worker
2208     // script and GetOrCreateGlobalScope() fails.  In that case we would have
2209     // returned false from WorkerRun, so assert that.
2210     MOZ_ASSERT_IF(!result && !mScriptLoader.mRv.Failed(), !aRunResult);
2211     ShutdownScriptLoader(aCx, aWorkerPrivate, result, mutedError);
2212   }
2213 }
2214 
Cancel()2215 nsresult ScriptExecutorRunnable::Cancel() {
2216   if (AllScriptsExecutable()) {
2217     ShutdownScriptLoader(mWorkerPrivate->GetJSContext(), mWorkerPrivate, false,
2218                          false);
2219   }
2220   return MainThreadWorkerSyncRunnable::Cancel();
2221 }
2222 
ShutdownScriptLoader(JSContext * aCx,WorkerPrivate * aWorkerPrivate,bool aResult,bool aMutedError)2223 void ScriptExecutorRunnable::ShutdownScriptLoader(JSContext* aCx,
2224                                                   WorkerPrivate* aWorkerPrivate,
2225                                                   bool aResult,
2226                                                   bool aMutedError) {
2227   aWorkerPrivate->AssertIsOnWorkerThread();
2228 
2229   MOZ_ASSERT(AllScriptsExecutable());
2230 
2231   if (mIsWorkerScript) {
2232     aWorkerPrivate->SetLoadingWorkerScript(false);
2233   }
2234 
2235   if (!aResult) {
2236     // At this point there are two possibilities:
2237     //
2238     // 1) mScriptLoader.mRv.Failed().  In that case we just want to leave it
2239     //    as-is, except if it has a JS exception and we need to mute JS
2240     //    exceptions.  In that case, we log the exception without firing any
2241     //    events and then replace it on the ErrorResult with a NetworkError,
2242     //    per spec.
2243     //
2244     // 2) mScriptLoader.mRv succeeded.  As far as I can tell, this can only
2245     //    happen when loading the main worker script and
2246     //    GetOrCreateGlobalScope() fails or if ScriptExecutorRunnable::Cancel
2247     //    got called.  Does it matter what we throw in this case?  I'm not
2248     //    sure...
2249     if (mScriptLoader.mRv.Failed()) {
2250       if (aMutedError && mScriptLoader.mRv.IsJSException()) {
2251         LogExceptionToConsole(aCx, aWorkerPrivate);
2252         mScriptLoader.mRv.Throw(NS_ERROR_DOM_NETWORK_ERR);
2253       }
2254     } else {
2255       mScriptLoader.mRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
2256     }
2257   }
2258 
2259   aWorkerPrivate->StopSyncLoop(mSyncLoopTarget, aResult);
2260 }
2261 
LogExceptionToConsole(JSContext * aCx,WorkerPrivate * aWorkerPrivate)2262 void ScriptExecutorRunnable::LogExceptionToConsole(
2263     JSContext* aCx, WorkerPrivate* aWorkerPrivate) {
2264   aWorkerPrivate->AssertIsOnWorkerThread();
2265 
2266   MOZ_ASSERT(mScriptLoader.mRv.IsJSException());
2267 
2268   JS::Rooted<JS::Value> exn(aCx);
2269   if (!ToJSValue(aCx, std::move(mScriptLoader.mRv), &exn)) {
2270     return;
2271   }
2272 
2273   // Now the exception state should all be in exn.
2274   MOZ_ASSERT(!JS_IsExceptionPending(aCx));
2275   MOZ_ASSERT(!mScriptLoader.mRv.Failed());
2276 
2277   JS::ExceptionStack exnStack(aCx, exn, nullptr);
2278   JS::ErrorReportBuilder report(aCx);
2279   if (!report.init(aCx, exnStack, JS::ErrorReportBuilder::WithSideEffects)) {
2280     JS_ClearPendingException(aCx);
2281     return;
2282   }
2283 
2284   RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
2285   xpcReport->Init(report.report(), report.toStringResult().c_str(),
2286                   aWorkerPrivate->IsChromeWorker(), aWorkerPrivate->WindowID());
2287 
2288   RefPtr<AsyncErrorReporter> r = new AsyncErrorReporter(xpcReport);
2289   NS_DispatchToMainThread(r);
2290 }
2291 
AllScriptsExecutable() const2292 bool ScriptExecutorRunnable::AllScriptsExecutable() const {
2293   return mScriptLoader.mLoadInfos.Length() ==
2294          mLoadInfosAlreadyExecuted.Length() + mLoadInfosToExecute.Length();
2295 }
2296 
LoadAllScripts(WorkerPrivate * aWorkerPrivate,UniquePtr<SerializedStackHolder> aOriginStack,nsTArray<ScriptLoadInfo> aLoadInfos,bool aIsMainScript,WorkerScriptType aWorkerScriptType,ErrorResult & aRv)2297 void LoadAllScripts(WorkerPrivate* aWorkerPrivate,
2298                     UniquePtr<SerializedStackHolder> aOriginStack,
2299                     nsTArray<ScriptLoadInfo> aLoadInfos, bool aIsMainScript,
2300                     WorkerScriptType aWorkerScriptType, ErrorResult& aRv) {
2301   aWorkerPrivate->AssertIsOnWorkerThread();
2302   NS_ASSERTION(!aLoadInfos.IsEmpty(), "Bad arguments!");
2303 
2304   AutoSyncLoopHolder syncLoop(aWorkerPrivate, Canceling);
2305   nsCOMPtr<nsIEventTarget> syncLoopTarget = syncLoop.GetEventTarget();
2306   if (!syncLoopTarget) {
2307     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
2308     return;
2309   }
2310 
2311   Maybe<ClientInfo> clientInfo;
2312   Maybe<ServiceWorkerDescriptor> controller;
2313   if (!aIsMainScript) {
2314     nsIGlobalObject* global =
2315         aWorkerScriptType == WorkerScript
2316             ? static_cast<nsIGlobalObject*>(aWorkerPrivate->GlobalScope())
2317             : aWorkerPrivate->DebuggerGlobalScope();
2318 
2319     clientInfo = global->GetClientInfo();
2320     controller = global->GetController();
2321   }
2322 
2323   RefPtr<ScriptLoaderRunnable> loader = new ScriptLoaderRunnable(
2324       aWorkerPrivate, std::move(aOriginStack), syncLoopTarget,
2325       std::move(aLoadInfos), clientInfo, controller, aIsMainScript,
2326       aWorkerScriptType, aRv);
2327 
2328   NS_ASSERTION(aLoadInfos.IsEmpty(), "Should have swapped!");
2329 
2330   RefPtr<StrongWorkerRef> workerRef =
2331       StrongWorkerRef::Create(aWorkerPrivate, "ScriptLoader", [loader]() {
2332         NS_DispatchToMainThread(NewRunnableMethod(
2333             "ScriptLoader::CancelMainThreadWithBindingAborted", loader,
2334             &ScriptLoaderRunnable::CancelMainThreadWithBindingAborted));
2335       });
2336 
2337   if (NS_WARN_IF(!workerRef)) {
2338     aRv.Throw(NS_ERROR_FAILURE);
2339     return;
2340   }
2341 
2342   if (NS_FAILED(NS_DispatchToMainThread(loader))) {
2343     NS_ERROR("Failed to dispatch!");
2344     aRv.Throw(NS_ERROR_FAILURE);
2345     return;
2346   }
2347 
2348   syncLoop.Run();
2349 }
2350 
2351 } /* anonymous namespace */
2352 
2353 namespace workerinternals {
2354 
ChannelFromScriptURLMainThread(nsIPrincipal * aPrincipal,Document * aParentDoc,nsILoadGroup * aLoadGroup,nsIURI * aScriptURL,const Maybe<ClientInfo> & aClientInfo,nsContentPolicyType aMainScriptContentPolicyType,nsICookieJarSettings * aCookieJarSettings,nsIReferrerInfo * aReferrerInfo,nsIChannel ** aChannel)2355 nsresult ChannelFromScriptURLMainThread(
2356     nsIPrincipal* aPrincipal, Document* aParentDoc, nsILoadGroup* aLoadGroup,
2357     nsIURI* aScriptURL, const Maybe<ClientInfo>& aClientInfo,
2358     nsContentPolicyType aMainScriptContentPolicyType,
2359     nsICookieJarSettings* aCookieJarSettings, nsIReferrerInfo* aReferrerInfo,
2360     nsIChannel** aChannel) {
2361   AssertIsOnMainThread();
2362 
2363   nsCOMPtr<nsIIOService> ios(do_GetIOService());
2364 
2365   nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
2366   NS_ASSERTION(secMan, "This should never be null!");
2367 
2368   return ChannelFromScriptURL(
2369       aPrincipal, aParentDoc, nullptr, aLoadGroup, ios, secMan, aScriptURL,
2370       aClientInfo, Maybe<ServiceWorkerDescriptor>(), true, WorkerScript,
2371       aMainScriptContentPolicyType, nsIRequest::LOAD_NORMAL, aCookieJarSettings,
2372       aReferrerInfo, aChannel);
2373 }
2374 
ChannelFromScriptURLWorkerThread(JSContext * aCx,WorkerPrivate * aParent,const nsAString & aScriptURL,WorkerLoadInfo & aLoadInfo)2375 nsresult ChannelFromScriptURLWorkerThread(JSContext* aCx,
2376                                           WorkerPrivate* aParent,
2377                                           const nsAString& aScriptURL,
2378                                           WorkerLoadInfo& aLoadInfo) {
2379   aParent->AssertIsOnWorkerThread();
2380 
2381   RefPtr<ChannelGetterRunnable> getter =
2382       new ChannelGetterRunnable(aParent, aScriptURL, aLoadInfo);
2383 
2384   ErrorResult rv;
2385   getter->Dispatch(Canceling, rv);
2386   if (rv.Failed()) {
2387     NS_ERROR("Failed to dispatch!");
2388     return rv.StealNSResult();
2389   }
2390 
2391   return getter->GetResult();
2392 }
2393 
ReportLoadError(ErrorResult & aRv,nsresult aLoadResult,const nsAString & aScriptURL)2394 void ReportLoadError(ErrorResult& aRv, nsresult aLoadResult,
2395                      const nsAString& aScriptURL) {
2396   MOZ_ASSERT(!aRv.Failed());
2397 
2398   nsPrintfCString err("Failed to load worker script at \"%s\"",
2399                       NS_ConvertUTF16toUTF8(aScriptURL).get());
2400 
2401   switch (aLoadResult) {
2402     case NS_ERROR_FILE_NOT_FOUND:
2403     case NS_ERROR_NOT_AVAILABLE:
2404     case NS_ERROR_CORRUPTED_CONTENT:
2405       aRv.Throw(NS_ERROR_DOM_NETWORK_ERR);
2406       break;
2407 
2408     case NS_ERROR_MALFORMED_URI:
2409     case NS_ERROR_DOM_SYNTAX_ERR:
2410       aRv.ThrowSyntaxError(err);
2411       break;
2412 
2413     case NS_BINDING_ABORTED:
2414       // Note: we used to pretend like we didn't set an exception for
2415       // NS_BINDING_ABORTED, but then ShutdownScriptLoader did it anyway.  The
2416       // other callsite, in WorkerPrivate::Constructor, never passed in
2417       // NS_BINDING_ABORTED.  So just throw it directly here.  Consumers will
2418       // deal as needed.  But note that we do NOT want to use one of the
2419       // Throw*Error() methods on ErrorResult for this case, because that will
2420       // make it impossible for consumers to realize that our error was
2421       // NS_BINDING_ABORTED.
2422       aRv.Throw(aLoadResult);
2423       return;
2424 
2425     case NS_ERROR_DOM_BAD_URI:
2426       // This is actually a security error.
2427     case NS_ERROR_DOM_SECURITY_ERR:
2428       aRv.ThrowSecurityError(err);
2429       break;
2430 
2431     default:
2432       // For lack of anything better, go ahead and throw a NetworkError here.
2433       // We don't want to throw a JS exception, because for toplevel script
2434       // loads that would get squelched.
2435       aRv.ThrowNetworkError(nsPrintfCString(
2436           "Failed to load worker script at %s (nsresult = 0x%" PRIx32 ")",
2437           NS_ConvertUTF16toUTF8(aScriptURL).get(),
2438           static_cast<uint32_t>(aLoadResult)));
2439       return;
2440   }
2441 }
2442 
LoadMainScript(WorkerPrivate * aWorkerPrivate,UniquePtr<SerializedStackHolder> aOriginStack,const nsAString & aScriptURL,WorkerScriptType aWorkerScriptType,ErrorResult & aRv)2443 void LoadMainScript(WorkerPrivate* aWorkerPrivate,
2444                     UniquePtr<SerializedStackHolder> aOriginStack,
2445                     const nsAString& aScriptURL,
2446                     WorkerScriptType aWorkerScriptType, ErrorResult& aRv) {
2447   nsTArray<ScriptLoadInfo> loadInfos;
2448 
2449   ScriptLoadInfo* info = loadInfos.AppendElement();
2450   info->mURL = aScriptURL;
2451   info->mLoadFlags = aWorkerPrivate->GetLoadFlags();
2452 
2453   // We are loading the main script, so the worker's Client must be
2454   // reserved.
2455   if (aWorkerScriptType == WorkerScript) {
2456     info->mReservedClientInfo = aWorkerPrivate->GlobalScope()->GetClientInfo();
2457   } else {
2458     info->mReservedClientInfo =
2459         aWorkerPrivate->DebuggerGlobalScope()->GetClientInfo();
2460   }
2461 
2462   LoadAllScripts(aWorkerPrivate, std::move(aOriginStack), std::move(loadInfos),
2463                  true, aWorkerScriptType, aRv);
2464 }
2465 
Load(WorkerPrivate * aWorkerPrivate,UniquePtr<SerializedStackHolder> aOriginStack,const nsTArray<nsString> & aScriptURLs,WorkerScriptType aWorkerScriptType,ErrorResult & aRv)2466 void Load(WorkerPrivate* aWorkerPrivate,
2467           UniquePtr<SerializedStackHolder> aOriginStack,
2468           const nsTArray<nsString>& aScriptURLs,
2469           WorkerScriptType aWorkerScriptType, ErrorResult& aRv) {
2470   const uint32_t urlCount = aScriptURLs.Length();
2471 
2472   if (!urlCount) {
2473     return;
2474   }
2475 
2476   if (urlCount > MAX_CONCURRENT_SCRIPTS) {
2477     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
2478     return;
2479   }
2480 
2481   nsTArray<ScriptLoadInfo> loadInfos = TransformIntoNewArray(
2482       aScriptURLs,
2483       [loadFlags = aWorkerPrivate->GetLoadFlags()](const auto& scriptURL) {
2484         ScriptLoadInfo res;
2485         res.mURL = scriptURL;
2486         res.mLoadFlags = loadFlags;
2487         return res;
2488       });
2489 
2490   LoadAllScripts(aWorkerPrivate, std::move(aOriginStack), std::move(loadInfos),
2491                  false, aWorkerScriptType, aRv);
2492 }
2493 
2494 }  // namespace workerinternals
2495 
2496 }  // namespace dom
2497 }  // namespace mozilla
2498