Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/worklet/Worklet.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 "Worklet.h"
8
#include "WorkletThread.h"
9
#include "AudioWorkletGlobalScope.h"
10
#include "PaintWorkletGlobalScope.h"
11
12
#include "mozilla/dom/WorkletBinding.h"
13
#include "mozilla/dom/AudioWorkletBinding.h"
14
#include "mozilla/dom/BlobBinding.h"
15
#include "mozilla/dom/DOMPrefs.h"
16
#include "mozilla/dom/Fetch.h"
17
#include "mozilla/dom/PromiseNativeHandler.h"
18
#include "mozilla/dom/RegisterWorkletBindings.h"
19
#include "mozilla/dom/Response.h"
20
#include "mozilla/dom/ScriptSettings.h"
21
#include "mozilla/dom/ScriptLoader.h"
22
#include "js/CompilationAndEvaluation.h"
23
#include "js/SourceBufferHolder.h"
24
#include "nsIInputStreamPump.h"
25
#include "nsIThreadRetargetableRequest.h"
26
#include "nsNetUtil.h"
27
#include "xpcprivate.h"
28
29
namespace mozilla {
30
namespace dom {
31
32
class ExecutionRunnable final : public Runnable
33
{
34
public:
35
  ExecutionRunnable(WorkletFetchHandler* aHandler, Worklet::WorkletType aType,
36
                    JS::UniqueTwoByteChars aScriptBuffer, size_t aScriptLength,
37
                    const WorkletLoadInfo& aWorkletLoadInfo)
38
    : Runnable("Worklet::ExecutionRunnable")
39
    , mHandler(aHandler)
40
    , mScriptBuffer(std::move(aScriptBuffer))
41
    , mScriptLength(aScriptLength)
42
    , mWorkletType(aType)
43
    , mResult(NS_ERROR_FAILURE)
44
0
  {
45
0
    MOZ_ASSERT(NS_IsMainThread());
46
0
  }
47
48
  NS_IMETHOD
49
  Run() override;
50
51
private:
52
  void
53
  RunOnWorkletThread();
54
55
  void
56
  RunOnMainThread();
57
58
  RefPtr<WorkletFetchHandler> mHandler;
59
  JS::UniqueTwoByteChars mScriptBuffer;
60
  size_t mScriptLength;
61
  Worklet::WorkletType mWorkletType;
62
  nsresult mResult;
63
};
64
65
// ---------------------------------------------------------------------------
66
// WorkletFetchHandler
67
68
class WorkletFetchHandler final : public PromiseNativeHandler
69
                                , public nsIStreamLoaderObserver
70
{
71
public:
72
  NS_DECL_THREADSAFE_ISUPPORTS
73
74
  static already_AddRefed<Promise>
75
  Fetch(Worklet* aWorklet, const nsAString& aModuleURL,
76
        const WorkletOptions& aOptions, CallerType aCallerType,
77
        ErrorResult& aRv)
78
0
  {
79
0
    MOZ_ASSERT(aWorklet);
80
0
    MOZ_ASSERT(NS_IsMainThread());
81
0
82
0
    nsCOMPtr<nsIGlobalObject> global =
83
0
      do_QueryInterface(aWorklet->GetParentObject());
84
0
    MOZ_ASSERT(global);
85
0
86
0
    RefPtr<Promise> promise = Promise::Create(global, aRv);
87
0
    if (NS_WARN_IF(aRv.Failed())) {
88
0
      return nullptr;
89
0
    }
90
0
91
0
    nsCOMPtr<nsPIDOMWindowInner> window = aWorklet->GetParentObject();
92
0
    MOZ_ASSERT(window);
93
0
94
0
    nsCOMPtr<nsIDocument> doc;
95
0
    doc = window->GetExtantDoc();
96
0
    if (!doc) {
97
0
      promise->MaybeReject(NS_ERROR_FAILURE);
98
0
      return promise.forget();
99
0
    }
100
0
101
0
    nsCOMPtr<nsIURI> baseURI = doc->GetBaseURI();
102
0
    nsCOMPtr<nsIURI> resolvedURI;
103
0
    nsresult rv = NS_NewURI(getter_AddRefs(resolvedURI), aModuleURL, nullptr,
104
0
                            baseURI);
105
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
106
0
      promise->MaybeReject(rv);
107
0
      return promise.forget();
108
0
    }
109
0
110
0
    nsAutoCString spec;
111
0
    rv = resolvedURI->GetSpec(spec);
112
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
113
0
      promise->MaybeReject(rv);
114
0
      return promise.forget();
115
0
    }
116
0
117
0
    // Maybe we already have an handler for this URI
118
0
    {
119
0
      WorkletFetchHandler* handler = aWorklet->GetImportFetchHandler(spec);
120
0
      if (handler) {
121
0
        handler->AddPromise(promise);
122
0
        return promise.forget();
123
0
      }
124
0
    }
125
0
126
0
    RequestOrUSVString request;
127
0
    request.SetAsUSVString().Rebind(aModuleURL.Data(), aModuleURL.Length());
128
0
129
0
    RequestInit init;
130
0
    init.mCredentials.Construct(aOptions.mCredentials);
131
0
132
0
    RefPtr<Promise> fetchPromise =
133
0
      FetchRequest(global, request, init, aCallerType, aRv);
134
0
    if (NS_WARN_IF(aRv.Failed())) {
135
0
      promise->MaybeReject(aRv);
136
0
      return promise.forget();
137
0
    }
138
0
139
0
    RefPtr<WorkletFetchHandler> handler =
140
0
      new WorkletFetchHandler(aWorklet, aModuleURL, promise);
141
0
    fetchPromise->AppendNativeHandler(handler);
142
0
143
0
    aWorklet->AddImportFetchHandler(spec, handler);
144
0
    return promise.forget();
145
0
  }
146
147
  virtual void
148
  ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
149
0
  {
150
0
    MOZ_ASSERT(NS_IsMainThread());
151
0
152
0
    if (!aValue.isObject()) {
153
0
      RejectPromises(NS_ERROR_FAILURE);
154
0
      return;
155
0
    }
156
0
157
0
    RefPtr<Response> response;
158
0
    nsresult rv = UNWRAP_OBJECT(Response, &aValue.toObject(), response);
159
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
160
0
      RejectPromises(NS_ERROR_FAILURE);
161
0
      return;
162
0
    }
163
0
164
0
    if (!response->Ok()) {
165
0
      RejectPromises(NS_ERROR_DOM_NETWORK_ERR);
166
0
      return;
167
0
    }
168
0
169
0
    nsCOMPtr<nsIInputStream> inputStream;
170
0
    response->GetBody(getter_AddRefs(inputStream));
171
0
    if (!inputStream) {
172
0
      RejectPromises(NS_ERROR_DOM_NETWORK_ERR);
173
0
      return;
174
0
    }
175
0
176
0
    nsCOMPtr<nsIInputStreamPump> pump;
177
0
    rv = NS_NewInputStreamPump(getter_AddRefs(pump), inputStream.forget());
178
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
179
0
      RejectPromises(rv);
180
0
      return;
181
0
    }
