Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/serviceworkers/ServiceWorkerScriptCache.cpp
Line
Count
Source (jump to first uncovered line)
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 "ServiceWorkerScriptCache.h"
8
#include "mozilla/SystemGroup.h"
9
#include "mozilla/Unused.h"
10
#include "mozilla/dom/CacheBinding.h"
11
#include "mozilla/dom/cache/CacheStorage.h"
12
#include "mozilla/dom/cache/Cache.h"
13
#include "mozilla/dom/Promise.h"
14
#include "mozilla/dom/PromiseWorkerProxy.h"
15
#include "mozilla/dom/ScriptLoader.h"
16
#include "mozilla/dom/WorkerCommon.h"
17
#include "mozilla/ipc/BackgroundUtils.h"
18
#include "mozilla/ipc/PBackgroundSharedTypes.h"
19
#include "nsICacheInfoChannel.h"
20
#include "nsIHttpChannelInternal.h"
21
#include "nsIStreamLoader.h"
22
#include "nsIThreadRetargetableRequest.h"
23
24
#include "nsIInputStreamPump.h"
25
#include "nsIPrincipal.h"
26
#include "nsIScriptError.h"
27
#include "nsIScriptSecurityManager.h"
28
#include "nsContentUtils.h"
29
#include "nsNetUtil.h"
30
#include "ServiceWorkerManager.h"
31
#include "nsStringStream.h"
32
33
using mozilla::dom::cache::Cache;
34
using mozilla::dom::cache::CacheStorage;
35
using mozilla::ipc::PrincipalInfo;
36
37
namespace mozilla {
38
namespace dom {
39
40
namespace serviceWorkerScriptCache {
41
42
namespace {
43
44
already_AddRefed<CacheStorage>
45
CreateCacheStorage(JSContext* aCx, nsIPrincipal* aPrincipal, ErrorResult& aRv)
46
0
{
47
0
  MOZ_ASSERT(NS_IsMainThread());
48
0
  MOZ_ASSERT(aPrincipal);
49
0
50
0
  nsIXPConnect* xpc = nsContentUtils::XPConnect();
51
0
  MOZ_ASSERT(xpc, "This should never be null!");
52
0
  JS::Rooted<JSObject*> sandbox(aCx);
53
0
  aRv = xpc->CreateSandbox(aCx, aPrincipal, sandbox.address());
54
0
  if (NS_WARN_IF(aRv.Failed())) {
55
0
    return nullptr;
56
0
  }
57
0
58
0
  // This is called when the JSContext is not in a realm, so CreateSandbox
59
0
  // returned an unwrapped global.
60
0
  MOZ_ASSERT(JS_IsGlobalObject(sandbox));
61
0
62
0
  nsCOMPtr<nsIGlobalObject> sandboxGlobalObject = xpc::NativeGlobal(sandbox);
63
0
  if (!sandboxGlobalObject) {
64
0
    aRv.Throw(NS_ERROR_FAILURE);
65
0
    return nullptr;
66
0
  }
67
0
68
0
  // We assume private browsing is not enabled here.  The ScriptLoader
69
0
  // explicitly fails for private browsing so there should never be
70
0
  // a service worker running in private browsing mode.  Therefore if
71
0
  // we are purging scripts or running a comparison algorithm we cannot
72
0
  // be in private browing.
73
0
  //
74
0
  // Also, bypass the CacheStorage trusted origin checks.  The ServiceWorker
75
0
  // has validated the origin prior to this point.  All the information
76
0
  // to revalidate is not available now.
77
0
  return CacheStorage::CreateOnMainThread(cache::CHROME_ONLY_NAMESPACE,
78
0
                                          sandboxGlobalObject, aPrincipal,
79
0
                                          true /* force trusted origin */,
80
0
                                          aRv);
81
0
}
82
83
class CompareManager;
84
class CompareCache;
85
86
// This class downloads a URL from the network, compare the downloaded script
87
// with an existing cache if provided, and report to CompareManager via calling
88
// ComparisonFinished().
89
class CompareNetwork final : public nsIStreamLoaderObserver,
90
                             public nsIRequestObserver
91
{
92
public:
93
  NS_DECL_ISUPPORTS
94
  NS_DECL_NSISTREAMLOADEROBSERVER
95
  NS_DECL_NSIREQUESTOBSERVER
96
97
  CompareNetwork(CompareManager* aManager,
98
                 ServiceWorkerRegistrationInfo* aRegistration,
99
                 bool aIsMainScript)
100
    : mManager(aManager)
101
    , mRegistration(aRegistration)
102
    , mInternalHeaders(new InternalHeaders())
103
    , mLoadFlags(nsIChannel::LOAD_BYPASS_SERVICE_WORKER)
104
    , mState(WaitingForInitialization)
105
    , mNetworkResult(NS_OK)
106
    , mCacheResult(NS_OK)
107
    , mIsMainScript(aIsMainScript)
108
    , mIsFromCache(false)
109
0
  {
110
0
    MOZ_ASSERT(aManager);
111
0
    MOZ_ASSERT(NS_IsMainThread());
112
0
  }
113
114
  nsresult
115
  Initialize(nsIPrincipal* aPrincipal,
116
             const nsAString& aURL,
117
             nsILoadGroup* aLoadGroup,
118
             Cache* const aCache);
119
120
  void
121
  Abort();
122
123
  void
124
  NetworkFinish(nsresult aRv);
125
126
  void
127
  CacheFinish(nsresult aRv);
128
129
  const nsString& URL() const
130
0
  {
131
0
    MOZ_ASSERT(NS_IsMainThread());
132
0
    return mURL;
133
0
  }
134
135
  const nsString& Buffer() const
136
0
  {
137
0
    MOZ_ASSERT(NS_IsMainThread());
138
0
    return mBuffer;
139
0
  }
140
141
  const ChannelInfo&
142
  GetChannelInfo() const
143
0
  {
144
0
    return mChannelInfo;
145
0
  }
146
147
  already_AddRefed<InternalHeaders>
148
  GetInternalHeaders() const
149
0
  {
150
0
    RefPtr<InternalHeaders> internalHeaders = mInternalHeaders;
151
0
    return internalHeaders.forget();
152
0
  }
153
154
  UniquePtr<PrincipalInfo>
155
  TakePrincipalInfo()
156
0
  {
157
0
    return std::move(mPrincipalInfo);
158
0
  }
159
160
  bool
161
  Succeeded() const
162
0
  {
163
0
    return NS_SUCCEEDED(mNetworkResult);
164
0
  }
165
166
  const nsTArray<nsCString>&
167
  URLList() const
168
0
  {
169
0
    return mURLList;
170
0
  }
171
172
private:
173
  ~CompareNetwork()
174
0
  {
175
0
    MOZ_ASSERT(NS_IsMainThread());
176
0
    MOZ_ASSERT(!mCC);
177
0
  }
178
179
  void
180
  Finish();
181
182
  nsresult
183
  SetPrincipalInfo(nsIChannel* aChannel);
184
185
  RefPtr<CompareManager> mManager;
186
  RefPtr<CompareCache> mCC;
187
  RefPtr<ServiceWorkerRegistrationInfo> mRegistration;
188
189
  nsCOMPtr<nsIChannel> mChannel;
190
  nsString mBuffer;
191
  nsString mURL;
192
  ChannelInfo mChannelInfo;
193
  RefPtr<InternalHeaders> mInternalHeaders;
194
  UniquePtr<PrincipalInfo> mPrincipalInfo;
195
  nsTArray<nsCString> mURLList;
196
197
  nsCString mMaxScope;
198
  nsLoadFlags mLoadFlags;
199
200
  enum {
201
    WaitingForInitialization,
202
    WaitingForBothFinished,
203
    WaitingForNetworkFinished,
204
    WaitingForCacheFinished,
205
    Finished
206
  } mState;
207
208
  nsresult mNetworkResult;
209
  nsresult mCacheResult;
210
211
  const bool mIsMainScript;
212
  bool mIsFromCache;
213
};
214
215
NS_IMPL_ISUPPORTS(CompareNetwork, nsIStreamLoaderObserver,
216
                  nsIRequestObserver)
217
218
// This class gets a cached Response from the CacheStorage and then it calls
219
// CacheFinish() in the CompareNetwork.
220
class CompareCache final : public PromiseNativeHandler
221
                         , public nsIStreamLoaderObserver
222
{
223
public:
224
  NS_DECL_ISUPPORTS
225
  NS_DECL_NSISTREAMLOADEROBSERVER
226
227
  explicit CompareCache(CompareNetwork* aCN)
228
    : mCN(aCN)
229
    , mState(WaitingForInitialization)
230
    , mInCache(false)
231
0
  {
232
0
    MOZ_ASSERT(aCN);
233
0
    MOZ_ASSERT(NS_IsMainThread());
234
0
  }
235
236
  nsresult
237
  Initialize(Cache* const aCache, const nsAString& aURL);
238
239
  void
240
  Finish(nsresult aStatus, bool aInCache);
241
242
  void
243
  Abort();
244
245
  virtual void
246
  ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
247
248
  virtual void
249
  RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
250
251
  const nsString& Buffer() const
252
0
  {
253
0
    MOZ_ASSERT(NS_IsMainThread());
254
0
    return mBuffer;
255
0
  }
256
257
  bool
258
  InCache()
259
0
  {
260
0
    return mInCache;
261
0
  }
262
263
private:
264
  ~CompareCache()
265
0
  {
266
0
    MOZ_ASSERT(NS_IsMainThread());
267
0
  }
268
269
  void
270
  ManageValueResult(JSContext* aCx, JS::Handle<JS::Value> aValue);
271
272
  RefPtr<CompareNetwork> mCN;
273
  nsCOMPtr<nsIInputStreamPump> mPump;
274
275
  nsString mURL;
276
  nsString mBuffer;
277
278
  enum {
279
    WaitingForInitialization,
280
    WaitingForScript,
281
    Finished,
282
  } mState;
283
284
  bool mInCache;
285
};
286
287
NS_IMPL_ISUPPORTS(CompareCache, nsIStreamLoaderObserver)
288
289
class CompareManager final : public PromiseNativeHandler
290
{
291
public:
292
  NS_DECL_ISUPPORTS
293
294
  explicit CompareManager(ServiceWorkerRegistrationInfo* aRegistration,
295
                          CompareCallback* aCallback)
296
    : mRegistration(aRegistration)
297
    , mCallback(aCallback)
298
    , mLoadFlags(nsIChannel::LOAD_BYPASS_SERVICE_WORKER)
299
    , mState(WaitingForInitialization)
300
    , mPendingCount(0)
301
    , mOnFailure(OnFailure::DoNothing)
302
    , mAreScriptsEqual(true)
303
0
  {
304
0
    MOZ_ASSERT(NS_IsMainThread());
305
0
    MOZ_ASSERT(aRegistration);
306
0
  }
307
308
  nsresult
309
  Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL,
310
             const nsAString& aCacheName, nsILoadGroup* aLoadGroup);
311
312
  void
313
  ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
314
315
  void
316
  RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
317
318
  CacheStorage*
319
  CacheStorage_()
320
0
  {
321
0
    MOZ_ASSERT(NS_IsMainThread());
322
0
    MOZ_ASSERT(mCacheStorage);
323
0
    return mCacheStorage;
324
0
  }
325
326
  void
327
  ComparisonFinished(nsresult aStatus,
328
                     bool aIsMainScript,
329
                     bool aIsEqual,
330
                     const nsACString& aMaxScope,
331
                     nsLoadFlags aLoadFlags)
332
0
  {
333
0
    MOZ_ASSERT(NS_IsMainThread());
334
0
    if (mState == Finished) {
335
0
      return;
336
0
    }
337
0
338
0
    MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForScriptOrComparisonResult);
339
0
340
0
    if (NS_WARN_IF(NS_FAILED(aStatus))) {
341
0
      Fail(aStatus);
342
0
      return;
343
0
    }
344
0
345
0
    mAreScriptsEqual = mAreScriptsEqual && aIsEqual;
346
0
347
0
    if (aIsMainScript) {
348
0
      mMaxScope = aMaxScope;
349
0
      mLoadFlags = aLoadFlags;
350
0
    }
351
0
352
0
    // Check whether all CompareNetworks finished their jobs.
353
0
    MOZ_DIAGNOSTIC_ASSERT(mPendingCount > 0);
354
0
    if (--mPendingCount) {
355
0
      return;
356
0
    }
357
0
358
0
    if (mAreScriptsEqual) {
359
0
      MOZ_ASSERT(mCallback);
360
0
      mCallback->ComparisonResult(aStatus,
361
0
                                  true /* aSameScripts */,
362
0
                                  mOnFailure,
363
0
                                  EmptyString(),
364
0
                                  mMaxScope,
365
0
                                  mLoadFlags);
366
0
      Cleanup();
367
0
      return;
368
0
    }
369
0
370
0
    // Write to Cache so ScriptLoader reads succeed.
371
0
    WriteNetworkBufferToNewCache();
372
0
  }
373
374
private:
375
  ~CompareManager()
376
0
  {
377
0
    MOZ_ASSERT(NS_IsMainThread());
378
0
    MOZ_ASSERT(mCNList.Length() == 0);
379
0
  }
380
381
  void
382
  Fail(nsresult aStatus);
383
384
  void
385
  Cleanup();
386
387
  nsresult
388
  FetchScript(const nsAString& aURL,
389
              bool aIsMainScript,
390
              Cache* const aCache = nullptr)
391
0
  {
392
0
    MOZ_ASSERT(NS_IsMainThread());
393
0
394
0
    MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForInitialization ||
395
0
                          mState == WaitingForScriptOrComparisonResult);
396
0
397
0
    RefPtr<CompareNetwork> cn = new CompareNetwork(this,
398
0
                                                   mRegistration,
399
0
                                                   aIsMainScript);
400
0
    mCNList.AppendElement(cn);
401
0
    mPendingCount += 1;
402
0
403
0
    nsresult rv = cn->Initialize(mPrincipal, aURL, mLoadGroup, aCache);
404
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
405
0
      return rv;
406
0
    }
407
0
408
0
    return NS_OK;
409
0
  }
410
411
  void
412
  ManageOldCache(JSContext* aCx, JS::Handle<JS::Value> aValue)
413
0
  {
414
0
    MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForExistingOpen);
415
0
416
0
    // RAII Cleanup when fails.
417
0
    nsresult rv = NS_ERROR_FAILURE;
418
0
    auto guard = MakeScopeExit([&] {
419
0
        Fail(rv);
420
0
    });
421
0
422
0
    if (NS_WARN_IF(!aValue.isObject())) {
423
0
      return;
424
0
    }
425
0
426
0
    MOZ_ASSERT(!mOldCache);
427
0
    JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
428
0
    if (NS_WARN_IF(!obj) ||
429
0
        NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Cache, obj, mOldCache)))) {
430
0
      return;
431
0
    }
