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