182
0
183
0
    nsCOMPtr<nsIStreamLoader> loader;
184
0
    rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
185
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
186
0
      RejectPromises(rv);
187
0
      return;
188
0
    }
189
0
190
0
    rv = pump->AsyncRead(loader, nullptr);
191
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
192
0
      RejectPromises(rv);
193
0
      return;
194
0
    }
195
0
196
0
    nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(pump);
197
0
    if (rr) {
198
0
      nsCOMPtr<nsIEventTarget> sts =
199
0
        do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
200
0
      rv = rr->RetargetDeliveryTo(sts);
201
0
      if (NS_FAILED(rv)) {
202
0
        NS_WARNING("Failed to dispatch the nsIInputStreamPump to a IO thread.");
203
0
      }
204
0
    }
205
0
  }
206
207
  NS_IMETHOD
208
  OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
209
                   nsresult aStatus, uint32_t aStringLen,
210
                   const uint8_t* aString) override
211
0
  {
212
0
    MOZ_ASSERT(NS_IsMainThread());
213
0
214
0
    if (NS_FAILED(aStatus)) {
215
0
      RejectPromises(aStatus);
216
0
      return NS_OK;
217
0
    }
218
0
219
0
    JS::UniqueTwoByteChars scriptTextBuf;
220
0
    size_t scriptTextLength;
221
0
    nsresult rv =
222
0
      ScriptLoader::ConvertToUTF16(nullptr, aString, aStringLen,
223
0
                                   NS_LITERAL_STRING("UTF-8"), nullptr,
224
0
                                   scriptTextBuf, scriptTextLength);
225
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
226
0
      RejectPromises(rv);
227
0
      return NS_OK;
228
0
    }
229
0
230
0
    // Moving the ownership of the buffer
231
0
    nsCOMPtr<nsIRunnable> runnable =