432
0
433
0
    Optional<RequestOrUSVString> request;
434
0
    CacheQueryOptions options;
435
0
    ErrorResult error;
436
0
    RefPtr<Promise> promise = mOldCache->Keys(aCx, request, options, error);
437
0
    if (NS_WARN_IF(error.Failed())) {
438
0
      // No exception here because there are no ReadableStreams involved here.
439
0
      MOZ_ASSERT(!error.IsJSException());
440
0
      rv = error.StealNSResult();
441
0
      return;
442
0
    }
443
0
444
0
    mState = WaitingForExistingKeys;
445
0
    promise->AppendNativeHandler(this);
446
0
    guard.release();
447
0
  }
448
449
  void
450
  ManageOldKeys(JSContext* aCx, JS::Handle<JS::Value> aValue)
451
0
  {
452
0
    MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForExistingKeys);
453
0
454
0
    // RAII Cleanup when fails.
455
0
    nsresult rv = NS_ERROR_FAILURE;
456
0
    auto guard = MakeScopeExit([&] {
457
0
        Fail(rv);
458
0
    });
459
0
460
0
    if (NS_WARN_IF(!aValue.isObject())) {
461
0
      return;
462
0
    }
463
0
464
0
    JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
465
0
    if (NS_WARN_IF(!obj)) {
466
0
      return;
467
0
    }
