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 "ServiceWorkerEvents.h"
8 
9 #include <utility>
10 
11 #include "ServiceWorker.h"
12 #include "ServiceWorkerManager.h"
13 #include "js/Conversions.h"
14 #include "js/Exception.h"  // JS::ExceptionStack, JS::StealPendingExceptionStack
15 #include "js/TypeDecls.h"
16 #include "mozilla/Encoding.h"
17 #include "mozilla/ErrorResult.h"
18 #include "mozilla/HoldDropJSObjects.h"
19 #include "mozilla/LoadInfo.h"
20 #include "mozilla/Preferences.h"
21 #include "mozilla/dom/BodyUtil.h"
22 #include "mozilla/dom/Client.h"
23 #include "mozilla/dom/EventBinding.h"
24 #include "mozilla/dom/FetchEventBinding.h"
25 #include "mozilla/dom/MessagePort.h"
26 #include "mozilla/dom/PromiseNativeHandler.h"
27 #include "mozilla/dom/PushEventBinding.h"
28 #include "mozilla/dom/PushMessageDataBinding.h"
29 #include "mozilla/dom/PushUtil.h"
30 #include "mozilla/dom/Request.h"
31 #include "mozilla/dom/Response.h"
32 #include "mozilla/dom/ServiceWorkerOp.h"
33 #include "mozilla/dom/TypedArray.h"
34 #include "mozilla/dom/WorkerPrivate.h"
35 #include "mozilla/dom/WorkerScope.h"
36 #include "mozilla/net/NeckoChannelParams.h"
37 #include "mozilla/Telemetry.h"
38 #include "nsComponentManagerUtils.h"
39 #include "nsContentPolicyUtils.h"
40 #include "nsContentUtils.h"
41 #include "nsIConsoleReportCollector.h"
42 #include "nsINetworkInterceptController.h"
43 #include "nsIScriptError.h"
44 #include "nsNetCID.h"
45 #include "nsNetUtil.h"
46 #include "nsQueryObject.h"
47 #include "nsSerializationHelper.h"
48 #include "nsServiceManagerUtils.h"
49 #include "nsStreamUtils.h"
50 #include "xpcpublic.h"
51 
52 using namespace mozilla;
53 using namespace mozilla::dom;
54 
55 namespace {
56 
AsyncLog(nsIInterceptedChannel * aInterceptedChannel,const nsACString & aRespondWithScriptSpec,uint32_t aRespondWithLineNumber,uint32_t aRespondWithColumnNumber,const nsACString & aMessageName,const nsTArray<nsString> & aParams)57 void AsyncLog(nsIInterceptedChannel* aInterceptedChannel,
58               const nsACString& aRespondWithScriptSpec,
59               uint32_t aRespondWithLineNumber,
60               uint32_t aRespondWithColumnNumber, const nsACString& aMessageName,
61               const nsTArray<nsString>& aParams) {
62   MOZ_ASSERT(aInterceptedChannel);
63   nsCOMPtr<nsIConsoleReportCollector> reporter =
64       aInterceptedChannel->GetConsoleReportCollector();
65   if (reporter) {
66     reporter->AddConsoleReport(nsIScriptError::errorFlag,
67                                "Service Worker Interception"_ns,
68                                nsContentUtils::eDOM_PROPERTIES,
69                                aRespondWithScriptSpec, aRespondWithLineNumber,
70                                aRespondWithColumnNumber, aMessageName, aParams);
71   }
72 }
73 
74 template <typename... Params>
AsyncLog(nsIInterceptedChannel * aInterceptedChannel,const nsACString & aRespondWithScriptSpec,uint32_t aRespondWithLineNumber,uint32_t aRespondWithColumnNumber,const nsACString & aMessageName,const nsAString & aFirstParam,Params &&...aParams)75 void AsyncLog(nsIInterceptedChannel* aInterceptedChannel,
76               const nsACString& aRespondWithScriptSpec,
77               uint32_t aRespondWithLineNumber,
78               uint32_t aRespondWithColumnNumber,
79               // We have to list one explicit string so that calls with an
80               // nsTArray of params won't end up in here.
81               const nsACString& aMessageName, const nsAString& aFirstParam,
82               Params&&... aParams) {
83   nsTArray<nsString> paramsList(sizeof...(Params) + 1);
84   StringArrayAppender::Append(paramsList, sizeof...(Params) + 1, aFirstParam,
85                               std::forward<Params>(aParams)...);
86   AsyncLog(aInterceptedChannel, aRespondWithScriptSpec, aRespondWithLineNumber,
87            aRespondWithColumnNumber, aMessageName, paramsList);
88 }
89 
90 }  // anonymous namespace
91 
92 namespace mozilla {
93 namespace dom {
94 
CancelChannelRunnable(nsMainThreadPtrHandle<nsIInterceptedChannel> & aChannel,nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> & aRegistration,nsresult aStatus)95 CancelChannelRunnable::CancelChannelRunnable(
96     nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
97     nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration,
98     nsresult aStatus)
99     : Runnable("dom::CancelChannelRunnable"),
100       mChannel(aChannel),
101       mRegistration(aRegistration),
102       mStatus(aStatus) {}
103 
104 NS_IMETHODIMP
Run()105 CancelChannelRunnable::Run() {
106   MOZ_ASSERT(NS_IsMainThread());
107 
108   // TODO: When bug 1204254 is implemented, this time marker should be moved to
109   // the point where the body of the network request is complete.
110   mChannel->SetHandleFetchEventEnd(TimeStamp::Now());
111   mChannel->SaveTimeStamps();
112 
113   mChannel->CancelInterception(mStatus);
114   mRegistration->MaybeScheduleUpdate();
115   return NS_OK;
116 }
117 
FetchEvent(EventTarget * aOwner)118 FetchEvent::FetchEvent(EventTarget* aOwner)
119     : ExtendableEvent(aOwner),
120       mPreventDefaultLineNumber(0),
121       mPreventDefaultColumnNumber(0),
122       mWaitToRespond(false) {}
123 
124 FetchEvent::~FetchEvent() = default;
125 
PostInit(nsMainThreadPtrHandle<nsIInterceptedChannel> & aChannel,nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> & aRegistration,const nsACString & aScriptSpec)126 void FetchEvent::PostInit(
127     nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
128     nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration,
129     const nsACString& aScriptSpec) {
130   mChannel = aChannel;
131   mRegistration = aRegistration;
132   mScriptSpec.Assign(aScriptSpec);
133 }
134 
PostInit(const nsACString & aScriptSpec,RefPtr<FetchEventOp> aRespondWithHandler)135 void FetchEvent::PostInit(const nsACString& aScriptSpec,
136                           RefPtr<FetchEventOp> aRespondWithHandler) {
137   MOZ_ASSERT(aRespondWithHandler);
138 
139   mScriptSpec.Assign(aScriptSpec);
140   mRespondWithHandler = std::move(aRespondWithHandler);
141 }
142 
143 /*static*/
Constructor(const GlobalObject & aGlobal,const nsAString & aType,const FetchEventInit & aOptions)144 already_AddRefed<FetchEvent> FetchEvent::Constructor(
145     const GlobalObject& aGlobal, const nsAString& aType,
146     const FetchEventInit& aOptions) {
147   RefPtr<EventTarget> owner = do_QueryObject(aGlobal.GetAsSupports());
148   MOZ_ASSERT(owner);
149   RefPtr<FetchEvent> e = new FetchEvent(owner);
150   bool trusted = e->Init(owner);
151   e->InitEvent(aType, aOptions.mBubbles, aOptions.mCancelable);
152   e->SetTrusted(trusted);
153   e->SetComposed(aOptions.mComposed);
154   e->mRequest = aOptions.mRequest;
155   e->mClientId = aOptions.mClientId;
156   e->mResultingClientId = aOptions.mResultingClientId;
157   RefPtr<nsIGlobalObject> global = do_QueryObject(aGlobal.GetAsSupports());
158   MOZ_ASSERT(global);
159   ErrorResult rv;
160   e->mHandled = Promise::Create(global, rv);
161   if (rv.Failed()) {
162     rv.SuppressException();
163     return nullptr;
164   }
165   return e.forget();
166 }
167 
168 namespace {
169 
170 struct RespondWithClosure {
171   nsMainThreadPtrHandle<nsIInterceptedChannel> mInterceptedChannel;
172   nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> mRegistration;
173   const nsString mRequestURL;
174   const nsCString mRespondWithScriptSpec;
175   const uint32_t mRespondWithLineNumber;
176   const uint32_t mRespondWithColumnNumber;
177 
RespondWithClosuremozilla::dom::__anon5f3e25f80211::RespondWithClosure178   RespondWithClosure(
179       nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
180       nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration,
181       const nsAString& aRequestURL, const nsACString& aRespondWithScriptSpec,
182       uint32_t aRespondWithLineNumber, uint32_t aRespondWithColumnNumber)
183       : mInterceptedChannel(aChannel),
184         mRegistration(aRegistration),
185         mRequestURL(aRequestURL),
186         mRespondWithScriptSpec(aRespondWithScriptSpec),
187         mRespondWithLineNumber(aRespondWithLineNumber),
188         mRespondWithColumnNumber(aRespondWithColumnNumber) {}
189 };
190 
191 class FinishResponse final : public Runnable {
192   nsMainThreadPtrHandle<nsIInterceptedChannel> mChannel;
193 
194  public:
FinishResponse(nsMainThreadPtrHandle<nsIInterceptedChannel> & aChannel)195   explicit FinishResponse(
196       nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel)
197       : Runnable("dom::FinishResponse"), mChannel(aChannel) {}
198 
199   NS_IMETHOD
Run()200   Run() override {
201     MOZ_ASSERT(NS_IsMainThread());
202 
203     nsresult rv = mChannel->FinishSynthesizedResponse();
204     if (NS_WARN_IF(NS_FAILED(rv))) {
205       mChannel->CancelInterception(NS_ERROR_INTERCEPTION_FAILED);
206       return NS_OK;
207     }
208 
209     TimeStamp timeStamp = TimeStamp::Now();
210     mChannel->SetHandleFetchEventEnd(timeStamp);
211     mChannel->SetFinishSynthesizedResponseEnd(timeStamp);
212     mChannel->SaveTimeStamps();
213 
214     return rv;
215   }
216 };
217 
218 class BodyCopyHandle final : public nsIInterceptedBodyCallback {
219   UniquePtr<RespondWithClosure> mClosure;
220 
221   ~BodyCopyHandle() = default;
222 
223  public:
224   NS_DECL_THREADSAFE_ISUPPORTS
225 
BodyCopyHandle(UniquePtr<RespondWithClosure> && aClosure)226   explicit BodyCopyHandle(UniquePtr<RespondWithClosure>&& aClosure)
227       : mClosure(std::move(aClosure)) {}
228 
229   NS_IMETHOD
BodyComplete(nsresult aRv)230   BodyComplete(nsresult aRv) override {
231     MOZ_ASSERT(NS_IsMainThread());
232 
233     nsCOMPtr<nsIRunnable> event;
234     if (NS_WARN_IF(NS_FAILED(aRv))) {
235       AsyncLog(mClosure->mInterceptedChannel, mClosure->mRespondWithScriptSpec,
236                mClosure->mRespondWithLineNumber,
237                mClosure->mRespondWithColumnNumber,
238                "InterceptionFailedWithURL"_ns, mClosure->mRequestURL);
239       event = new CancelChannelRunnable(mClosure->mInterceptedChannel,
240                                         mClosure->mRegistration,
241                                         NS_ERROR_INTERCEPTION_FAILED);
242     } else {
243       event = new FinishResponse(mClosure->mInterceptedChannel);
244     }
245 
246     mClosure.reset();
247 
248     event->Run();
249 
250     return NS_OK;
251   }
252 };
253 
254 NS_IMPL_ISUPPORTS(BodyCopyHandle, nsIInterceptedBodyCallback)
255 
256 class StartResponse final : public Runnable {
257   nsMainThreadPtrHandle<nsIInterceptedChannel> mChannel;
258   RefPtr<InternalResponse> mInternalResponse;
259   ChannelInfo mWorkerChannelInfo;
260   const nsCString mScriptSpec;
261   const nsCString mResponseURLSpec;
262   UniquePtr<RespondWithClosure> mClosure;
263 
264  public:
StartResponse(nsMainThreadPtrHandle<nsIInterceptedChannel> & aChannel,InternalResponse * aInternalResponse,const ChannelInfo & aWorkerChannelInfo,const nsACString & aScriptSpec,const nsACString & aResponseURLSpec,UniquePtr<RespondWithClosure> && aClosure)265   StartResponse(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
266                 InternalResponse* aInternalResponse,
267                 const ChannelInfo& aWorkerChannelInfo,
268                 const nsACString& aScriptSpec,
269                 const nsACString& aResponseURLSpec,
270                 UniquePtr<RespondWithClosure>&& aClosure)
271       : Runnable("dom::StartResponse"),
272         mChannel(aChannel),
273         mInternalResponse(aInternalResponse),
274         mWorkerChannelInfo(aWorkerChannelInfo),
275         mScriptSpec(aScriptSpec),
276         mResponseURLSpec(aResponseURLSpec),
277         mClosure(std::move(aClosure)) {}
278 
279   NS_IMETHOD
Run()280   Run() override {
281     MOZ_ASSERT(NS_IsMainThread());
282 
283     nsCOMPtr<nsIChannel> underlyingChannel;
284     nsresult rv = mChannel->GetChannel(getter_AddRefs(underlyingChannel));
285     NS_ENSURE_SUCCESS(rv, rv);
286     NS_ENSURE_TRUE(underlyingChannel, NS_ERROR_UNEXPECTED);
287     nsCOMPtr<nsILoadInfo> loadInfo = underlyingChannel->LoadInfo();
288 
289     if (!CSPPermitsResponse(loadInfo)) {
290       mChannel->CancelInterception(NS_ERROR_CONTENT_BLOCKED);
291       return NS_OK;
292     }
293 
294     ChannelInfo channelInfo;
295     if (mInternalResponse->GetChannelInfo().IsInitialized()) {
296       channelInfo = mInternalResponse->GetChannelInfo();
297     } else {
298       // We are dealing with a synthesized response here, so fall back to the
299       // channel info for the worker script.
300       channelInfo = mWorkerChannelInfo;
301     }
302     rv = mChannel->SetChannelInfo(&channelInfo);
303     if (NS_WARN_IF(NS_FAILED(rv))) {
304       mChannel->CancelInterception(NS_ERROR_INTERCEPTION_FAILED);
305       return NS_OK;
306     }
307 
308     rv = mChannel->SynthesizeStatus(
309         mInternalResponse->GetUnfilteredStatus(),
310         mInternalResponse->GetUnfilteredStatusText());
311     if (NS_WARN_IF(NS_FAILED(rv))) {
312       mChannel->CancelInterception(NS_ERROR_INTERCEPTION_FAILED);
313       return NS_OK;
314     }
315 
316     AutoTArray<InternalHeaders::Entry, 5> entries;
317     mInternalResponse->UnfilteredHeaders()->GetEntries(entries);
318     for (uint32_t i = 0; i < entries.Length(); ++i) {
319       mChannel->SynthesizeHeader(entries[i].mName, entries[i].mValue);
320     }
321 
322     auto castLoadInfo = static_cast<mozilla::net::LoadInfo*>(loadInfo.get());
323     castLoadInfo->SynthesizeServiceWorkerTainting(
324         mInternalResponse->GetTainting());
325 
326     // Get the preferred alternative data type of outter channel
327     nsAutoCString preferredAltDataType(""_ns);
328     nsCOMPtr<nsICacheInfoChannel> outerChannel =
329         do_QueryInterface(underlyingChannel);
330     if (outerChannel &&
331         !outerChannel->PreferredAlternativeDataTypes().IsEmpty()) {
332       // TODO: handle multiple types properly.
333       preferredAltDataType.Assign(
334           outerChannel->PreferredAlternativeDataTypes()[0].type());
335     }
336 
337     // Get the alternative data type saved in the InternalResponse
338     nsAutoCString altDataType;
339     nsCOMPtr<nsICacheInfoChannel> cacheInfoChannel =
340         mInternalResponse->TakeCacheInfoChannel().get();
341     if (cacheInfoChannel) {
342       cacheInfoChannel->GetAlternativeDataType(altDataType);
343     }
344 
345     nsCOMPtr<nsIInputStream> body;
346     if (preferredAltDataType.Equals(altDataType)) {
347       body = mInternalResponse->TakeAlternativeBody();
348     }
349     if (!body) {
350       mInternalResponse->GetUnfilteredBody(getter_AddRefs(body));
351     } else {
352       Telemetry::ScalarAdd(Telemetry::ScalarID::SW_ALTERNATIVE_BODY_USED_COUNT,
353                            1);
354     }
355 
356     RefPtr<BodyCopyHandle> copyHandle;
357     copyHandle = new BodyCopyHandle(std::move(mClosure));
358 
359     rv = mChannel->StartSynthesizedResponse(body, copyHandle, cacheInfoChannel,
360                                             mResponseURLSpec,
361                                             mInternalResponse->IsRedirected());
362     if (NS_WARN_IF(NS_FAILED(rv))) {
363       mChannel->CancelInterception(NS_ERROR_INTERCEPTION_FAILED);
364       return NS_OK;
365     }
366 
367     nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
368     if (obsService) {
369       obsService->NotifyObservers(
370           underlyingChannel, "service-worker-synthesized-response", nullptr);
371     }
372 
373     return rv;
374   }
375 
CSPPermitsResponse(nsILoadInfo * aLoadInfo)376   bool CSPPermitsResponse(nsILoadInfo* aLoadInfo) {
377     MOZ_ASSERT(NS_IsMainThread());
378     MOZ_ASSERT(aLoadInfo);
379     nsresult rv;
380     nsCOMPtr<nsIURI> uri;
381     nsCString url = mInternalResponse->GetUnfilteredURL();
382     if (url.IsEmpty()) {
383       // Synthetic response. The buck stops at the worker script.
384       url = mScriptSpec;
385     }
386     rv = NS_NewURI(getter_AddRefs(uri), url);
387     NS_ENSURE_SUCCESS(rv, false);
388     int16_t decision = nsIContentPolicy::ACCEPT;
389     rv = NS_CheckContentLoadPolicy(uri, aLoadInfo, ""_ns, &decision);
390     NS_ENSURE_SUCCESS(rv, false);
391     return decision == nsIContentPolicy::ACCEPT;
392   }
393 };
394 
395 class RespondWithHandler final : public PromiseNativeHandler {
396   nsMainThreadPtrHandle<nsIInterceptedChannel> mInterceptedChannel;
397   nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> mRegistration;
398   const RequestMode mRequestMode;
399   const RequestRedirect mRequestRedirectMode;
400 #ifdef DEBUG
401   const bool mIsClientRequest;
402 #endif
403   const nsCString mScriptSpec;
404   const nsString mRequestURL;
405   const nsCString mRequestFragment;
406   const nsCString mRespondWithScriptSpec;
407   const uint32_t mRespondWithLineNumber;
408   const uint32_t mRespondWithColumnNumber;
409   bool mRequestWasHandled;
410 
411  public:
412   NS_DECL_ISUPPORTS
413 
RespondWithHandler(nsMainThreadPtrHandle<nsIInterceptedChannel> & aChannel,nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> & aRegistration,RequestMode aRequestMode,bool aIsClientRequest,RequestRedirect aRedirectMode,const nsACString & aScriptSpec,const nsAString & aRequestURL,const nsACString & aRequestFragment,const nsACString & aRespondWithScriptSpec,uint32_t aRespondWithLineNumber,uint32_t aRespondWithColumnNumber)414   RespondWithHandler(
415       nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
416       nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration,
417       RequestMode aRequestMode, bool aIsClientRequest,
418       RequestRedirect aRedirectMode, const nsACString& aScriptSpec,
419       const nsAString& aRequestURL, const nsACString& aRequestFragment,
420       const nsACString& aRespondWithScriptSpec, uint32_t aRespondWithLineNumber,
421       uint32_t aRespondWithColumnNumber)
422       : mInterceptedChannel(aChannel),
423         mRegistration(aRegistration),
424         mRequestMode(aRequestMode),
425         mRequestRedirectMode(aRedirectMode)
426 #ifdef DEBUG
427         ,
428         mIsClientRequest(aIsClientRequest)
429 #endif
430         ,
431         mScriptSpec(aScriptSpec),
432         mRequestURL(aRequestURL),
433         mRequestFragment(aRequestFragment),
434         mRespondWithScriptSpec(aRespondWithScriptSpec),
435         mRespondWithLineNumber(aRespondWithLineNumber),
436         mRespondWithColumnNumber(aRespondWithColumnNumber),
437         mRequestWasHandled(false) {
438   }
439 
440   void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
441 
442   void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
443 
444   void CancelRequest(nsresult aStatus);
445 
AsyncLog(const nsACString & aMessageName,const nsTArray<nsString> & aParams)446   void AsyncLog(const nsACString& aMessageName,
447                 const nsTArray<nsString>& aParams) {
448     ::AsyncLog(mInterceptedChannel, mRespondWithScriptSpec,
449                mRespondWithLineNumber, mRespondWithColumnNumber, aMessageName,
450                aParams);
451   }
452 
AsyncLog(const nsACString & aSourceSpec,uint32_t aLine,uint32_t aColumn,const nsACString & aMessageName,const nsTArray<nsString> & aParams)453   void AsyncLog(const nsACString& aSourceSpec, uint32_t aLine, uint32_t aColumn,
454                 const nsACString& aMessageName,
455                 const nsTArray<nsString>& aParams) {
456     ::AsyncLog(mInterceptedChannel, aSourceSpec, aLine, aColumn, aMessageName,
457                aParams);
458   }
459 
460  private:
~RespondWithHandler()461   ~RespondWithHandler() {
462     if (!mRequestWasHandled) {
463       ::AsyncLog(mInterceptedChannel, mRespondWithScriptSpec,
464                  mRespondWithLineNumber, mRespondWithColumnNumber,
465                  "InterceptionFailedWithURL"_ns, mRequestURL);
466       CancelRequest(NS_ERROR_INTERCEPTION_FAILED);
467     }
468   }
469 };
470 
471 class MOZ_STACK_CLASS AutoCancel {
472   RefPtr<RespondWithHandler> mOwner;
473   nsCString mSourceSpec;
474   uint32_t mLine;
475   uint32_t mColumn;
476   nsCString mMessageName;
477   nsTArray<nsString> mParams;
478 
479  public:
AutoCancel(RespondWithHandler * aOwner,const nsString & aRequestURL)480   AutoCancel(RespondWithHandler* aOwner, const nsString& aRequestURL)
481       : mOwner(aOwner),
482         mLine(0),
483         mColumn(0),
484         mMessageName("InterceptionFailedWithURL"_ns) {
485     mParams.AppendElement(aRequestURL);
486   }
487 
~AutoCancel()488   ~AutoCancel() {
489     if (mOwner) {
490       if (mSourceSpec.IsEmpty()) {
491         mOwner->AsyncLog(mMessageName, mParams);
492       } else {
493         mOwner->AsyncLog(mSourceSpec, mLine, mColumn, mMessageName, mParams);
494       }
495       mOwner->CancelRequest(NS_ERROR_INTERCEPTION_FAILED);
496     }
497   }
498 
499   // This function steals the error message from a ErrorResult.
SetCancelErrorResult(JSContext * aCx,ErrorResult & aRv)500   void SetCancelErrorResult(JSContext* aCx, ErrorResult& aRv) {
501     MOZ_DIAGNOSTIC_ASSERT(aRv.Failed());
502     MOZ_DIAGNOSTIC_ASSERT(!JS_IsExceptionPending(aCx));
503 
504     // Storing the error as exception in the JSContext.
505     if (!aRv.MaybeSetPendingException(aCx)) {
506       return;
507     }
508 
509     MOZ_ASSERT(!aRv.Failed());
510 
511     // Let's take the pending exception.
512     JS::ExceptionStack exnStack(aCx);
513     if (!JS::StealPendingExceptionStack(aCx, &exnStack)) {
514       return;
515     }
516 
517     // Converting the exception in a JS::ErrorReportBuilder.
518     JS::ErrorReportBuilder report(aCx);
519     if (!report.init(aCx, exnStack, JS::ErrorReportBuilder::WithSideEffects)) {
520       JS_ClearPendingException(aCx);
521       return;
522     }
523 
524     MOZ_ASSERT(mOwner);
525     MOZ_ASSERT(mMessageName.EqualsLiteral("InterceptionFailedWithURL"));
526     MOZ_ASSERT(mParams.Length() == 1);
527 
528     // Let's store the error message here.
529     mMessageName.Assign(report.toStringResult().c_str());
530     mParams.Clear();
531   }
532 
533   template <typename... Params>
SetCancelMessage(const nsACString & aMessageName,Params &&...aParams)534   void SetCancelMessage(const nsACString& aMessageName, Params&&... aParams) {
535     MOZ_ASSERT(mOwner);
536     MOZ_ASSERT(mMessageName.EqualsLiteral("InterceptionFailedWithURL"));
537     MOZ_ASSERT(mParams.Length() == 1);
538     mMessageName = aMessageName;
539     mParams.Clear();
540     StringArrayAppender::Append(mParams, sizeof...(Params),
541                                 std::forward<Params>(aParams)...);
542   }
543 
544   template <typename... Params>
SetCancelMessageAndLocation(const nsACString & aSourceSpec,uint32_t aLine,uint32_t aColumn,const nsACString & aMessageName,Params &&...aParams)545   void SetCancelMessageAndLocation(const nsACString& aSourceSpec,
546                                    uint32_t aLine, uint32_t aColumn,
547                                    const nsACString& aMessageName,
548                                    Params&&... aParams) {
549     MOZ_ASSERT(mOwner);
550     MOZ_ASSERT(mMessageName.EqualsLiteral("InterceptionFailedWithURL"));
551     MOZ_ASSERT(mParams.Length() == 1);
552 
553     mSourceSpec = aSourceSpec;
554     mLine = aLine;
555     mColumn = aColumn;
556 
557     mMessageName = aMessageName;
558     mParams.Clear();
559     StringArrayAppender::Append(mParams, sizeof...(Params),
560                                 std::forward<Params>(aParams)...);
561   }
562 
Reset()563   void Reset() { mOwner = nullptr; }
564 };
565 
NS_IMPL_ISUPPORTS0(RespondWithHandler)566 NS_IMPL_ISUPPORTS0(RespondWithHandler)
567 
568 void RespondWithHandler::ResolvedCallback(JSContext* aCx,
569                                           JS::Handle<JS::Value> aValue) {
570   AutoCancel autoCancel(this, mRequestURL);
571   mInterceptedChannel->SetFinishResponseStart(TimeStamp::Now());
572 
573   if (!aValue.isObject()) {
574     NS_WARNING(
575         "FetchEvent::RespondWith was passed a promise resolved to a non-Object "
576         "value");
577 
578     nsCString sourceSpec;
579     uint32_t line = 0;
580     uint32_t column = 0;
581     nsString valueString;
582     nsContentUtils::ExtractErrorValues(aCx, aValue, sourceSpec, &line, &column,
583                                        valueString);
584 
585     autoCancel.SetCancelMessageAndLocation(sourceSpec, line, column,
586                                            "InterceptedNonResponseWithURL"_ns,
587                                            mRequestURL, valueString);
588     return;
589   }
590 
591   RefPtr<Response> response;
592   nsresult rv = UNWRAP_OBJECT(Response, &aValue.toObject(), response);
593   if (NS_FAILED(rv)) {
594     nsCString sourceSpec;
595     uint32_t line = 0;
596     uint32_t column = 0;
597     nsString valueString;
598     nsContentUtils::ExtractErrorValues(aCx, aValue, sourceSpec, &line, &column,
599                                        valueString);
600 
601     autoCancel.SetCancelMessageAndLocation(sourceSpec, line, column,
602                                            "InterceptedNonResponseWithURL"_ns,
603                                            mRequestURL, valueString);
604     return;
605   }
606 
607   WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
608   MOZ_ASSERT(worker);
609   worker->AssertIsOnWorkerThread();
610 
611   // Section "HTTP Fetch", step 3.3:
612   //  If one of the following conditions is true, return a network error:
613   //    * response's type is "error".
614   //    * request's mode is not "no-cors" and response's type is "opaque".
615   //    * request's redirect mode is not "manual" and response's type is
616   //      "opaqueredirect".
617   //    * request's redirect mode is not "follow" and response's url list
618   //      has more than one item.
619 
620   if (response->Type() == ResponseType::Error) {
621     autoCancel.SetCancelMessage("InterceptedErrorResponseWithURL"_ns,
622                                 mRequestURL);
623     return;
624   }
625 
626   MOZ_ASSERT_IF(mIsClientRequest, mRequestMode == RequestMode::Same_origin ||
627                                       mRequestMode == RequestMode::Navigate);
628 
629   if (response->Type() == ResponseType::Opaque &&
630       mRequestMode != RequestMode::No_cors) {
631     NS_ConvertASCIItoUTF16 modeString(
632         RequestModeValues::GetString(mRequestMode));
633 
634     autoCancel.SetCancelMessage("BadOpaqueInterceptionRequestModeWithURL"_ns,
635                                 mRequestURL, modeString);
636     return;
637   }
638 
639   if (mRequestRedirectMode != RequestRedirect::Manual &&
640       response->Type() == ResponseType::Opaqueredirect) {
641     autoCancel.SetCancelMessage("BadOpaqueRedirectInterceptionWithURL"_ns,
642                                 mRequestURL);
643     return;
644   }
645 
646   if (mRequestRedirectMode != RequestRedirect::Follow &&
647       response->Redirected()) {
648     autoCancel.SetCancelMessage("BadRedirectModeInterceptionWithURL"_ns,
649                                 mRequestURL);
650     return;
651   }
652 
653   {
654     ErrorResult error;
655     bool bodyUsed = response->GetBodyUsed(error);
656     error.WouldReportJSException();
657     if (NS_WARN_IF(error.Failed())) {
658       autoCancel.SetCancelErrorResult(aCx, error);
659       return;
660     }
661     if (NS_WARN_IF(bodyUsed)) {
662       autoCancel.SetCancelMessage("InterceptedUsedResponseWithURL"_ns,
663                                   mRequestURL);
664       return;
665     }
666   }
667 
668   RefPtr<InternalResponse> ir = response->GetInternalResponse();
669   if (NS_WARN_IF(!ir)) {
670     return;
671   }
672 
673   // An extra safety check to make sure our invariant that opaque and cors
674   // responses always have a URL does not break.
675   if (NS_WARN_IF((response->Type() == ResponseType::Opaque ||
676                   response->Type() == ResponseType::Cors) &&
677                  ir->GetUnfilteredURL().IsEmpty())) {
678     MOZ_DIAGNOSTIC_ASSERT(false, "Cors or opaque Response without a URL");
679     return;
680   }
681 
682   Telemetry::ScalarAdd(Telemetry::ScalarID::SW_SYNTHESIZED_RES_COUNT, 1);
683 
684   if (mRequestMode == RequestMode::Same_origin &&
685       response->Type() == ResponseType::Cors) {
686     Telemetry::ScalarAdd(Telemetry::ScalarID::SW_CORS_RES_FOR_SO_REQ_COUNT, 1);
687 
688     // XXXtt: Will have a pref to enable the quirk response in bug 1419684.
689     // The variadic template provided by StringArrayAppender requires exactly
690     // an nsString.
691     NS_ConvertUTF8toUTF16 responseURL(ir->GetUnfilteredURL());
692     autoCancel.SetCancelMessage("CorsResponseForSameOriginRequest"_ns,
693                                 mRequestURL, responseURL);
694     return;
695   }
696 
697   // Propagate the URL to the content if the request mode is not "navigate".
698   // Note that, we only reflect the final URL if the response.redirected is
699   // false. We propagate all the URLs if the response.redirected is true.
700   nsCString responseURL;
701   if (mRequestMode != RequestMode::Navigate) {
702     responseURL = ir->GetUnfilteredURL();
703 
704     // Similar to how we apply the request fragment to redirects automatically
705     // we also want to apply it automatically when propagating the response
706     // URL from a service worker interception.  Currently response.url strips
707     // the fragment, so this will never conflict with an existing fragment
708     // on the response.  In the future we will have to check for a response
709     // fragment and avoid overriding in that case.
710     if (!mRequestFragment.IsEmpty() && !responseURL.IsEmpty()) {
711       MOZ_ASSERT(!responseURL.Contains('#'));
712       responseURL.Append("#"_ns);
713       responseURL.Append(mRequestFragment);
714     }
715   }
716 
717   UniquePtr<RespondWithClosure> closure(new RespondWithClosure(
718       mInterceptedChannel, mRegistration, mRequestURL, mRespondWithScriptSpec,
719       mRespondWithLineNumber, mRespondWithColumnNumber));
720 
721   nsCOMPtr<nsIRunnable> startRunnable =
722       new StartResponse(mInterceptedChannel, ir, worker->GetChannelInfo(),
723                         mScriptSpec, responseURL, std::move(closure));
724 
725   nsCOMPtr<nsIInputStream> body;
726   ir->GetUnfilteredBody(getter_AddRefs(body));
727   // Errors and redirects may not have a body.
728   if (body) {
729     ErrorResult error;
730     response->SetBodyUsed(aCx, error);
731     error.WouldReportJSException();
732     if (NS_WARN_IF(error.Failed())) {
733       autoCancel.SetCancelErrorResult(aCx, error);
734       return;
735     }
736   }
737 
738   MOZ_ALWAYS_SUCCEEDS(worker->DispatchToMainThread(startRunnable.forget()));
739 
740   MOZ_ASSERT(!closure);
741   autoCancel.Reset();
742   mRequestWasHandled = true;
743 }
744 
RejectedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue)745 void RespondWithHandler::RejectedCallback(JSContext* aCx,
746                                           JS::Handle<JS::Value> aValue) {
747   nsCString sourceSpec = mRespondWithScriptSpec;
748   uint32_t line = mRespondWithLineNumber;
749   uint32_t column = mRespondWithColumnNumber;
750   nsString valueString;
751 
752   mInterceptedChannel->SetFinishResponseStart(TimeStamp::Now());
753 
754   nsContentUtils::ExtractErrorValues(aCx, aValue, sourceSpec, &line, &column,
755                                      valueString);
756 
757   ::AsyncLog(mInterceptedChannel, sourceSpec, line, column,
758              "InterceptionRejectedResponseWithURL"_ns, mRequestURL,
759              valueString);
760 
761   CancelRequest(NS_ERROR_INTERCEPTION_FAILED);
762 }
763 
CancelRequest(nsresult aStatus)764 void RespondWithHandler::CancelRequest(nsresult aStatus) {
765   nsCOMPtr<nsIRunnable> runnable =
766       new CancelChannelRunnable(mInterceptedChannel, mRegistration, aStatus);
767   // Note, this may run off the worker thread during worker termination.
768   WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
769   if (worker) {
770     MOZ_ALWAYS_SUCCEEDS(worker->DispatchToMainThread(runnable.forget()));
771   } else {
772     MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable.forget()));
773   }
774   mRequestWasHandled = true;
775 }
776 
777 }  // namespace
778 
RespondWith(JSContext * aCx,Promise & aArg,ErrorResult & aRv)779 void FetchEvent::RespondWith(JSContext* aCx, Promise& aArg, ErrorResult& aRv) {
780   if (!GetDispatchFlag() || mWaitToRespond) {
781     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
782     return;
783   }
784 
785   // Record where respondWith() was called in the script so we can include the
786   // information in any error reporting.  We should be guaranteed not to get
787   // a file:// string here because service workers require http/https.
788   nsCString spec;
789   uint32_t line = 0;
790   uint32_t column = 0;
791   nsJSUtils::GetCallingLocation(aCx, spec, &line, &column);
792 
793   SafeRefPtr<InternalRequest> ir = mRequest->GetInternalRequest();
794 
795   nsAutoCString requestURL;
796   ir->GetURL(requestURL);
797 
798   StopImmediatePropagation();
799   mWaitToRespond = true;
800 
801   if (mChannel) {
802     RefPtr<RespondWithHandler> handler = new RespondWithHandler(
803         mChannel, mRegistration, mRequest->Mode(), ir->IsClientRequest(),
804         mRequest->Redirect(), mScriptSpec, NS_ConvertUTF8toUTF16(requestURL),
805         ir->GetFragment(), spec, line, column);
806 
807     aArg.AppendNativeHandler(handler);
808   } else {
809     MOZ_ASSERT(mRespondWithHandler);
810 
811     mRespondWithHandler->RespondWithCalledAt(spec, line, column);
812     aArg.AppendNativeHandler(mRespondWithHandler);
813     mRespondWithHandler = nullptr;
814   }
815 
816   if (!WaitOnPromise(aArg)) {
817     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
818   }
819 }
820 
PreventDefault(JSContext * aCx,CallerType aCallerType)821 void FetchEvent::PreventDefault(JSContext* aCx, CallerType aCallerType) {
822   MOZ_ASSERT(aCx);
823   MOZ_ASSERT(aCallerType != CallerType::System,
824              "Since when do we support system-principal service workers?");
825 
826   if (mPreventDefaultScriptSpec.IsEmpty()) {
827     // Note when the FetchEvent might have been canceled by script, but don't
828     // actually log the location until we are sure it matters.  This is
829     // determined in ServiceWorkerPrivate.cpp.  We only remember the first
830     // call to preventDefault() as its the most likely to have actually canceled
831     // the event.
832     nsJSUtils::GetCallingLocation(aCx, mPreventDefaultScriptSpec,
833                                   &mPreventDefaultLineNumber,
834                                   &mPreventDefaultColumnNumber);
835   }
836 
837   Event::PreventDefault(aCx, aCallerType);
838 }
839 
ReportCanceled()840 void FetchEvent::ReportCanceled() {
841   MOZ_ASSERT(!mPreventDefaultScriptSpec.IsEmpty());
842 
843   SafeRefPtr<InternalRequest> ir = mRequest->GetInternalRequest();
844   nsAutoCString url;
845   ir->GetURL(url);
846 
847   // The variadic template provided by StringArrayAppender requires exactly
848   // an nsString.
849   NS_ConvertUTF8toUTF16 requestURL(url);
850   // nsString requestURL;
851   // CopyUTF8toUTF16(url, requestURL);
852 
853   if (mChannel) {
854     ::AsyncLog(mChannel.get(), mPreventDefaultScriptSpec,
855                mPreventDefaultLineNumber, mPreventDefaultColumnNumber,
856                "InterceptionCanceledWithURL"_ns, requestURL);
857   } else {
858     mRespondWithHandler->ReportCanceled(mPreventDefaultScriptSpec,
859                                         mPreventDefaultLineNumber,
860                                         mPreventDefaultColumnNumber);
861     mRespondWithHandler = nullptr;
862   }
863 }
864 
865 namespace {
866 
867 class WaitUntilHandler final : public PromiseNativeHandler {
868   WorkerPrivate* mWorkerPrivate;
869   const nsCString mScope;
870   nsString mSourceSpec;
871   uint32_t mLine;
872   uint32_t mColumn;
873   nsString mRejectValue;
874 
875   ~WaitUntilHandler() = default;
876 
877  public:
878   NS_DECL_THREADSAFE_ISUPPORTS
879 
WaitUntilHandler(WorkerPrivate * aWorkerPrivate,JSContext * aCx)880   WaitUntilHandler(WorkerPrivate* aWorkerPrivate, JSContext* aCx)
881       : mWorkerPrivate(aWorkerPrivate),
882         mScope(mWorkerPrivate->ServiceWorkerScope()),
883         mLine(0),
884         mColumn(0) {
885     mWorkerPrivate->AssertIsOnWorkerThread();
886 
887     // Save the location of the waitUntil() call itself as a fallback
888     // in case the rejection value does not contain any location info.
889     nsJSUtils::GetCallingLocation(aCx, mSourceSpec, &mLine, &mColumn);
890   }
891 
ResolvedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue)892   void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
893     // do nothing, we are only here to report errors
894   }
895 
RejectedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue)896   void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
897     mWorkerPrivate->AssertIsOnWorkerThread();
898 
899     nsString spec;
900     uint32_t line = 0;
901     uint32_t column = 0;
902     nsContentUtils::ExtractErrorValues(aCx, aValue, spec, &line, &column,
903                                        mRejectValue);
904 
905     // only use the extracted location if we found one
906     if (!spec.IsEmpty()) {
907       mSourceSpec = spec;
908       mLine = line;
909       mColumn = column;
910     }
911 
912     MOZ_ALWAYS_SUCCEEDS(mWorkerPrivate->DispatchToMainThread(
913         NewRunnableMethod("WaitUntilHandler::ReportOnMainThread", this,
914                           &WaitUntilHandler::ReportOnMainThread)));
915   }
916 
ReportOnMainThread()917   void ReportOnMainThread() {
918     MOZ_ASSERT(NS_IsMainThread());
919     RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
920     if (!swm) {
921       // browser shutdown
922       return;
923     }
924 
925     // TODO: Make the error message a localized string. (bug 1222720)
926     nsString message;
927     message.AppendLiteral(
928         "Service worker event waitUntil() was passed a "
929         "promise that rejected with '");
930     message.Append(mRejectValue);
931     message.AppendLiteral("'.");
932 
933     // Note, there is a corner case where this won't report to the window
934     // that triggered the error.  Consider a navigation fetch event that
935     // rejects waitUntil() without holding respondWith() open.  In this case
936     // there is no controlling document yet, the window did call .register()
937     // because there is no documeny yet, and the navigation is no longer
938     // being intercepted.
939 
940     swm->ReportToAllClients(mScope, message, mSourceSpec, u""_ns, mLine,
941                             mColumn, nsIScriptError::errorFlag);
942   }
943 };
944 
945 NS_IMPL_ISUPPORTS0(WaitUntilHandler)
946 
947 }  // anonymous namespace
948 
~ExtensionsHandler()949 ExtendableEvent::ExtensionsHandler::~ExtensionsHandler() {
950   MOZ_ASSERT(!mExtendableEvent);
951 }
952 
GetDispatchFlag() const953 bool ExtendableEvent::ExtensionsHandler::GetDispatchFlag() const {
954   // mExtendableEvent should set itself as nullptr in its destructor, and we
955   // can't be dispatching an event that doesn't exist, so this should work for
956   // as long as it's not needed to determine whether the event is still alive,
957   // which seems unlikely.
958   if (!mExtendableEvent) {
959     return false;
960   }
961 
962   return mExtendableEvent->GetDispatchFlag();
963 }
964 
SetExtendableEvent(const ExtendableEvent * const aExtendableEvent)965 void ExtendableEvent::ExtensionsHandler::SetExtendableEvent(
966     const ExtendableEvent* const aExtendableEvent) {
967   mExtendableEvent = aExtendableEvent;
968 }
969 
NS_IMPL_ADDREF_INHERITED(FetchEvent,ExtendableEvent)970 NS_IMPL_ADDREF_INHERITED(FetchEvent, ExtendableEvent)
971 NS_IMPL_RELEASE_INHERITED(FetchEvent, ExtendableEvent)
972 
973 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FetchEvent)
974 NS_INTERFACE_MAP_END_INHERITING(ExtendableEvent)
975 
976 NS_IMPL_CYCLE_COLLECTION_INHERITED(FetchEvent, ExtendableEvent, mRequest)
977 
978 ExtendableEvent::ExtendableEvent(EventTarget* aOwner)
979     : Event(aOwner, nullptr, nullptr) {}
980 
WaitOnPromise(Promise & aPromise)981 bool ExtendableEvent::WaitOnPromise(Promise& aPromise) {
982   if (!mExtensionsHandler) {
983     return false;
984   }
985   return mExtensionsHandler->WaitOnPromise(aPromise);
986 }
987 
SetKeepAliveHandler(ExtensionsHandler * aExtensionsHandler)988 void ExtendableEvent::SetKeepAliveHandler(
989     ExtensionsHandler* aExtensionsHandler) {
990   MOZ_ASSERT(!mExtensionsHandler);
991   WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
992   MOZ_ASSERT(worker);
993   worker->AssertIsOnWorkerThread();
994   mExtensionsHandler = aExtensionsHandler;
995   mExtensionsHandler->SetExtendableEvent(this);
996 }
997 
WaitUntil(JSContext * aCx,Promise & aPromise,ErrorResult & aRv)998 void ExtendableEvent::WaitUntil(JSContext* aCx, Promise& aPromise,
999                                 ErrorResult& aRv) {
1000   MOZ_ASSERT(!NS_IsMainThread());
1001 
1002   if (!WaitOnPromise(aPromise)) {
1003     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1004     return;
1005   }
1006 
1007   // Append our handler to each waitUntil promise separately so we
1008   // can record the location in script where waitUntil was called.
1009   RefPtr<WaitUntilHandler> handler =
1010       new WaitUntilHandler(GetCurrentThreadWorkerPrivate(), aCx);
1011   aPromise.AppendNativeHandler(handler);
1012 }
1013 
1014 NS_IMPL_ADDREF_INHERITED(ExtendableEvent, Event)
1015 NS_IMPL_RELEASE_INHERITED(ExtendableEvent, Event)
1016 
1017 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ExtendableEvent)
1018 NS_INTERFACE_MAP_END_INHERITING(Event)
1019 
1020 namespace {
ExtractBytesFromUSVString(const nsAString & aStr,nsTArray<uint8_t> & aBytes)1021 nsresult ExtractBytesFromUSVString(const nsAString& aStr,
1022                                    nsTArray<uint8_t>& aBytes) {
1023   MOZ_ASSERT(aBytes.IsEmpty());
1024   auto encoder = UTF_8_ENCODING->NewEncoder();
1025   CheckedInt<size_t> needed =
1026       encoder->MaxBufferLengthFromUTF16WithoutReplacement(aStr.Length());
1027   if (NS_WARN_IF(!needed.isValid() ||
1028                  !aBytes.SetLength(needed.value(), fallible))) {
1029     return NS_ERROR_OUT_OF_MEMORY;
1030   }
1031   uint32_t result;
1032   size_t read;
1033   size_t written;
1034   Tie(result, read, written) =
1035       encoder->EncodeFromUTF16WithoutReplacement(aStr, aBytes, true);
1036   MOZ_ASSERT(result == kInputEmpty);
1037   MOZ_ASSERT(read == aStr.Length());
1038   aBytes.TruncateLength(written);
1039   return NS_OK;
1040 }
1041 
ExtractBytesFromData(const OwningArrayBufferViewOrArrayBufferOrUSVString & aDataInit,nsTArray<uint8_t> & aBytes)1042 nsresult ExtractBytesFromData(
1043     const OwningArrayBufferViewOrArrayBufferOrUSVString& aDataInit,
1044     nsTArray<uint8_t>& aBytes) {
1045   if (aDataInit.IsArrayBufferView()) {
1046     const ArrayBufferView& view = aDataInit.GetAsArrayBufferView();
1047     if (NS_WARN_IF(!PushUtil::CopyArrayBufferViewToArray(view, aBytes))) {
1048       return NS_ERROR_OUT_OF_MEMORY;
1049     }
1050     return NS_OK;
1051   }
1052   if (aDataInit.IsArrayBuffer()) {
1053     const ArrayBuffer& buffer = aDataInit.GetAsArrayBuffer();
1054     if (NS_WARN_IF(!PushUtil::CopyArrayBufferToArray(buffer, aBytes))) {
1055       return NS_ERROR_OUT_OF_MEMORY;
1056     }
1057     return NS_OK;
1058   }
1059   if (aDataInit.IsUSVString()) {
1060     return ExtractBytesFromUSVString(aDataInit.GetAsUSVString(), aBytes);
1061   }
1062   MOZ_ASSERT_UNREACHABLE("Unexpected push message data");
1063   return NS_ERROR_FAILURE;
1064 }
1065 }  // namespace
1066 
PushMessageData(nsIGlobalObject * aOwner,nsTArray<uint8_t> && aBytes)1067 PushMessageData::PushMessageData(nsIGlobalObject* aOwner,
1068                                  nsTArray<uint8_t>&& aBytes)
1069     : mOwner(aOwner), mBytes(std::move(aBytes)) {}
1070 
1071 PushMessageData::~PushMessageData() = default;
1072 
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PushMessageData,mOwner)1073 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PushMessageData, mOwner)
1074 
1075 NS_IMPL_CYCLE_COLLECTING_ADDREF(PushMessageData)
1076 NS_IMPL_CYCLE_COLLECTING_RELEASE(PushMessageData)
1077 
1078 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushMessageData)
1079   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
1080   NS_INTERFACE_MAP_ENTRY(nsISupports)
1081 NS_INTERFACE_MAP_END
1082 
1083 JSObject* PushMessageData::WrapObject(JSContext* aCx,
1084                                       JS::Handle<JSObject*> aGivenProto) {
1085   return mozilla::dom::PushMessageData_Binding::Wrap(aCx, this, aGivenProto);
1086 }
1087 
Json(JSContext * cx,JS::MutableHandle<JS::Value> aRetval,ErrorResult & aRv)1088 void PushMessageData::Json(JSContext* cx, JS::MutableHandle<JS::Value> aRetval,
1089                            ErrorResult& aRv) {
1090   if (NS_FAILED(EnsureDecodedText())) {
1091     aRv.Throw(NS_ERROR_DOM_UNKNOWN_ERR);
1092     return;
1093   }
1094   BodyUtil::ConsumeJson(cx, aRetval, mDecodedText, aRv);
1095 }
1096 
Text(nsAString & aData)1097 void PushMessageData::Text(nsAString& aData) {
1098   if (NS_SUCCEEDED(EnsureDecodedText())) {
1099     aData = mDecodedText;
1100   }
1101 }
1102 
ArrayBuffer(JSContext * cx,JS::MutableHandle<JSObject * > aRetval,ErrorResult & aRv)1103 void PushMessageData::ArrayBuffer(JSContext* cx,
1104                                   JS::MutableHandle<JSObject*> aRetval,
1105                                   ErrorResult& aRv) {
1106   uint8_t* data = GetContentsCopy();
1107   if (data) {
1108     BodyUtil::ConsumeArrayBuffer(cx, aRetval, mBytes.Length(), data, aRv);
1109   }
1110 }
1111 
Blob(ErrorResult & aRv)1112 already_AddRefed<mozilla::dom::Blob> PushMessageData::Blob(ErrorResult& aRv) {
1113   uint8_t* data = GetContentsCopy();
1114   if (data) {
1115     RefPtr<mozilla::dom::Blob> blob =
1116         BodyUtil::ConsumeBlob(mOwner, u""_ns, mBytes.Length(), data, aRv);
1117     if (blob) {
1118       return blob.forget();
1119     }
1120   }
1121   return nullptr;
1122 }
1123 
EnsureDecodedText()1124 nsresult PushMessageData::EnsureDecodedText() {
1125   if (mBytes.IsEmpty() || !mDecodedText.IsEmpty()) {
1126     return NS_OK;
1127   }
1128   nsresult rv = BodyUtil::ConsumeText(
1129       mBytes.Length(), reinterpret_cast<uint8_t*>(mBytes.Elements()),
1130       mDecodedText);
1131   if (NS_WARN_IF(NS_FAILED(rv))) {
1132     mDecodedText.Truncate();
1133     return rv;
1134   }
1135   return NS_OK;
1136 }
1137 
GetContentsCopy()1138 uint8_t* PushMessageData::GetContentsCopy() {
1139   uint32_t length = mBytes.Length();
1140   void* data = malloc(length);
1141   if (!data) {
1142     return nullptr;
1143   }
1144   memcpy(data, mBytes.Elements(), length);
1145   return reinterpret_cast<uint8_t*>(data);
1146 }
1147 
PushEvent(EventTarget * aOwner)1148 PushEvent::PushEvent(EventTarget* aOwner) : ExtendableEvent(aOwner) {}
1149 
Constructor(mozilla::dom::EventTarget * aOwner,const nsAString & aType,const PushEventInit & aOptions,ErrorResult & aRv)1150 already_AddRefed<PushEvent> PushEvent::Constructor(
1151     mozilla::dom::EventTarget* aOwner, const nsAString& aType,
1152     const PushEventInit& aOptions, ErrorResult& aRv) {
1153   RefPtr<PushEvent> e = new PushEvent(aOwner);
1154   bool trusted = e->Init(aOwner);
1155   e->InitEvent(aType, aOptions.mBubbles, aOptions.mCancelable);
1156   e->SetTrusted(trusted);
1157   e->SetComposed(aOptions.mComposed);
1158   if (aOptions.mData.WasPassed()) {
1159     nsTArray<uint8_t> bytes;
1160     nsresult rv = ExtractBytesFromData(aOptions.mData.Value(), bytes);
1161     if (NS_FAILED(rv)) {
1162       aRv.Throw(rv);
1163       return nullptr;
1164     }
1165     e->mData = new PushMessageData(aOwner->GetOwnerGlobal(), std::move(bytes));
1166   }
1167   return e.forget();
1168 }
1169 
NS_IMPL_ADDREF_INHERITED(PushEvent,ExtendableEvent)1170 NS_IMPL_ADDREF_INHERITED(PushEvent, ExtendableEvent)
1171 NS_IMPL_RELEASE_INHERITED(PushEvent, ExtendableEvent)
1172 
1173 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushEvent)
1174 NS_INTERFACE_MAP_END_INHERITING(ExtendableEvent)
1175 
1176 NS_IMPL_CYCLE_COLLECTION_INHERITED(PushEvent, ExtendableEvent, mData)
1177 
1178 JSObject* PushEvent::WrapObjectInternal(JSContext* aCx,
1179                                         JS::Handle<JSObject*> aGivenProto) {
1180   return mozilla::dom::PushEvent_Binding::Wrap(aCx, this, aGivenProto);
1181 }
1182 
ExtendableMessageEvent(EventTarget * aOwner)1183 ExtendableMessageEvent::ExtendableMessageEvent(EventTarget* aOwner)
1184     : ExtendableEvent(aOwner), mData(JS::UndefinedValue()) {
1185   mozilla::HoldJSObjects(this);
1186 }
1187 
~ExtendableMessageEvent()1188 ExtendableMessageEvent::~ExtendableMessageEvent() { DropJSObjects(this); }
1189 
GetData(JSContext * aCx,JS::MutableHandle<JS::Value> aData,ErrorResult & aRv)1190 void ExtendableMessageEvent::GetData(JSContext* aCx,
1191                                      JS::MutableHandle<JS::Value> aData,
1192                                      ErrorResult& aRv) {
1193   aData.set(mData);
1194   if (!JS_WrapValue(aCx, aData)) {
1195     aRv.Throw(NS_ERROR_FAILURE);
1196   }
1197 }
1198 
GetSource(Nullable<OwningClientOrServiceWorkerOrMessagePort> & aValue) const1199 void ExtendableMessageEvent::GetSource(
1200     Nullable<OwningClientOrServiceWorkerOrMessagePort>& aValue) const {
1201   if (mClient) {
1202     aValue.SetValue().SetAsClient() = mClient;
1203   } else if (mServiceWorker) {
1204     aValue.SetValue().SetAsServiceWorker() = mServiceWorker;
1205   } else if (mMessagePort) {
1206     aValue.SetValue().SetAsMessagePort() = mMessagePort;
1207   } else {
1208     // nullptr source is possible for manually constructed event
1209     aValue.SetNull();
1210   }
1211 }
1212 
1213 /* static */
Constructor(const GlobalObject & aGlobal,const nsAString & aType,const ExtendableMessageEventInit & aOptions)1214 already_AddRefed<ExtendableMessageEvent> ExtendableMessageEvent::Constructor(
1215     const GlobalObject& aGlobal, const nsAString& aType,
1216     const ExtendableMessageEventInit& aOptions) {
1217   nsCOMPtr<EventTarget> t = do_QueryInterface(aGlobal.GetAsSupports());
1218   return Constructor(t, aType, aOptions);
1219 }
1220 
1221 /* static */
Constructor(mozilla::dom::EventTarget * aEventTarget,const nsAString & aType,const ExtendableMessageEventInit & aOptions)1222 already_AddRefed<ExtendableMessageEvent> ExtendableMessageEvent::Constructor(
1223     mozilla::dom::EventTarget* aEventTarget, const nsAString& aType,
1224     const ExtendableMessageEventInit& aOptions) {
1225   RefPtr<ExtendableMessageEvent> event =
1226       new ExtendableMessageEvent(aEventTarget);
1227 
1228   event->InitEvent(aType, aOptions.mBubbles, aOptions.mCancelable);
1229   bool trusted = event->Init(aEventTarget);
1230   event->SetTrusted(trusted);
1231 
1232   event->mData = aOptions.mData;
1233   event->mOrigin = aOptions.mOrigin;
1234   event->mLastEventId = aOptions.mLastEventId;
1235 
1236   if (!aOptions.mSource.IsNull()) {
1237     if (aOptions.mSource.Value().IsClient()) {
1238       event->mClient = aOptions.mSource.Value().GetAsClient();
1239     } else if (aOptions.mSource.Value().IsServiceWorker()) {
1240       event->mServiceWorker = aOptions.mSource.Value().GetAsServiceWorker();
1241     } else if (aOptions.mSource.Value().IsMessagePort()) {
1242       event->mMessagePort = aOptions.mSource.Value().GetAsMessagePort();
1243     }
1244   }
1245 
1246   event->mPorts.AppendElements(aOptions.mPorts);
1247   return event.forget();
1248 }
1249 
GetPorts(nsTArray<RefPtr<MessagePort>> & aPorts)1250 void ExtendableMessageEvent::GetPorts(nsTArray<RefPtr<MessagePort>>& aPorts) {
1251   aPorts = mPorts.Clone();
1252 }
1253 
1254 NS_IMPL_CYCLE_COLLECTION_MULTI_ZONE_JSHOLDER_CLASS(ExtendableMessageEvent)
1255 
1256 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ExtendableMessageEvent, Event)
1257   tmp->mData.setUndefined();
1258   NS_IMPL_CYCLE_COLLECTION_UNLINK(mClient)
1259   NS_IMPL_CYCLE_COLLECTION_UNLINK(mServiceWorker)
1260   NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessagePort)
1261   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPorts)
1262 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1263 
1264 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ExtendableMessageEvent, Event)
1265   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mClient)
1266   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServiceWorker)
1267   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMessagePort)
1268   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPorts)
1269 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1270 
1271 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(ExtendableMessageEvent, Event)
1272   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mData)
1273 NS_IMPL_CYCLE_COLLECTION_TRACE_END
1274 
1275 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ExtendableMessageEvent)
1276 NS_INTERFACE_MAP_END_INHERITING(Event)
1277 
1278 NS_IMPL_ADDREF_INHERITED(ExtendableMessageEvent, Event)
1279 NS_IMPL_RELEASE_INHERITED(ExtendableMessageEvent, Event)
1280 
1281 }  // namespace dom
1282 }  // namespace mozilla
1283