232
0
      new ExecutionRunnable(this, mWorklet->Type(), std::move(scriptTextBuf),
233
0
                            scriptTextLength, mWorklet->LoadInfo());
234
0
235
0
    RefPtr<WorkletThread> thread = mWorklet->GetOrCreateThread();
236
0
    if (!thread) {
237
0
      RejectPromises(NS_ERROR_FAILURE);
238
0
      return NS_OK;
239
0
    }
240
0
241
0
    if (NS_FAILED(thread->DispatchRunnable(runnable.forget()))) {
242
0
      RejectPromises(NS_ERROR_FAILURE);
243
0
      return NS_OK;
244
0
    }
245
0
246
0
    return NS_OK;
247
0
  }
248
249
  virtual void
250
  RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
251
0
  {
252
0
    MOZ_ASSERT(NS_IsMainThread());
253
0
    RejectPromises(NS_ERROR_DOM_NETWORK_ERR);
254
0
  }
255
256
  const nsString& URL() const
257
0
  {
258
0
    return mURL;
259
0
  }
260
261
  void
262
  ExecutionFailed(nsresult aRv)
263
0
  {
264
0
    MOZ_ASSERT(NS_IsMainThread());
265
0
    RejectPromises(aRv);
266
0
  }
267
268
  void
269
  ExecutionSucceeded()
270
0
  {
271
0
    MOZ_ASSERT(NS_IsMainThread());
272
0
    ResolvePromises();
273
0
  }
274
275
private:
276
  WorkletFetchHandler(Worklet* aWorklet, const nsAString& aURL,
277
                      Promise* aPromise)
278
    : mWorklet(aWorklet)
279
    , mStatus(ePending)
280
    , mErrorStatus(NS_OK)
281
    , mURL(aURL)
282
0
  {
283
0
    MOZ_ASSERT(aWorklet);
284
0
    MOZ_ASSERT(aPromise);
285
0
    MOZ_ASSERT(NS_IsMainThread());
286
0
287
0
    mPromises.AppendElement(aPromise);
288
0
  }
289
290
  ~WorkletFetchHandler()
291
0
  {}
292
293
  void
294
  AddPromise(Promise* aPromise)
295
0
  {
296
0
    MOZ_ASSERT(aPromise);
297
0
    MOZ_ASSERT(NS_IsMainThread());
298
0
299
0
    switch (mStatus) {
300
0
      case ePending:
301
0
        mPromises.AppendElement(aPromise);
302
0
        return;
303
0
304
0
      case eRejected:
305
0
        MOZ_ASSERT(NS_FAILED(mErrorStatus));
306
0
        aPromise->MaybeReject(mErrorStatus);
307
0
        return;
308
0
309
0
      case eResolved:
310
0
        aPromise->MaybeResolveWithUndefined();
311
0
        return;
312
0
    }
313
0
  }
314
315
  void
316
  RejectPromises(nsresult aResult)
317
0
  {
318
0
    MOZ_ASSERT(mStatus == ePending);
319
0
    MOZ_ASSERT(NS_FAILED(aResult));
320
0
    MOZ_ASSERT(NS_IsMainThread());
321
0
322
0
    for (uint32_t i = 0; i < mPromises.Length(); ++i) {
323
0
      mPromises[i]->MaybeReject(aResult);
324
0
    }
325
0
    mPromises.Clear();
326
0
327
0
    mStatus = eRejected;
328
0
    mErrorStatus = aResult;
329
0
    mWorklet = nullptr;
330
0
  }
331
332
  void
333
  ResolvePromises()
334
0
  {
335
0
    MOZ_ASSERT(mStatus == ePending);
336
0
    MOZ_ASSERT(NS_IsMainThread());
337
0
338
0
    for (uint32_t i = 0; i < mPromises.Length(); ++i) {
339
0
      mPromises[i]->MaybeResolveWithUndefined();
340
0
    }
341
0
    mPromises.Clear();
342
0
343
0
    mStatus = eResolved;
344
0
    mWorklet = nullptr;
345
0
  }
346
347
  RefPtr<Worklet> mWorklet;
348
  nsTArray<RefPtr<Promise>> mPromises;
349
350
  enum {
351
    ePending,
352
    eRejected,
353
    eResolved
354
  } mStatus;
355
356
  nsresult mErrorStatus;
357
358
  nsString mURL;