468
0
469
0
    uint32_t len = 0;
470
0
    if (!JS_GetArrayLength(aCx, obj, &len)) {
471
0
      return;
472
0
    }
473
0
474
0
    // Fetch and compare the source scripts.
475
0
    MOZ_ASSERT(mPendingCount == 0);
476
0
477
0
    mState = WaitingForScriptOrComparisonResult;
478
0
479
0
    bool hasMainScript = false;
480
0
    AutoTArray<nsString, 8> urlList;
481
0
482
0
    // Extract the list of URLs in the old cache.
483
0
    for (uint32_t i = 0; i < len; ++i) {
484
0
      JS::Rooted<JS::Value> val(aCx);
485
0
      if (NS_WARN_IF(!JS_GetElement(aCx, obj, i, &val)) ||
486
0
          NS_WARN_IF(!val.isObject())) {
487
0
        return;
488
0
      }
489
0
490
0
      Request* request;
491
0
      JS::Rooted<JSObject*> requestObj(aCx, &val.toObject());
492
0
      if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Request, &requestObj, request)))) {
493
0
        return;
494
0
      };
495
0
496
0
      nsString url;
497
0
      request->GetUrl(url);
498
0
499
0
      if (!hasMainScript && url == mURL) {
500
0
        hasMainScript = true;
501
0
      }
502
0
503
0
      urlList.AppendElement(url);
504
0
    }
505
0
506
0
    // If the main script is missing, then something has gone wrong.  We
507
0
    // will try to continue with the update process to trigger a new
508
0
    // installation.  If that fails, however, then uninstall the registration
509
0
    // because it is broken in a way that cannot be fixed.
510
0
    if (!hasMainScript) {
511
0
      mOnFailure = OnFailure::Uninstall;
512
0
    }
513
0
514
0
    // Always make sure to fetch the main script.  If the old cache has
515
0
    // no entries or the main script entry is missing, then the loop below
516
0
    // may not trigger it.  This should not really happen, but we handle it
517
0
    // gracefully if it does occur.  Its possible the bad cache state is due
518
0
    // to a crash or shutdown during an update, etc.
519
0
    rv = FetchScript(mURL, true /* aIsMainScript */, mOldCache);
520
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
521
0
      return;
522
0
    }
523
0
524
0
    for (const auto& url : urlList) {
525
0
      // We explicitly start the fetch for the main script above.
526
0
      if (mURL == url) {
527
0
        continue;
528
0
      }
529
0
530
0
      rv = FetchScript(url, false /* aIsMainScript */, mOldCache);
531
0
      if (NS_WARN_IF(NS_FAILED(rv))) {
532
0
        return;
533
0
      }
534
0
    }
535
0
536
0
    guard.release();
537
0
  }
538
539
  void
540
  ManageNewCache(JSContext* aCx, JS::Handle<JS::Value> aValue)
541
0
  {
542
0
    MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForOpen);
543
0
544
0
    // RAII Cleanup when fails.
545
0
    nsresult rv = NS_ERROR_FAILURE;
546
0
    auto guard = MakeScopeExit([&] {
547
0
        Fail(rv);
548
0
    });
549
0
550
0
    if (NS_WARN_IF(!aValue.isObject())) {
551
0
      return;
552
0
    }
553
0
554
0
    JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
555
0
    if (NS_WARN_IF(!obj)) {
556
0
      return;
557
0
    }
558
0
559
0
    Cache* cache = nullptr;
560
0
    rv = UNWRAP_OBJECT(Cache, &obj, cache);
561
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
562
0
      return;
563
0
    }
564
0
565
0
    // Just to be safe.
566
0
    RefPtr<Cache> kungfuDeathGrip = cache;
567
0
568
0
    MOZ_ASSERT(mPendingCount == 0);
569
0
    for (uint32_t i = 0; i < mCNList.Length(); ++i) {
570
0
      // We bail out immediately when something goes wrong.
571
0
      rv = WriteToCache(aCx, cache, mCNList[i]);
572
0
      if (NS_WARN_IF(NS_FAILED(rv))) {
573
0
        return;
574
0
      }
575
0
    }
576
0
577
0
    mState = WaitingForPut;
578
0
    guard.release();
579
0
  }
580
581
  void
582
  WriteNetworkBufferToNewCache()
583
0
  {
584
0
    MOZ_ASSERT(NS_IsMainThread());
585
0
    MOZ_ASSERT(mCNList.Length() != 0);
586
0
    MOZ_ASSERT(mCacheStorage);
587
0
    MOZ_ASSERT(mNewCacheName.IsEmpty());
588
0
589
0
    ErrorResult result;
590
0
    result = serviceWorkerScriptCache::GenerateCacheName(mNewCacheName);
591
0
    if (NS_WARN_IF(result.Failed())) {
592
0
      MOZ_ASSERT(!result.IsErrorWithMessage());
593
0
      Fail(result.StealNSResult());
594
0
      return;
595
0
    }
596
0
597
0
    RefPtr<Promise> cacheOpenPromise = mCacheStorage->Open(mNewCacheName, result);
598
0
    if (NS_WARN_IF(result.Failed())) {
599
0
      MOZ_ASSERT(!result.IsErrorWithMessage());
600
0
      Fail(result.StealNSResult());
601
0
      return;
602
0
    }
603
0
604
0
    mState = WaitingForOpen;
605
0
    cacheOpenPromise->AppendNativeHandler(this);
606
0
  }
