tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

WorkletModuleLoader.cpp (12038B)


      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 "WorkletModuleLoader.h"
      8 
      9 #include "js/CompileOptions.h"  // JS::InstantiateOptions
     10 #include "js/experimental/JSStencil.h"  // JS::CompileModuleScriptToStencil, JS::InstantiateModuleStencil
     11 #include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_*
     12 #include "js/loader/ModuleLoadRequest.h"
     13 #include "mozilla/ScopeExit.h"
     14 #include "mozilla/StaticPrefs_javascript.h"
     15 #include "mozilla/dom/StructuredCloneHolder.h"
     16 #include "mozilla/dom/Worklet.h"
     17 #include "mozilla/dom/WorkletFetchHandler.h"
     18 #include "nsStringBundle.h"
     19 
     20 using JS::loader::ModuleLoadRequest;
     21 using JS::loader::ResolveError;
     22 
     23 namespace mozilla::dom::loader {
     24 
     25 //////////////////////////////////////////////////////////////
     26 // WorkletScriptLoader
     27 //////////////////////////////////////////////////////////////
     28 
     29 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WorkletScriptLoader)
     30 NS_INTERFACE_MAP_END
     31 
     32 NS_IMPL_CYCLE_COLLECTION(WorkletScriptLoader)
     33 
     34 NS_IMPL_CYCLE_COLLECTING_ADDREF(WorkletScriptLoader)
     35 NS_IMPL_CYCLE_COLLECTING_RELEASE(WorkletScriptLoader)
     36 
     37 nsresult WorkletScriptLoader::FillCompileOptionsForRequest(
     38    JSContext* cx, ScriptLoadRequest* aRequest, JS::CompileOptions* aOptions,
     39    JS::MutableHandle<JSScript*> aIntroductionScript) {
     40  aOptions->setIntroductionType("Worklet");
     41  aOptions->setFileAndLine(aRequest->mURL.get(), 1);
     42  aOptions->setIsRunOnce(true);
     43  aOptions->setNoScriptRval(true);
     44  return NS_OK;
     45 }
     46 
     47 //////////////////////////////////////////////////////////////
     48 // WorkletModuleLoader
     49 //////////////////////////////////////////////////////////////
     50 
     51 NS_IMPL_ADDREF_INHERITED(WorkletModuleLoader, ModuleLoaderBase)
     52 NS_IMPL_RELEASE_INHERITED(WorkletModuleLoader, ModuleLoaderBase)
     53 
     54 NS_IMPL_CYCLE_COLLECTION_INHERITED(WorkletModuleLoader, ModuleLoaderBase,
     55                                   mFetchingRequests)
     56 
     57 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WorkletModuleLoader)
     58 NS_INTERFACE_MAP_END_INHERITING(ModuleLoaderBase)
     59 
     60 WorkletModuleLoader::WorkletModuleLoader(WorkletScriptLoader* aScriptLoader,
     61                                         nsIGlobalObject* aGlobalObject)
     62    : ModuleLoaderBase(aScriptLoader, aGlobalObject) {
     63  // This should be constructed on a worklet thread.
     64  MOZ_ASSERT(!NS_IsMainThread());
     65 }
     66 
     67 already_AddRefed<ModuleLoadRequest> WorkletModuleLoader::CreateRequest(
     68    JSContext* aCx, nsIURI* aURI, JS::Handle<JSObject*> aModuleRequest,
     69    JS::Handle<JS::Value> aHostDefined, JS::Handle<JS::Value> aPayload,
     70    bool aIsDynamicImport, ScriptFetchOptions* aOptions,
     71    dom::ReferrerPolicy aReferrerPolicy, nsIURI* aBaseURL,
     72    const dom::SRIMetadata& aSriMetadata) {
     73  JS::ModuleType moduleType = GetModuleRequestType(aCx, aModuleRequest);
     74  ModuleLoadRequest* root = nullptr;
     75  MOZ_ASSERT(!aHostDefined.isUndefined());
     76  root = static_cast<ModuleLoadRequest*>(aHostDefined.toPrivate());
     77  MOZ_ASSERT(root);
     78  WorkletLoadContext* context = root->mLoadContext->AsWorkletContext();
     79  const nsMainThreadPtrHandle<WorkletFetchHandler>& handlerRef =
     80      context->GetHandlerRef();
     81  RefPtr<WorkletLoadContext> loadContext = new WorkletLoadContext(handlerRef);
     82  RefPtr<ModuleLoadRequest> request =
     83      new ModuleLoadRequest(moduleType, SRIMetadata(), aBaseURL, loadContext,
     84                            ModuleLoadRequest::Kind::StaticImport, this, root);
     85 
     86  request->mURL = aURI->GetSpecOrDefault();
     87  request->NoCacheEntryFound(aReferrerPolicy, aOptions, aURI);
     88  return request.forget();
     89 }
     90 
     91 bool WorkletModuleLoader::CanStartLoad(ModuleLoadRequest* aRequest,
     92                                       nsresult* aRvOut) {
     93  return true;
     94 }
     95 
     96 nsresult WorkletModuleLoader::StartFetch(ModuleLoadRequest* aRequest) {
     97  InsertRequest(aRequest->URI(), aRequest);
     98 
     99  RefPtr<StartFetchRunnable> runnable =
    100      new StartFetchRunnable(aRequest->GetWorkletLoadContext()->GetHandlerRef(),
    101                             aRequest->URI(), aRequest->mReferrer);
    102  NS_DispatchToMainThread(runnable.forget());
    103  return NS_OK;
    104 }
    105 
    106 nsresult WorkletModuleLoader::CompileFetchedModule(
    107    JSContext* aCx, JS::Handle<JSObject*> aGlobal, JS::CompileOptions& aOptions,
    108    ModuleLoadRequest* aRequest, JS::MutableHandle<JSObject*> aModuleScript) {
    109  switch (aRequest->mModuleType) {
    110    case JS::ModuleType::Unknown:
    111    case JS::ModuleType::Bytes:
    112      MOZ_CRASH("Unexpected module type");
    113    case JS::ModuleType::JavaScriptOrWasm:
    114      return CompileJavaScriptOrWasmModule(aCx, aOptions, aRequest,
    115                                           aModuleScript);
    116    case JS::ModuleType::JSON:
    117      return CompileJsonModule(aCx, aOptions, aRequest, aModuleScript);
    118    case JS::ModuleType::CSS:
    119      MOZ_CRASH("CSS modules are not supported in worklets");
    120  }
    121 
    122  MOZ_CRASH("Unhandled module type");
    123 }
    124 
    125 nsresult WorkletModuleLoader::CompileJavaScriptOrWasmModule(
    126    JSContext* aCx, JS::CompileOptions& aOptions, ModuleLoadRequest* aRequest,
    127    JS::MutableHandle<JSObject*> aModuleScript) {
    128  MOZ_ASSERT(aRequest->IsTextSource());
    129 
    130  MaybeSourceText maybeSource;
    131  nsresult rv = aRequest->GetScriptSource(aCx, &maybeSource,
    132                                          aRequest->mLoadContext.get());
    133  NS_ENSURE_SUCCESS(rv, rv);
    134 
    135 #ifdef NIGHTLY_BUILD
    136  if (aRequest->HasWasmMimeTypeEssence()) {
    137    auto compile = [&](auto& source) {
    138      return JS::CompileWasmModule(aCx, aOptions, source);
    139    };
    140 
    141    auto* wasmModule = maybeSource.mapNonEmpty(compile);
    142    if (!wasmModule) {
    143      return NS_ERROR_FAILURE;
    144    }
    145 
    146    aModuleScript.set(wasmModule);
    147    return NS_OK;
    148  }
    149 #endif
    150 
    151  RefPtr<JS::Stencil> stencil;
    152 
    153  auto compile = [&](auto& source) {
    154    return JS::CompileModuleScriptToStencil(aCx, aOptions, source);
    155  };
    156  stencil = maybeSource.mapNonEmpty(compile);
    157 
    158  if (!stencil) {
    159    return NS_ERROR_FAILURE;
    160  }
    161 
    162  JS::InstantiateOptions instantiateOptions(aOptions);
    163  aModuleScript.set(
    164      JS::InstantiateModuleStencil(aCx, instantiateOptions, stencil));
    165  return aModuleScript ? NS_OK : NS_ERROR_FAILURE;
    166 }
    167 
    168 nsresult WorkletModuleLoader::CompileJsonModule(
    169    JSContext* aCx, JS::CompileOptions& aOptions, ModuleLoadRequest* aRequest,
    170    JS::MutableHandle<JSObject*> aModuleScript) {
    171  MOZ_ASSERT(aRequest->IsTextSource());
    172 
    173  MaybeSourceText maybeSource;
    174  nsresult rv = aRequest->GetScriptSource(aCx, &maybeSource,
    175                                          aRequest->mLoadContext.get());
    176  NS_ENSURE_SUCCESS(rv, rv);
    177 
    178  auto compile = [&](auto& source) {
    179    return JS::CompileJsonModule(aCx, aOptions, source);
    180  };
    181 
    182  auto* jsonModule = maybeSource.mapNonEmpty(compile);
    183  if (!jsonModule) {
    184    return NS_ERROR_FAILURE;
    185  }
    186 
    187  aModuleScript.set(jsonModule);
    188  return NS_OK;
    189 }
    190 
    191 // AddModuleResultRunnable is a Runnable which will notify the result of
    192 // Worklet::AddModule on the main thread.
    193 class AddModuleResultRunnable final : public Runnable {
    194 public:
    195  explicit AddModuleResultRunnable(
    196      const nsMainThreadPtrHandle<WorkletFetchHandler>& aHandlerRef,
    197      bool aSucceeded)
    198      : Runnable("Worklet::AddModuleResultRunnable"),
    199        mHandlerRef(aHandlerRef),
    200        mSucceeded(aSucceeded) {
    201    MOZ_ASSERT(!NS_IsMainThread());
    202  }
    203 
    204  ~AddModuleResultRunnable() = default;
    205 
    206  NS_IMETHOD
    207  Run() override;
    208 
    209 private:
    210  nsMainThreadPtrHandle<WorkletFetchHandler> mHandlerRef;
    211  bool mSucceeded;
    212 };
    213 
    214 NS_IMETHODIMP
    215 AddModuleResultRunnable::Run() {
    216  MOZ_ASSERT(NS_IsMainThread());
    217  if (mSucceeded) {
    218    mHandlerRef->ExecutionSucceeded();
    219  } else {
    220    mHandlerRef->ExecutionFailed();
    221  }
    222 
    223  return NS_OK;
    224 }
    225 
    226 class AddModuleThrowErrorRunnable final : public Runnable,
    227                                          public StructuredCloneHolder {
    228 public:
    229  explicit AddModuleThrowErrorRunnable(
    230      const nsMainThreadPtrHandle<WorkletFetchHandler>& aHandlerRef)
    231      : Runnable("Worklet::AddModuleThrowErrorRunnable"),
    232        StructuredCloneHolder(CloningSupported, TransferringNotSupported,
    233                              StructuredCloneScope::SameProcess),
    234        mHandlerRef(aHandlerRef) {
    235    MOZ_ASSERT(!NS_IsMainThread());
    236  }
    237 
    238  ~AddModuleThrowErrorRunnable() = default;
    239 
    240  NS_IMETHOD
    241  Run() override;
    242 
    243 private:
    244  nsMainThreadPtrHandle<WorkletFetchHandler> mHandlerRef;
    245 };
    246 
    247 NS_IMETHODIMP
    248 AddModuleThrowErrorRunnable::Run() {
    249  MOZ_ASSERT(NS_IsMainThread());
    250 
    251  nsCOMPtr<nsIGlobalObject> global =
    252      do_QueryInterface(mHandlerRef->mWorklet->GetParentObject());
    253  MOZ_ASSERT(global);
    254 
    255  AutoJSAPI jsapi;
    256  if (NS_WARN_IF(!jsapi.Init(global))) {
    257    mHandlerRef->ExecutionFailed();
    258    return NS_ERROR_FAILURE;
    259  }
    260 
    261  JSContext* cx = jsapi.cx();
    262  JS::Rooted<JS::Value> error(cx);
    263  ErrorResult result;
    264  Read(global, cx, &error, result);
    265  (void)NS_WARN_IF(result.Failed());
    266  mHandlerRef->ExecutionFailed(error);
    267 
    268  return NS_OK;
    269 }
    270 
    271 void WorkletModuleLoader::OnModuleLoadComplete(ModuleLoadRequest* aRequest) {
    272  if (aRequest->IsStaticImport()) {
    273    return;
    274  }
    275 
    276  const nsMainThreadPtrHandle<WorkletFetchHandler>& handlerRef =
    277      aRequest->GetWorkletLoadContext()->GetHandlerRef();
    278 
    279  auto addModuleFailed = MakeScopeExit([&] {
    280    RefPtr<AddModuleResultRunnable> runnable =
    281        new AddModuleResultRunnable(handlerRef, false);
    282    NS_DispatchToMainThread(runnable.forget());
    283  });
    284 
    285  if (!aRequest->mModuleScript) {
    286    return;
    287  }
    288 
    289  bool hasParseError = aRequest->mModuleScript->HasParseError();
    290  bool hasError = aRequest->mModuleScript->HasErrorToRethrow();
    291 
    292  if (!hasParseError && !hasError) {
    293    if (!aRequest->InstantiateModuleGraph()) {
    294      return;
    295    }
    296 
    297    nsresult rv = aRequest->EvaluateModule();
    298    if (NS_FAILED(rv)) {
    299      return;
    300    }
    301 
    302    hasError = aRequest->mModuleScript->HasErrorToRethrow();
    303  }
    304 
    305  if (hasParseError || hasError) {
    306    AutoJSAPI jsapi;
    307    if (NS_WARN_IF(!jsapi.Init(GetGlobalObject()))) {
    308      return;
    309    }
    310 
    311    JSContext* cx = jsapi.cx();
    312    JS::Rooted<JS::Value> error(cx);
    313    if (hasParseError) {
    314      error = aRequest->mModuleScript->ParseError();
    315    } else {
    316      error = aRequest->mModuleScript->ErrorToRethrow();
    317    }
    318    JS_SetPendingException(cx, error);
    319    RefPtr<AddModuleThrowErrorRunnable> runnable =
    320        new AddModuleThrowErrorRunnable(handlerRef);
    321    ErrorResult result;
    322    runnable->Write(cx, error, result);
    323    if (NS_WARN_IF(result.Failed())) {
    324      return;
    325    }
    326 
    327    addModuleFailed.release();
    328    NS_DispatchToMainThread(runnable.forget());
    329    return;
    330  }
    331 
    332  addModuleFailed.release();
    333  RefPtr<AddModuleResultRunnable> runnable =
    334      new AddModuleResultRunnable(handlerRef, true);
    335  NS_DispatchToMainThread(runnable.forget());
    336 }
    337 
    338 // TODO: Bug 1808301: Call FormatLocalizedString from a worklet thread.
    339 nsresult WorkletModuleLoader::GetResolveFailureMessage(
    340    ResolveError aError, const nsAString& aSpecifier, nsAString& aResult) {
    341  uint8_t index = static_cast<uint8_t>(aError);
    342  MOZ_ASSERT(index < static_cast<uint8_t>(ResolveError::Length));
    343  MOZ_ASSERT(mLocalizedStrs);
    344  MOZ_ASSERT(!mLocalizedStrs->IsEmpty());
    345  if (!mLocalizedStrs || NS_WARN_IF(mLocalizedStrs->IsEmpty())) {
    346    return NS_ERROR_FAILURE;
    347  }
    348 
    349  const nsString& localizedStr = mLocalizedStrs->ElementAt(index);
    350 
    351  AutoTArray<nsString, 1> params;
    352  params.AppendElement(aSpecifier);
    353 
    354  nsStringBundleBase::FormatString(localizedStr.get(), params, aResult);
    355  return NS_OK;
    356 }
    357 
    358 void WorkletModuleLoader::InsertRequest(nsIURI* aURI,
    359                                        ModuleLoadRequest* aRequest) {
    360  mFetchingRequests.InsertOrUpdate(aURI, aRequest);
    361 }
    362 
    363 void WorkletModuleLoader::RemoveRequest(nsIURI* aURI) {
    364  MOZ_ASSERT(mFetchingRequests.Remove(aURI));
    365 }
    366 
    367 ModuleLoadRequest* WorkletModuleLoader::GetRequest(nsIURI* aURI) const {
    368  RefPtr<ModuleLoadRequest> req;
    369  MOZ_ALWAYS_TRUE(mFetchingRequests.Get(aURI, getter_AddRefs(req)));
    370  return req;
    371 }
    372 
    373 }  // namespace mozilla::dom::loader