359
};
360
361
NS_IMPL_ISUPPORTS(WorkletFetchHandler, nsIStreamLoaderObserver)
362
363
NS_IMETHODIMP
364
ExecutionRunnable::Run()
365
0
{
366
0
  if (WorkletThread::IsOnWorkletThread()) {
367
0
    RunOnWorkletThread();
368
0
    return NS_DispatchToMainThread(this);
369
0
  }
370
0
371
0
  MOZ_ASSERT(NS_IsMainThread());
372
0
  RunOnMainThread();
373
0
  return NS_OK;
374
0
}
375
376
void
377
ExecutionRunnable::RunOnWorkletThread()
378
0
{
379
0
  WorkletThread::AssertIsOnWorkletThread();
380
0
381
0
  WorkletThread* workletThread = WorkletThread::Get();
382
0
  MOZ_ASSERT(workletThread);
383
0
384
0
  JSContext* cx = workletThread->GetJSContext();
385
0
386
0
  AutoJSAPI jsapi;
387
0
  jsapi.Init();
388
0
389
0
  RefPtr<WorkletGlobalScope> globalScope =
390
0
    Worklet::CreateGlobalScope(jsapi.cx(), mWorkletType);
391
0
  MOZ_ASSERT(globalScope);
392
0
393
0
  AutoEntryScript aes(globalScope, "Worklet");
394
0
  cx = aes.cx();
395
0
396
0
  JS::Rooted<JSObject*> globalObj(cx, globalScope->GetGlobalJSObject());
397
0
398
0
  NS_ConvertUTF16toUTF8 url(mHandler->URL());
399
0
400
0
  JS::CompileOptions compileOptions(cx);
401
0
  compileOptions.setIntroductionType("Worklet");
402
0
  compileOptions.setFileAndLine(url.get(), 0);
403
0
  compileOptions.setIsRunOnce(true);
404
0
  compileOptions.setNoScriptRval(true);
405
0
406
0
  JSAutoRealm ar(cx, globalObj);
407
0
408
0
  JS::SourceBufferHolder buffer(mScriptBuffer.release(), mScriptLength,
409
0
                                JS::SourceBufferHolder::GiveOwnership);
410
0
  JS::Rooted<JS::Value> unused(cx);
411
0
  if (!JS::Evaluate(cx, compileOptions, buffer, &unused)) {
412
0
    ErrorResult error;
413
0
    error.MightThrowJSException();
414
0
    error.StealExceptionFromJSContext(cx);
415
0
    mResult = error.StealNSResult();
416
0
    return;
417
0
  }
418
0
419
0
  // All done.
420
0
  mResult = NS_OK;
421
0
}
422
423
void
424
ExecutionRunnable::RunOnMainThread()
425
0
{
426
0
  MOZ_ASSERT(NS_IsMainThread());
427
0
428
0
  if (NS_FAILED(mResult)) {
429
0
    mHandler->ExecutionFailed(mResult);
430
0
    return;
431
0
  }
432
0
433
0
  mHandler->ExecutionSucceeded();
434
0
}
435
436
// ---------------------------------------------------------------------------
437
// WorkletLoadInfo
438
439
WorkletLoadInfo::WorkletLoadInfo(nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal)
440
  : mInnerWindowID(aWindow->WindowID())
441
  , mDumpEnabled(DOMPrefs::DumpEnabled())
442
  , mOriginAttributes(BasePrincipal::Cast(aPrincipal)->OriginAttributesRef())
443
  , mPrincipal(aPrincipal)
444
0
{
445
0
  MOZ_ASSERT(NS_IsMainThread());
446
0
  nsPIDOMWindowOuter* outerWindow = aWindow->GetOuterWindow();
447
0
  if (outerWindow) {
448
0
    mOuterWindowID = outerWindow->WindowID();
449
0
  } else {
450
0
    mOuterWindowID = 0;
451
0
  }
452
0
}
453
454
WorkletLoadInfo::~WorkletLoadInfo()
455
0
{
456
0
  MOZ_ASSERT(NS_IsMainThread());
457
0
}
458
459
// ---------------------------------------------------------------------------
460
// Worklet
461
462
NS_IMPL_CYCLE_COLLECTION_CLASS(Worklet)
463
464
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Worklet)
465
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
466
0
  tmp->TerminateThread();
467
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
468
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
469
470
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Worklet)
471
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
472
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
473
474
NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(Worklet)
475
476
NS_IMPL_CYCLE_COLLECTING_ADDREF(Worklet)
477
NS_IMPL_CYCLE_COLLECTING_RELEASE(Worklet)
478
479
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Worklet)
480
0
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
481
0
  NS_INTERFACE_MAP_ENTRY(nsISupports)