607
608
  nsresult
609
  WriteToCache(JSContext* aCx, Cache* aCache, CompareNetwork* aCN)
610
0
  {
611
0
    MOZ_ASSERT(NS_IsMainThread());
612
0
    MOZ_ASSERT(aCache);
613
0
    MOZ_ASSERT(aCN);
614
0
    MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForOpen);
615
0
616
0
    // We don't have to save any information from a failed CompareNetwork.
617
0
    if (!aCN->Succeeded()) {
618
0
      return NS_OK;
619
0
    }
620
0
621
0
    ErrorResult result;
622
0
    nsCOMPtr<nsIInputStream> body;
623
0
    result = NS_NewCStringInputStream(getter_AddRefs(body),
624
0
                                      NS_ConvertUTF16toUTF8(aCN->Buffer()));
625
0
    if (NS_WARN_IF(result.Failed())) {
626
0
      MOZ_ASSERT(!result.IsErrorWithMessage());
627
0
      return result.StealNSResult();
628
0
    }
629
0
630
0
    RefPtr<InternalResponse> ir =
631
0
      new InternalResponse(200, NS_LITERAL_CSTRING("OK"));
632
0
    ir->SetBody(body, aCN->Buffer().Length());
633
0
    ir->SetURLList(aCN->URLList());
634
0
635
0
    ir->InitChannelInfo(aCN->GetChannelInfo());
636
0
    UniquePtr<PrincipalInfo> principalInfo = aCN->TakePrincipalInfo();
637
0
    if (principalInfo) {
638
0
      ir->SetPrincipalInfo(std::move(principalInfo));
639
0
    }
640
0
641
0
    RefPtr<InternalHeaders> internalHeaders = aCN->GetInternalHeaders();
642
0
    ir->Headers()->Fill(*(internalHeaders.get()), IgnoreErrors());
643
0
644
0
    RefPtr<Response> response =
645
0
      new Response(aCache->GetGlobalObject(), ir, nullptr);
646
0
647
0
    RequestOrUSVString request;
648
0
    request.SetAsUSVString().Rebind(aCN->URL().Data(), aCN->URL().Length());
649
0
650
0
    // For now we have to wait until the Put Promise is fulfilled before we can
651
0
    // continue since Cache does not yet support starting a read that is being
652
0
    // written to.
653
0
    RefPtr<Promise> cachePromise = aCache->Put(aCx, request, *response, result);
654
0
    if (NS_WARN_IF(result.Failed())) {
655
0
      // No exception here because there are no ReadableStreams involved here.
656
0
      MOZ_ASSERT(!result.IsJSException());
657
0
      MOZ_ASSERT(!result.IsErrorWithMessage());
658
0
      return result.StealNSResult();
659
0
    }
660
0
661
0
    mPendingCount += 1;
662
0
    cachePromise->AppendNativeHandler(this);
663
0
    return NS_OK;
664
0
  }
665
666
  RefPtr<ServiceWorkerRegistrationInfo> mRegistration;
667
  RefPtr<CompareCallback> mCallback;
668
  RefPtr<CacheStorage> mCacheStorage;
669
670
  nsTArray<RefPtr<CompareNetwork>> mCNList;
671
672
  nsString mURL;
673
  RefPtr<nsIPrincipal> mPrincipal;
674
  RefPtr<nsILoadGroup> mLoadGroup;
675
676
  // Used for the old cache where saves the old source scripts.
677
  RefPtr<Cache> mOldCache;
678
679
  // Only used if the network script has changed and needs to be cached.
680
  nsString mNewCacheName;
681
682
  nsCString mMaxScope;
683
  nsLoadFlags mLoadFlags;
684
685
  enum {
686
    WaitingForInitialization,
687
    WaitingForExistingOpen,
688
    WaitingForExistingKeys,
689
    WaitingForScriptOrComparisonResult,
690
    WaitingForOpen,
691
    WaitingForPut,
692
    Finished
693
  } mState;
694
695
  uint32_t mPendingCount;
696
  OnFailure mOnFailure;
697
  bool mAreScriptsEqual;
698
};
699
700
NS_IMPL_ISUPPORTS0(CompareManager)
701
702
nsresult
703
CompareNetwork::Initialize(nsIPrincipal* aPrincipal,
704
                           const nsAString& aURL,
705
                           nsILoadGroup* aLoadGroup,
706
                           Cache* const aCache)
707
0
{
708
0
  MOZ_ASSERT(aPrincipal);
709
0
  MOZ_ASSERT(NS_IsMainThread());
710
0
711
0
  nsCOMPtr<nsIURI> uri;
712
0
  nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr, nullptr);
713
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
714
0
    return rv;
715
0
  }
716
0
717
0
  mURL = aURL;
718
0
  mURLList.AppendElement(NS_ConvertUTF16toUTF8(mURL));
719
0
720
0
  nsCOMPtr<nsILoadGroup> loadGroup;
721
0
  rv = NS_NewLoadGroup(getter_AddRefs(loadGroup), aPrincipal);
722
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
723
0
    return rv;
724
0
  }
725
0
726
0
  // Update LoadFlags for propagating to ServiceWorkerInfo.
727
0
  mLoadFlags = nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
728
0
729
0
  ServiceWorkerUpdateViaCache uvc = mRegistration->GetUpdateViaCache();
730
0
  if (uvc == ServiceWorkerUpdateViaCache::None ||
731
0
      (uvc == ServiceWorkerUpdateViaCache::Imports && mIsMainScript)) {
732
0
    mLoadFlags |= nsIRequest::VALIDATE_ALWAYS;
733
0
  }
734
0
735
0
  if (mRegistration->IsLastUpdateCheckTimeOverOneDay()) {
736
0
    mLoadFlags |= nsIRequest::LOAD_BYPASS_CACHE;
737
0
  }
738
0
739
0
  // Different settings are needed for fetching imported scripts, since they
740
0
  // might be cross-origin scripts.
741
0
  uint32_t secFlags =
742
0
      mIsMainScript ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
743
0
                    : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
744
0
745
0
  nsContentPolicyType contentPolicyType =
746
0
      mIsMainScript ? nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER
747
0
                    : nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS;
748
0
749
0
  // Note that because there is no "serviceworker" RequestContext type, we can
750
0
  // use the TYPE_INTERNAL_SCRIPT content policy types when loading a service
751
0
  // worker.
752
0
  rv = NS_NewChannel(getter_AddRefs(mChannel), uri, aPrincipal, secFlags,
753
0
                     contentPolicyType,
754
0
                     nullptr, /* aPerformanceStorage */
755
0
                     loadGroup,
756
0
                     nullptr /* aCallbacks */,
757
0
                     mLoadFlags);
758
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
759
0
    return rv;
760
0
  }
761
0
762
0
  nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
763
0
  if (httpChannel) {
764
0
    // Spec says no redirects allowed for top-level SW scripts.
765
0
    if (mIsMainScript) {
766
0
      rv = httpChannel->SetRedirectionLimit(0);
767
0
      MOZ_ASSERT(NS_SUCCEEDED(rv));
768
0
    }
769
0
770
0
    rv = httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Service-Worker"),
771
0
                                       NS_LITERAL_CSTRING("script"),
772
0
                                       /* merge */ false);
773
0
    MOZ_ASSERT(NS_SUCCEEDED(rv));
774
0
  }
775
0
776
0
  nsCOMPtr<nsIStreamLoader> loader;
777
0
  rv = NS_NewStreamLoader(getter_AddRefs(loader), this, this);
778
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
779
0
    return rv;
780
0
  }
781
0
782
0
  rv = mChannel->AsyncOpen2(loader);
783
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
784
0
    return rv;
785
0
  }
786
0
787
0
  // If we do have an existing cache to compare with.
788
0
  if (aCache) {
789
0
    mCC = new CompareCache(this);
790
0
    rv = mCC->Initialize(aCache, aURL);
791
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
792
0
      Abort();
793
0
      return rv;
794
0
    }
795
0
796
0
    mState = WaitingForBothFinished;
797
0
    return NS_OK;
798
0
  }
799
0
800
0
  mState = WaitingForNetworkFinished;
801
0
  return NS_OK;
802
0
}
803
804
 void
805
CompareNetwork::Finish()
806
0
{
807
0
  if (mState == Finished) {
808
0
    return;
809
0
  }
810
0
811
0
  bool same = true;
812
0
  nsresult rv = NS_OK;
813
0
814
0
  // mNetworkResult is prior to mCacheResult, since it's needed for reporting
815
0
  // various errors to web content.
816
0
  if (NS_FAILED(mNetworkResult)) {
817
0
    // An imported script could become offline, since it might no longer be
818
0
    // needed by the new importing script. In that case, the importing script
819
0
    // must be different, and thus, it's okay to report same script found here.
820
0
    rv = mIsMainScript ? mNetworkResult : NS_OK;
821
0
    same = true;
822
0
  } else if (mCC && NS_FAILED(mCacheResult)) {
823
0
    rv = mCacheResult;
824
0
  } else { // Both passed.
825
0
    same = mCC &&
826
0
           mCC->InCache() &&
827
0
           mCC->Buffer().Equals(mBuffer);
828
0
  }
829
0
830
0
  mManager->ComparisonFinished(rv, mIsMainScript, same, mMaxScope, mLoadFlags);
831
0
832
0
  // We have done with the CompareCache.
833
0
  mCC = nullptr;
834
0
}
835
836
void
837
CompareNetwork::NetworkFinish(nsresult aRv)
838
0
{
839
0
  MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForBothFinished ||
840
0
                        mState == WaitingForNetworkFinished);
841
0
842
0
  mNetworkResult = aRv;
843
0
844
0
  if (mState == WaitingForBothFinished) {
845
0
    mState = WaitingForCacheFinished;
846
0
    return;
847
0
  }
848
0
849
0
  if (mState == WaitingForNetworkFinished) {
850
0
    Finish();
851
0
    return;
852
0
  }
853
0
}
854
855
void
856
CompareNetwork::CacheFinish(nsresult aRv)
857
0
{
858
0
  MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForBothFinished ||
859
0
                        mState == WaitingForCacheFinished);
860
0
861
0
  mCacheResult = aRv;
862
0
863
0
  if (mState == WaitingForBothFinished) {
864
0
    mState = WaitingForNetworkFinished;
865
0
    return;
866
0
  }
867
0
868
0
  if (mState == WaitingForCacheFinished) {
869
0
    Finish();
870
0
    return;
871
0
  }
872
0
}
873
874
void
875
CompareNetwork::Abort()
876
0
{
877
0
  MOZ_ASSERT(NS_IsMainThread());
878
0
879
0
  if (mState != Finished) {
880
0
    mState = Finished;
881
0
882
0
    MOZ_ASSERT(mChannel);
883
0
    mChannel->Cancel(NS_BINDING_ABORTED);
884
0
    mChannel = nullptr;
885
0
886
0
    if (mCC) {
887
0
      mCC->Abort();
888
0
      mCC = nullptr;
889
0
    }
890
0
  }
891
0
}
892
893
NS_IMETHODIMP
894
CompareNetwork::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
895
0
{
896
0
  MOZ_ASSERT(NS_IsMainThread());
897
0
898
0
  if (mState == Finished) {
899
0
    return NS_OK;
900
0
  }
901
0
902
0
  nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
903
0
  MOZ_ASSERT_IF(mIsMainScript, channel == mChannel);
904
0
  mChannel = channel;
905
0
906
0
  MOZ_ASSERT(!mChannelInfo.IsInitialized());
907
0
  mChannelInfo.InitFromChannel(mChannel);
908
0
909
0
  nsresult rv = SetPrincipalInfo(mChannel);
910
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
911
0
    return rv;
912
0
  }
913
0
914
0
  mInternalHeaders->FillResponseHeaders(mChannel);
915
0
916
0
  nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(channel));
917
0
  if (cacheChannel) {
918
0
    cacheChannel->IsFromCache(&mIsFromCache);
919
0
  }
920
0
921
0
  return NS_OK;
922
0
}
923
924
nsresult
925
CompareNetwork::SetPrincipalInfo(nsIChannel* aChannel)
926
0
{
927
0
  nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
928
0
  if (!ssm) {
929
0
    return NS_ERROR_FAILURE;
930
0
  }
931
0
932
0
  nsCOMPtr<nsIPrincipal> channelPrincipal;
933
0
  nsresult rv = ssm->GetChannelResultPrincipal(aChannel, getter_AddRefs(channelPrincipal));
934
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
935
0
    return rv;
936
0
  }
937
0
938
0
  UniquePtr<PrincipalInfo> principalInfo = MakeUnique<PrincipalInfo>();
939
0
  rv = PrincipalToPrincipalInfo(channelPrincipal, principalInfo.get());
940
0
941
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
942
0
    return rv;
943
0
  }
944
0
945
0
  mPrincipalInfo = std::move(principalInfo);
946
0
  return NS_OK;
947
0
}
948
949
NS_IMETHODIMP
950
CompareNetwork::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
951
                              nsresult aStatusCode)
952
0
{
953
0
  // Nothing to do here!
954
0
  return NS_OK;
955
0
}
956
957
NS_IMETHODIMP
958
CompareNetwork::OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
959
                                 nsresult aStatus, uint32_t aLen,
960
                                 const uint8_t* aString)