482
0
NS_INTERFACE_MAP_END
483
484
Worklet::Worklet(nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal,
485
                 WorkletType aWorkletType)
486
  : mWindow(aWindow)
487
  , mWorkletType(aWorkletType)
488
  , mWorkletLoadInfo(aWindow, aPrincipal)
489
0
{
490
0
  MOZ_ASSERT(aWindow);
491
0
  MOZ_ASSERT(aPrincipal);
492
0
  MOZ_ASSERT(NS_IsMainThread());
493
0
494
#ifdef RELEASE_OR_BETA
495
  MOZ_CRASH("This code should not go to release/beta yet!");
496
#endif
497
}
498
499
Worklet::~Worklet()
500
0
{
501
0
  TerminateThread();
502
0
}
503
504
JSObject*
505
Worklet::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
506
0
{
507
0
  MOZ_ASSERT(NS_IsMainThread());
508
0
  if (mWorkletType == eAudioWorklet) {
509
0
    return AudioWorklet_Binding::Wrap(aCx, this, aGivenProto);
510
0
  } else {
511
0
    return Worklet_Binding::Wrap(aCx, this, aGivenProto);
512
0
  }
513
0
}
514
515
already_AddRefed<Promise>
516
Worklet::AddModule(const nsAString& aModuleURL,
517
                   const WorkletOptions& aOptions,
518
                   CallerType aCallerType,
519
                   ErrorResult& aRv)
520
0
{
521
0
  MOZ_ASSERT(NS_IsMainThread());
522
0
  return WorkletFetchHandler::Fetch(this, aModuleURL, aOptions, aCallerType, aRv);
523
0
}
524
525
/* static */ already_AddRefed<WorkletGlobalScope>
526
Worklet::CreateGlobalScope(JSContext* aCx, WorkletType aWorkletType)
527
0
{
528
0
  WorkletThread::AssertIsOnWorkletThread();
529
0
530
0
  RefPtr<WorkletGlobalScope> scope;
531
0
532
0
  switch (aWorkletType) {
533
0
    case eAudioWorklet:
534
0
      scope = new AudioWorkletGlobalScope();
535
0
      break;
536
0
    case ePaintWorklet:
537
0
      scope = new PaintWorkletGlobalScope();
538
0
      break;
539
0
  }
540
0
541
0
  JS::Rooted<JSObject*> global(aCx);
542
0
  NS_ENSURE_TRUE(scope->WrapGlobalObject(aCx, &global), nullptr);
543
0
544
0
  JSAutoRealm ar(aCx, global);
545
0
546
0
  // Init Web IDL bindings
547
0
  if (!RegisterWorkletBindings(aCx, global)) {
548
0
    return nullptr;
549
0
  }
550
0
551
0
  JS_FireOnNewGlobalObject(aCx, global);
552
0
553
0
  return scope.forget();
554
0
}
555
556
WorkletFetchHandler*
557
Worklet::GetImportFetchHandler(const nsACString& aURI)
558
0
{
559
0
  MOZ_ASSERT(NS_IsMainThread());
560
0
  return mImportHandlers.GetWeak(aURI);
561
0
}
562
563
void
564
Worklet::AddImportFetchHandler(const nsACString& aURI,
565
                               WorkletFetchHandler* aHandler)
566
0
{
567
0
  MOZ_ASSERT(aHandler);
568
0
  MOZ_ASSERT(!mImportHandlers.GetWeak(aURI));
569
0
  MOZ_ASSERT(NS_IsMainThread());
570
0
571
0
  mImportHandlers.Put(aURI, aHandler);
572
0
}
573
574
WorkletThread*
575
Worklet::GetOrCreateThread()
576
0
{
577
0
  MOZ_ASSERT(NS_IsMainThread());
578
0
579
0
  if (!mWorkletThread) {
580
0
    // Thread creation. FIXME: this will change.
581
0
    mWorkletThread = WorkletThread::Create(mWorkletLoadInfo);
582
0
  }
583
0
584
0
  return mWorkletThread;
585
0
}
586
587
void
588
Worklet::TerminateThread()
589
0
{
590
0
  MOZ_ASSERT(NS_IsMainThread());
591
0
  if (!mWorkletThread) {
592
0
    return;
593
0
  }
594
0
595
0
  mWorkletThread->Terminate();
596
0
  mWorkletThread = nullptr;
597
0
  mWorkletLoadInfo.mPrincipal = nullptr;
598
0
}
599
600
} // dom namespace
601
} // mozilla namespace