961
0
{
962
0
  MOZ_ASSERT(NS_IsMainThread());
963
0
964
0
  if (mState == Finished) {
965
0
    return NS_OK;
966
0
  }
967
0
968
0
  nsresult rv = NS_ERROR_FAILURE;
969
0
  auto guard = MakeScopeExit([&] {
970
0
    NetworkFinish(rv);
971
0
  });
972
0
973
0
  if (NS_WARN_IF(NS_FAILED(aStatus))) {
974
0
    rv = (aStatus == NS_ERROR_REDIRECT_LOOP) ? NS_ERROR_DOM_SECURITY_ERR
975
0
                                             : aStatus;
976
0
    return NS_OK;
977
0
  }
978
0
979
0
  nsCOMPtr<nsIRequest> request;
980
0
  rv = aLoader->GetRequest(getter_AddRefs(request));
981
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
982
0
    return NS_OK;
983
0
  }
984
0
985
0
  nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request);
986
0
  MOZ_ASSERT(httpChannel, "How come we don't have an HTTP channel?");
987
0
988
0
  bool requestSucceeded;
989
0
  rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
990
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
991
0
    return NS_OK;
992
0
  }
993
0
994
0
  if (NS_WARN_IF(!requestSucceeded)) {
995
0
    // Get the stringified numeric status code, not statusText which could be
996
0
    // something misleading like OK for a 404.
997
0
    uint32_t status = 0;
998
0
    Unused << httpChannel->GetResponseStatus(&status); // don't care if this fails, use 0.
999
0
    nsAutoString statusAsText;
1000
0
    statusAsText.AppendInt(status);
1001
0
1002
0
    ServiceWorkerManager::LocalizeAndReportToAllClients(
1003
0
      mRegistration->Scope(), "ServiceWorkerRegisterNetworkError",
1004
0
      nsTArray<nsString> { NS_ConvertUTF8toUTF16(mRegistration->Scope()),
1005
0
        statusAsText, mURL });
1006
0
1007
0
    rv = NS_ERROR_FAILURE;
1008
0
    return NS_OK;
1009
0
  }
1010
0
1011
0
  // Note: we explicitly don't check for the return value here, because the
1012
0
  // absence of the header is not an error condition.
1013
0
  Unused << httpChannel->GetResponseHeader(
1014
0
      NS_LITERAL_CSTRING("Service-Worker-Allowed"),
1015
0
      mMaxScope);
1016
0
1017
0
  // [9.2 Update]4.13, If response's cache state is not "local",
1018
0
  // set registration's last update check time to the current time
1019
0
  if (!mIsFromCache) {
1020
0
    mRegistration->RefreshLastUpdateCheckTime();
1021
0
  }
1022
0
1023
0
  nsAutoCString mimeType;
1024
0
  rv = httpChannel->GetContentType(mimeType);
1025
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1026
0
    // We should only end up here if !mResponseHead in the channel.  If headers
1027
0
    // were received but no content type was specified, we'll be given
1028
0
    // UNKNOWN_CONTENT_TYPE "application/x-unknown-content-type" and so fall
1029
0
    // into the next case with its better error message.
1030
0
    rv = NS_ERROR_DOM_SECURITY_ERR;
1031
0
    return rv;
1032
0
  }
1033
0
1034
0
  if (!mimeType.LowerCaseEqualsLiteral("text/javascript") &&
1035
0
      !mimeType.LowerCaseEqualsLiteral("application/x-javascript") &&
1036
0
      !mimeType.LowerCaseEqualsLiteral("application/javascript")) {
1037
0
    ServiceWorkerManager::LocalizeAndReportToAllClients(
1038
0
      mRegistration->Scope(), "ServiceWorkerRegisterMimeTypeError",
1039
0
      nsTArray<nsString> { NS_ConvertUTF8toUTF16(mRegistration->Scope()),
1040
0
        NS_ConvertUTF8toUTF16(mimeType), mURL });
1041
0
    rv = NS_ERROR_DOM_SECURITY_ERR;
1042
0
    return rv;
1043
0
  }
1044
0
1045
0
  nsCOMPtr<nsIURI> channelURL;
1046
0
  rv = httpChannel->GetURI(getter_AddRefs(channelURL));
1047
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1048
0
    return rv;
1049
0
  }
1050
0
1051
0
  nsCString channelURLSpec;
1052
0
  MOZ_ALWAYS_SUCCEEDS(channelURL->GetSpec(channelURLSpec));
1053
0
1054
0
  // Append the final URL if its different from the original
1055
0
  // request URL.  This lets us note that a redirect occurred
1056
0
  // even though we don't track every redirect URL here.
1057
0
  MOZ_DIAGNOSTIC_ASSERT(!mURLList.IsEmpty());
1058
0
  if (channelURLSpec != mURLList[0]) {
1059
0
    mURLList.AppendElement(channelURLSpec);
1060
0
  }
1061
0
1062
0
  char16_t* buffer = nullptr;
1063
0
  size_t len = 0;
1064
0
1065
0
  rv = ScriptLoader::ConvertToUTF16(httpChannel, aString, aLen,
1066
0
                                    NS_LITERAL_STRING("UTF-8"), nullptr,
1067
0
                                    buffer, len);
1068
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1069
0
    return rv;
1070
0
  }
1071
0
1072
0
  mBuffer.Adopt(buffer, len);
1073
0
1074
0
  rv = NS_OK;
1075
0
  return NS_OK;
1076
0
}
1077
1078
nsresult
1079
CompareCache::Initialize(Cache* const aCache, const nsAString& aURL)
1080
0
{
1081
0
  MOZ_ASSERT(NS_IsMainThread());
1082
0
  MOZ_ASSERT(aCache);
1083
0
  MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForInitialization);
1084
0
1085
0
  // This JSContext will not end up executing JS code because here there are
1086
0
  // no ReadableStreams involved.
1087
0
  AutoJSAPI jsapi;
1088
0
  jsapi.Init();
1089
0
1090
0
  RequestOrUSVString request;
1091
0
  request.SetAsUSVString().Rebind(aURL.Data(), aURL.Length());
1092
0
  ErrorResult error;
1093
0
  CacheQueryOptions params;
1094
0
  RefPtr<Promise> promise = aCache->Match(jsapi.cx(), request, params, error);
1095
0
  if (NS_WARN_IF(error.Failed())) {
1096
0
    // No exception here because there are no ReadableStreams involved here.
1097
0
    MOZ_ASSERT(!error.IsJSException());
1098
0
    mState = Finished;
1099
0
    return error.StealNSResult();
1100
0
  }
1101
0
1102
0
  // Retrieve the script from aCache.
1103
0
  mState = WaitingForScript;
1104
0
  promise->AppendNativeHandler(this);
1105
0
  return NS_OK;
1106
0
}
1107
1108
void
1109
CompareCache::Finish(nsresult aStatus, bool aInCache)
1110
0
{
1111
0
  if (mState != Finished) {
1112
0
    mState = Finished;
1113
0
    mInCache = aInCache;
1114
0
    mCN->CacheFinish(aStatus);
1115
0
  }
1116
0
}
1117
1118
void
1119
CompareCache::Abort()
1120
0
{
1121
0
  MOZ_ASSERT(NS_IsMainThread());
1122
0
1123
0
  if (mState != Finished) {
1124
0
    mState = Finished;
1125
0
1126
0
    if (mPump) {
1127
0
      mPump->Cancel(NS_BINDING_ABORTED);
1128
0
      mPump = nullptr;
1129
0
    }
1130
0
  }
1131
0
}
1132
1133
NS_IMETHODIMP
1134
CompareCache::OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
1135
                               nsresult aStatus, uint32_t aLen,
1136
                               const uint8_t* aString)
1137
0
{
1138
0
  MOZ_ASSERT(NS_IsMainThread());
1139
0
1140
0
  if (mState == Finished) {
1141
0
    return aStatus;
1142
0
  }
1143
0
1144
0
  if (NS_WARN_IF(NS_FAILED(aStatus))) {
1145
0
    Finish(aStatus, false);
1146
0
    return aStatus;
1147
0
  }
1148
0
1149
0
  char16_t* buffer = nullptr;
1150
0
  size_t len = 0;
1151
0
1152
0
  nsresult rv = ScriptLoader::ConvertToUTF16(nullptr, aString, aLen,
1153
0
                                             NS_LITERAL_STRING("UTF-8"),
1154
0
                                             nullptr, buffer, len);
1155
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1156
0
    Finish(rv, false);
1157
0
    return rv;
1158
0
  }
1159
0
1160
0
  mBuffer.Adopt(buffer, len);
1161
0
1162
0
  Finish(NS_OK, true);
1163
0
  return NS_OK;
1164
0
}
1165
1166
void
1167
CompareCache::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
1168
0
{
1169
0
  MOZ_ASSERT(NS_IsMainThread());
1170
0
1171
0
  switch (mState) {
1172
0
    case Finished:
1173
0
      return;
1174
0
    case WaitingForScript:
1175
0
      ManageValueResult(aCx, aValue);
1176
0
      return;
1177
0
    default:
1178
0
      MOZ_CRASH("Unacceptable state.");
1179
0
  }
1180
0
}
1181
1182
void
1183
CompareCache::RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
1184
0
{
1185
0
  MOZ_ASSERT(NS_IsMainThread());
1186
0
1187
0
  if (mState != Finished) {
1188
0
    Finish(NS_ERROR_FAILURE, false);
1189
0
    return;
1190
0
  }
1191
0
}
1192
1193
void
1194
CompareCache::ManageValueResult(JSContext* aCx, JS::Handle<JS::Value> aValue)
1195
0
{
1196
0
  MOZ_ASSERT(NS_IsMainThread());
1197
0
1198
0
  // The cache returns undefined if the object is not stored.
1199
0
  if (aValue.isUndefined()) {
1200
0
    Finish(NS_OK, false);
1201
0
    return;
1202
0
  }
1203
0
1204
0
  MOZ_ASSERT(aValue.isObject());
1205
0
1206
0
  JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
1207
0
  if (NS_WARN_IF(!obj)) {
1208
0
    Finish(NS_ERROR_FAILURE, false);
1209
0
    return;
1210
0
  }
1211
0
1212
0
  Response* response = nullptr;
1213
0
  nsresult rv = UNWRAP_OBJECT(Response, &obj, response);
1214
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1215
0
    Finish(rv, false);
1216
0
    return;
1217
0
  }
1218
0
1219
0
  MOZ_ASSERT(response->Ok());
1220
0
1221
0
  nsCOMPtr<nsIInputStream> inputStream;
1222
0
  response->GetBody(getter_AddRefs(inputStream));
1223
0
  MOZ_ASSERT(inputStream);
1224
0
1225
0
  MOZ_ASSERT(!mPump);
1226
0
  rv = NS_NewInputStreamPump(getter_AddRefs(mPump),
1227
0
                             inputStream.forget(),
1228
0
                             0, /* default segsize */
1229
0
                             0, /* default segcount */
1230
0
                             false, /* default closeWhenDone */
1231
0
                             SystemGroup::EventTargetFor(TaskCategory::Other));
1232
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1233
0
    Finish(rv, false);
1234
0
    return;
1235
0
  }
1236
0
1237
0
  nsCOMPtr<nsIStreamLoader> loader;
1238
0
  rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
1239
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1240
0
    Finish(rv, false);
1241
0
    return;
1242
0
  }
1243
0
1244
0
  rv = mPump->AsyncRead(loader, nullptr);
1245
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1246
0
    mPump = nullptr;
1247
0
    Finish(rv, false);
1248
0
    return;
1249
0
  }
1250
0
1251
0
  nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(mPump);
1252
0
  if (rr) {
1253
0
    nsCOMPtr<nsIEventTarget> sts =
1254
0
      do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
1255
0
    rv = rr->RetargetDeliveryTo(sts);
1256
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
1257
0
      mPump = nullptr;
1258
0
      Finish(rv, false);
1259
0
      return;
1260
0
    }
1261
0
  }
1262
0
}
1263
1264
nsresult
1265
CompareManager::Initialize(nsIPrincipal* aPrincipal,
1266
                           const nsAString& aURL,
1267
                           const nsAString& aCacheName,
1268
                           nsILoadGroup* aLoadGroup)
1269
0
{
1270
0
  MOZ_ASSERT(NS_IsMainThread());
1271
0
  MOZ_ASSERT(aPrincipal);
1272
0
  MOZ_ASSERT(mPendingCount == 0);
1273
0
  MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForInitialization);
1274
0
1275
0
  // RAII Cleanup when fails.
1276
0
  auto guard = MakeScopeExit([&] { Cleanup(); });
1277
0
1278
0
  mURL = aURL;
1279
0
  mPrincipal = aPrincipal;
1280
0
  mLoadGroup = aLoadGroup;
1281
0
1282
0
  // Always create a CacheStorage since we want to write the network entry to
1283
0
  // the cache even if there isn't an existing one.
1284
0
  AutoJSAPI jsapi;
1285
0
  jsapi.Init();
1286
0
  ErrorResult result;
1287
0
  mCacheStorage = CreateCacheStorage(jsapi.cx(), aPrincipal, result);
1288
0
  if (NS_WARN_IF(result.Failed())) {
1289
0
    MOZ_ASSERT(!result.IsErrorWithMessage());
1290
0
    return result.StealNSResult();
1291
0
  }
1292
0
1293
0
  // If there is no existing cache, proceed to fetch the script directly.
1294
0
  if (aCacheName.IsEmpty()) {
1295
0
    mState = WaitingForScriptOrComparisonResult;
1296
0
    nsresult rv = FetchScript(aURL, true /* aIsMainScript */);
1297
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
1298
0
      return rv;
1299
0
    }
1300
0
1301
0
    guard.release();
1302
0
    return NS_OK;
1303
0
  }
1304
0
1305
0
  // Open the cache saving the old source scripts.
1306
0
  RefPtr<Promise> promise = mCacheStorage->Open(aCacheName, result);
1307
0
  if (NS_WARN_IF(result.Failed())) {
1308
0
    MOZ_ASSERT(!result.IsErrorWithMessage());
1309
0
    return result.StealNSResult();
1310
0
  }
1311
0
1312
0
  mState = WaitingForExistingOpen;
1313
0
  promise->AppendNativeHandler(this);
1314
0
1315
0
  guard.release();
1316
0
  return NS_OK;
1317
0
}
1318
1319
// This class manages 4 promises if needed:
1320
// 1. Retrieve the Cache object by a given CacheName of OldCache.
1321
// 2. Retrieve the URLs saved in OldCache.
1322
// 3. Retrieve the Cache object of the NewCache for the newly created SW.
1323
// 4. Put the value in the cache.
1324
// For this reason we have mState to know what callback we are handling.
1325
void
1326
CompareManager::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
1327
0
{
1328
0
  MOZ_ASSERT(NS_IsMainThread());
1329
0
  MOZ_ASSERT(mCallback);
1330
0
1331
0
  switch (mState) {
1332
0
    case Finished:
1333
0
      return;
1334
0
    case WaitingForExistingOpen:
1335
0
      ManageOldCache(aCx, aValue);
1336
0
      return;
1337
0
    case WaitingForExistingKeys:
1338
0
      ManageOldKeys(aCx, aValue);
1339
0
      return;
1340
0
    case WaitingForOpen:
1341
0
      ManageNewCache(aCx, aValue);
1342
0
      return;
1343
0
    case WaitingForPut:
1344
0
      MOZ_DIAGNOSTIC_ASSERT(mPendingCount > 0);
1345
0
      if (--mPendingCount == 0) {
1346
0
        mCallback->ComparisonResult(NS_OK,
1347
0
                                    false /* aIsEqual */,
1348
0
                                    mOnFailure,
1349
0
                                    mNewCacheName,
1350
0
                                    mMaxScope,
1351
0
                                    mLoadFlags);
1352
0
        Cleanup();
1353
0
      }
1354
0
      return;
1355
0
    default:
1356
0
      MOZ_DIAGNOSTIC_ASSERT(false);
1357
0
  }
1358
0
}
1359
1360
void
1361
CompareManager::RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
1362
0
{
1363
0
  MOZ_ASSERT(NS_IsMainThread());
1364
0
  switch (mState) {
1365
0
    case Finished:
1366
0
      return;
1367
0
    case WaitingForExistingOpen:
1368
0
      NS_WARNING("Could not open the existing cache.");
1369
0
      break;
1370
0
    case WaitingForExistingKeys:
1371
0
      NS_WARNING("Could not get the existing URLs.");
1372
0
      break;
1373
0
    case WaitingForOpen:
1374
0
      NS_WARNING("Could not open cache.");
1375
0
      break;
1376
0
    case WaitingForPut:
1377
0
      NS_WARNING("Could not write to cache.");
1378
0
      break;
1379
0
    default:
1380
0
      MOZ_DIAGNOSTIC_ASSERT(false);
1381
0
  }
1382
0
1383
0
  Fail(NS_ERROR_FAILURE);
1384
0
}
1385
1386
void
1387
CompareManager::Fail(nsresult aStatus)
1388
0
{
1389
0
  MOZ_ASSERT(NS_IsMainThread());
1390
0
  mCallback->ComparisonResult(aStatus, false /* aIsEqual */, mOnFailure,
1391
0
                              EmptyString(), EmptyCString(), mLoadFlags);
1392
0
  Cleanup();
1393
0
}
1394
1395
void
1396
CompareManager::Cleanup()
1397
0
{
1398
0
  MOZ_ASSERT(NS_IsMainThread());
1399
0
1400
0
  if (mState != Finished) {
1401
0
    mState = Finished;
1402
0
1403
0
    MOZ_ASSERT(mCallback);
1404
0
    mCallback = nullptr;
1405
0
1406
0
    // Abort and release CompareNetworks.
1407
0
    for (uint32_t i = 0; i < mCNList.Length(); ++i) {
1408
0
      mCNList[i]->Abort();
1409
0
    }
1410
0
    mCNList.Clear();
1411
0
  }
1412
0
}
1413
1414
} // namespace
1415
1416
nsresult
1417
PurgeCache(nsIPrincipal* aPrincipal, const nsAString& aCacheName)
1418
0
{
1419
0
  MOZ_ASSERT(NS_IsMainThread());
1420
0
  MOZ_ASSERT(aPrincipal);
1421
0
1422
0
  if (aCacheName.IsEmpty()) {
1423
0
    return NS_OK;
1424
0
  }
1425
0
1426
0
  AutoJSAPI jsapi;
1427
0
  jsapi.Init();
1428
0
  ErrorResult rv;
1429
0
  RefPtr<CacheStorage> cacheStorage = CreateCacheStorage(jsapi.cx(), aPrincipal, rv);
1430
0
  if (NS_WARN_IF(rv.Failed())) {
1431
0
    return rv.StealNSResult();
1432
0
  }
1433
0
1434
0
  // We use the ServiceWorker scope as key for the cacheStorage.
1435
0
  RefPtr<Promise> promise =
1436
0
    cacheStorage->Delete(aCacheName, rv);
1437
0
  if (NS_WARN_IF(rv.Failed())) {
1438
0
    return rv.StealNSResult();
1439
0
  }
1440
0
1441
0
  // We don't actually care about the result of the delete operation.
1442
0
  return NS_OK;
1443
0
}
1444
1445
nsresult
1446
GenerateCacheName(nsAString& aName)
1447
0
{
1448
0
  nsresult rv;
1449
0
  nsCOMPtr<nsIUUIDGenerator> uuidGenerator =
1450
0
    do_GetService("@mozilla.org/uuid-generator;1", &rv);
1451
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1452
0
    return rv;
1453
0
  }
1454
0
1455
0
  nsID id;
1456
0
  rv = uuidGenerator->GenerateUUIDInPlace(&id);
1457
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1458
0
    return rv;
1459
0
  }
1460
0
1461
0
  char chars[NSID_LENGTH];
1462
0
  id.ToProvidedString(chars);
1463
0
1464
0
  // NSID_LENGTH counts the null terminator.
1465
0
  aName.AssignASCII(chars, NSID_LENGTH - 1);
1466
0
1467
0
  return NS_OK;
1468
0
}
1469
1470
nsresult
1471
Compare(ServiceWorkerRegistrationInfo* aRegistration,
1472
        nsIPrincipal* aPrincipal, const nsAString& aCacheName,
1473
        const nsAString& aURL, CompareCallback* aCallback,
1474
        nsILoadGroup* aLoadGroup)
1475
0
{
1476
0
  MOZ_ASSERT(NS_IsMainThread());
1477
0
  MOZ_ASSERT(aRegistration);
1478
0
  MOZ_ASSERT(aPrincipal);
1479
0
  MOZ_ASSERT(!aURL.IsEmpty());
1480
0
  MOZ_ASSERT(aCallback);
1481
0
1482
0
  RefPtr<CompareManager> cm = new CompareManager(aRegistration, aCallback);
1483
0
1484
0
  nsresult rv = cm->Initialize(aPrincipal, aURL, aCacheName, aLoadGroup);
1485
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1486
0
    return rv;
1487
0
  }
1488
0
1489
0
  return NS_OK;
1490
0
}
1491
1492
} // namespace serviceWorkerScriptCache
1493
1494
} // namespace dom
1495
} // namespace mozilla