tor-browser

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

ModuleLoader.cpp (23369B)


      1 /* -*- Mode: javascript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4
      2 * -*- */
      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 "shell/ModuleLoader.h"
      8 
      9 #include "mozilla/TextUtils.h"
     10 
     11 #include "jsapi.h"
     12 #include "NamespaceImports.h"
     13 
     14 #include "builtin/TestingUtility.h"  // js::CreateScriptPrivate
     15 #include "js/Conversions.h"
     16 #include "js/MapAndSet.h"
     17 #include "js/Modules.h"
     18 #include "js/PropertyAndElement.h"  // JS_DefineProperty, JS_GetProperty
     19 #include "js/SourceText.h"
     20 #include "js/StableStringChars.h"
     21 #include "shell/jsshell.h"
     22 #include "shell/OSObject.h"
     23 #include "shell/StringUtils.h"
     24 #include "util/Text.h"
     25 #include "vm/JSAtomUtils.h"  // AtomizeString, PinAtom
     26 #include "vm/JSContext.h"
     27 #include "vm/StringType.h"
     28 
     29 #include "vm/NativeObject-inl.h"
     30 
     31 using namespace js;
     32 using namespace js::shell;
     33 
     34 static constexpr char16_t JavaScriptScheme[] = u"javascript:";
     35 
     36 static bool IsJavaScriptURL(Handle<JSLinearString*> path) {
     37  return StringStartsWith(path, JavaScriptScheme);
     38 }
     39 
     40 static JSString* ExtractJavaScriptURLSource(JSContext* cx,
     41                                            Handle<JSLinearString*> path) {
     42  MOZ_ASSERT(IsJavaScriptURL(path));
     43 
     44  const size_t schemeLength = js_strlen(JavaScriptScheme);
     45  return SubString(cx, path, schemeLength);
     46 }
     47 
     48 bool ModuleLoader::init(JSContext* cx, HandleString loadPath) {
     49  loadPathStr = AtomizeString(cx, loadPath);
     50  if (!loadPathStr || !PinAtom(cx, loadPathStr)) {
     51    return false;
     52  }
     53 
     54  MOZ_ASSERT(IsAbsolutePath(loadPathStr));
     55 
     56  char16_t sep = PathSeparator;
     57  pathSeparatorStr = AtomizeChars(cx, &sep, 1);
     58  if (!pathSeparatorStr || !PinAtom(cx, pathSeparatorStr)) {
     59    return false;
     60  }
     61 
     62  JSRuntime* rt = cx->runtime();
     63  JS::SetModuleLoadHook(rt, ModuleLoader::LoadImportedModule);
     64  JS::SetModuleMetadataHook(rt, ModuleLoader::GetImportMetaProperties);
     65  return true;
     66 }
     67 
     68 // static
     69 bool ModuleLoader::LoadImportedModule(JSContext* cx,
     70                                      JS::Handle<JSScript*> referrer,
     71                                      JS::Handle<JSObject*> moduleRequest,
     72                                      JS::HandleValue hostDefined,
     73                                      JS::HandleValue payload,
     74                                      uint32_t lineNumber,
     75                                      JS::ColumnNumberOneOrigin columnNumber) {
     76  ShellContext* scx = GetShellContext(cx);
     77  return scx->moduleLoader->loadImportedModule(cx, referrer, moduleRequest,
     78                                               payload);
     79 }
     80 
     81 // static
     82 bool ModuleLoader::GetImportMetaProperties(JSContext* cx,
     83                                           JS::HandleValue privateValue,
     84                                           JS::HandleObject metaObject) {
     85  ShellContext* scx = GetShellContext(cx);
     86  return scx->moduleLoader->populateImportMeta(cx, privateValue, metaObject);
     87 }
     88 
     89 bool ModuleLoader::ImportMetaResolve(JSContext* cx, unsigned argc, Value* vp) {
     90  CallArgs args = CallArgsFromVp(argc, vp);
     91  RootedValue modulePrivate(
     92      cx, js::GetFunctionNativeReserved(&args.callee(), ModulePrivateSlot));
     93 
     94  // https://html.spec.whatwg.org/#hostgetimportmetaproperties
     95  // Step 4.1. Set specifier to ? ToString(specifier).
     96  //
     97  // https://tc39.es/ecma262/#sec-tostring
     98  RootedValue v(cx, args.get(ImportMetaResolveSpecifierArg));
     99  RootedString specifier(cx, JS::ToString(cx, v));
    100  if (!specifier) {
    101    return false;
    102  }
    103 
    104  // Step 4.2, 4.3 are implemented in importMetaResolve.
    105  ShellContext* scx = GetShellContext(cx);
    106  RootedString url(cx);
    107  if (!scx->moduleLoader->importMetaResolve(cx, modulePrivate, specifier,
    108                                            &url)) {
    109    return false;
    110  }
    111 
    112  // Step 4.4. Return the serialization of url.
    113  args.rval().setString(url);
    114  return true;
    115 }
    116 
    117 bool ModuleLoader::loadRootModule(JSContext* cx, HandleString path) {
    118  RootedValue rval(cx);
    119  if (!loadAndExecute(cx, path, nullptr, &rval)) {
    120    return false;
    121  }
    122 
    123  RootedObject evaluationPromise(cx, &rval.toObject());
    124  if (evaluationPromise == nullptr) {
    125    return false;
    126  }
    127 
    128  return JS::ThrowOnModuleEvaluationFailure(cx, evaluationPromise);
    129 }
    130 
    131 bool ModuleLoader::registerTestModule(JSContext* cx, HandleObject moduleRequest,
    132                                      Handle<ModuleObject*> module) {
    133  Rooted<JSLinearString*> path(cx, resolve(cx, moduleRequest, nullptr));
    134  if (!path) {
    135    return false;
    136  }
    137 
    138  path = normalizePath(cx, path);
    139  if (!path) {
    140    return false;
    141  }
    142 
    143  JS::ModuleType moduleType =
    144      moduleRequest->as<ModuleRequestObject>().moduleType();
    145 
    146  return addModuleToRegistry(cx, moduleType, path, module);
    147 }
    148 
    149 void ModuleLoader::clearModules(JSContext* cx) {
    150  Handle<GlobalObject*> global = cx->global();
    151  global->setReservedSlot(GlobalAppSlotModuleRegistry, UndefinedValue());
    152 }
    153 
    154 bool ModuleLoader::loadAndExecute(JSContext* cx, HandleString path,
    155                                  HandleObject moduleRequestArg,
    156                                  MutableHandleValue rval) {
    157  RootedObject module(cx, loadAndParse(cx, path, moduleRequestArg));
    158  if (!module) {
    159    return false;
    160  }
    161 
    162  return loadAndExecute(cx, module, rval);
    163 }
    164 
    165 bool ModuleLoader::loadAndExecute(JSContext* cx, HandleObject module,
    166                                  MutableHandleValue rval) {
    167  MOZ_ASSERT(module);
    168 
    169  RootedValue hostDefined(cx, ObjectValue(*module));
    170  if (!JS::LoadRequestedModules(cx, module, hostDefined, LoadResolved,
    171                                LoadRejected)) {
    172    return false;
    173  }
    174 
    175  if (JS_IsExceptionPending(cx)) {
    176    return false;
    177  }
    178 
    179  return JS::ModuleEvaluate(cx, module, rval);
    180 }
    181 
    182 /* static */
    183 bool ModuleLoader::LoadResolved(JSContext* cx, HandleValue hostDefined) {
    184  RootedObject module(cx, &hostDefined.toObject());
    185  return JS::ModuleLink(cx, module);
    186 }
    187 
    188 /* static */
    189 bool ModuleLoader::LoadRejected(JSContext* cx, HandleValue hostDefined,
    190                                HandleValue error) {
    191  JS_SetPendingException(cx, error);
    192  return true;
    193 }
    194 
    195 bool ModuleLoader::loadImportedModule(JSContext* cx,
    196                                      JS::Handle<JSScript*> referrer,
    197                                      JS::Handle<JSObject*> moduleRequest,
    198                                      JS::HandleValue payload) {
    199  // TODO: Bug 1968904: Update HostLoadImportedModule
    200  if (payload.isObject() && payload.toObject().is<PromiseObject>()) {
    201    // This is a dynamic import.
    202    return dynamicImport(cx, referrer, moduleRequest, payload);
    203  }
    204 
    205  Rooted<JSLinearString*> path(cx, resolve(cx, moduleRequest, referrer));
    206  if (!path) {
    207    return false;
    208  }
    209 
    210  RootedObject module(cx, loadAndParse(cx, path, moduleRequest));
    211  if (!module) {
    212    return false;
    213  }
    214 
    215  return JS::FinishLoadingImportedModule(cx, referrer, moduleRequest, payload,
    216                                         module, false);
    217 }
    218 
    219 bool ModuleLoader::populateImportMeta(JSContext* cx,
    220                                      JS::HandleValue privateValue,
    221                                      JS::HandleObject metaObject) {
    222  Rooted<JSLinearString*> path(cx);
    223  if (!privateValue.isUndefined()) {
    224    if (!getScriptPath(cx, privateValue, &path)) {
    225      return false;
    226    }
    227  }
    228 
    229  if (!path) {
    230    path = NewStringCopyZ<CanGC>(cx, "(unknown)");
    231    if (!path) {
    232      return false;
    233    }
    234  }
    235 
    236  RootedValue pathValue(cx, StringValue(path));
    237  if (!JS_DefineProperty(cx, metaObject, "url", pathValue, JSPROP_ENUMERATE)) {
    238    return false;
    239  }
    240 
    241  JSFunction* resolveFunc = js::DefineFunctionWithReserved(
    242      cx, metaObject, "resolve", ImportMetaResolve, ImportMetaResolveNumArgs,
    243      JSPROP_ENUMERATE);
    244  if (!resolveFunc) {
    245    return false;
    246  }
    247 
    248  RootedObject resolveFuncObj(cx, JS_GetFunctionObject(resolveFunc));
    249  js::SetFunctionNativeReserved(resolveFuncObj, ModulePrivateSlot,
    250                                privateValue);
    251 
    252  return true;
    253 }
    254 
    255 bool ModuleLoader::importMetaResolve(JSContext* cx,
    256                                     JS::Handle<JS::Value> referencingPrivate,
    257                                     JS::Handle<JSString*> specifier,
    258                                     JS::MutableHandle<JSString*> urlOut) {
    259  Rooted<JSLinearString*> path(cx, resolve(cx, specifier, referencingPrivate));
    260  if (!path) {
    261    return false;
    262  }
    263 
    264  urlOut.set(path);
    265  return true;
    266 }
    267 
    268 bool ModuleLoader::dynamicImport(JSContext* cx, JS::HandleScript referrer,
    269                                 JS::HandleObject moduleRequest,
    270                                 JS::HandleValue payload) {
    271  // To make this more realistic, use a promise to delay the import and make it
    272  // happen asynchronously. This method packages up the arguments and creates a
    273  // resolved promise, which on fullfillment calls doDynamicImport with the
    274  // original arguments.
    275 
    276  RootedValue moduleRequestValue(cx, ObjectValue(*moduleRequest));
    277  RootedObject closure(cx, JS_NewObjectWithGivenProto(cx, nullptr, nullptr));
    278  RootedValue referrerValue(cx);
    279  if (referrer) {
    280    referrerValue = PrivateGCThingValue(referrer);
    281  } else {
    282    RootedScript script(cx);
    283    const char* filename;
    284    uint32_t lineno;
    285    uint32_t pcOffset;
    286    bool mutedErrors;
    287    DescribeScriptedCallerForCompilation(cx, &script, &filename, &lineno,
    288                                         &pcOffset, &mutedErrors);
    289    MOZ_ASSERT(script);
    290    referrerValue = PrivateGCThingValue(script);
    291  }
    292  MOZ_ASSERT(!referrerValue.isUndefined());
    293 
    294  if (!closure ||
    295      !JS_DefineProperty(cx, closure, "referrer", referrerValue,
    296                         JSPROP_ENUMERATE) ||
    297      !JS_DefineProperty(cx, closure, "moduleRequest", moduleRequestValue,
    298                         JSPROP_ENUMERATE) ||
    299      !JS_DefineProperty(cx, closure, "payload", payload, JSPROP_ENUMERATE)) {
    300    return false;
    301  }
    302 
    303  RootedFunction onResolved(
    304      cx, NewNativeFunction(cx, DynamicImportDelayFulfilled, 1, nullptr));
    305  if (!onResolved) {
    306    return false;
    307  }
    308 
    309  RootedFunction onRejected(
    310      cx, NewNativeFunction(cx, DynamicImportDelayRejected, 1, nullptr));
    311  if (!onRejected) {
    312    return false;
    313  }
    314 
    315  RootedObject delayPromise(cx);
    316  RootedValue closureValue(cx, ObjectValue(*closure));
    317  delayPromise = PromiseObject::unforgeableResolve(cx, closureValue);
    318  if (!delayPromise) {
    319    return false;
    320  }
    321 
    322  return JS::AddPromiseReactions(cx, delayPromise, onResolved, onRejected);
    323 }
    324 
    325 bool ModuleLoader::DynamicImportDelayFulfilled(JSContext* cx, unsigned argc,
    326                                               Value* vp) {
    327  CallArgs args = CallArgsFromVp(argc, vp);
    328  RootedObject closure(cx, &args[0].toObject());
    329 
    330  RootedValue referrerValue(cx);
    331  RootedValue moduleRequestValue(cx);
    332  RootedValue payload(cx);
    333  if (!JS_GetProperty(cx, closure, "referrer", &referrerValue) ||
    334      !JS_GetProperty(cx, closure, "moduleRequest", &moduleRequestValue) ||
    335      !JS_GetProperty(cx, closure, "payload", &payload)) {
    336    return false;
    337  }
    338 
    339  RootedObject moduleRequest(cx, &moduleRequestValue.toObject());
    340  RootedScript referrer(cx, static_cast<JSScript*>(referrerValue.toGCThing()));
    341 
    342  ShellContext* scx = GetShellContext(cx);
    343  return scx->moduleLoader->doDynamicImport(cx, referrer, moduleRequest,
    344                                            payload);
    345 }
    346 
    347 bool ModuleLoader::DynamicImportDelayRejected(JSContext* cx, unsigned argc,
    348                                              Value* vp) {
    349  MOZ_CRASH("This promise should never be rejected");
    350 }
    351 
    352 bool ModuleLoader::doDynamicImport(JSContext* cx, JS::HandleScript referrer,
    353                                   JS::HandleObject moduleRequest,
    354                                   JS::HandleValue payload) {
    355  // Exceptions during dynamic import are handled by calling
    356  // FinishLoadingImportedModule with a pending exception on the context.
    357  Rooted<JSLinearString*> path(cx, resolve(cx, moduleRequest, referrer));
    358  if (!path) {
    359    return JS::FinishLoadingImportedModuleFailedWithPendingException(cx,
    360                                                                     payload);
    361  }
    362 
    363  RootedObject module(cx, loadAndParse(cx, path, moduleRequest));
    364  if (!module) {
    365    return JS::FinishLoadingImportedModuleFailedWithPendingException(cx,
    366                                                                     payload);
    367  }
    368 
    369  RootedValue hostDefined(cx, ObjectValue(*module));
    370  if (!JS::LoadRequestedModules(cx, module, hostDefined, LoadResolved,
    371                                LoadRejected)) {
    372    return JS::FinishLoadingImportedModuleFailedWithPendingException(cx,
    373                                                                     payload);
    374  }
    375 
    376  if (JS_IsExceptionPending(cx)) {
    377    return JS::FinishLoadingImportedModuleFailedWithPendingException(cx,
    378                                                                     payload);
    379  }
    380 
    381  return JS::FinishLoadingImportedModule(cx, nullptr, moduleRequest, payload,
    382                                         module, false);
    383 }
    384 
    385 JSLinearString* ModuleLoader::resolve(JSContext* cx,
    386                                      HandleObject moduleRequestArg,
    387                                      HandleScript referrer) {
    388  RootedValue referencingInfo(cx);
    389  if (referrer) {
    390    referencingInfo = GetScriptPrivate(referrer);
    391  }
    392 
    393  ModuleRequestObject* moduleRequest =
    394      &moduleRequestArg->as<ModuleRequestObject>();
    395  if (moduleRequest->specifier()->length() == 0) {
    396    JS_ReportErrorASCII(cx, "Invalid module specifier");
    397    return nullptr;
    398  }
    399 
    400  Rooted<JSLinearString*> name(
    401      cx, JS_EnsureLinearString(cx, moduleRequest->specifier()));
    402  if (!name) {
    403    return nullptr;
    404  }
    405 
    406  return resolve(cx, name, referencingInfo);
    407 }
    408 
    409 JSLinearString* ModuleLoader::resolve(JSContext* cx, HandleString specifier,
    410                                      HandleValue referencingInfo) {
    411  Rooted<JSLinearString*> name(cx, JS_EnsureLinearString(cx, specifier));
    412  if (!name) {
    413    return nullptr;
    414  }
    415 
    416  if (IsJavaScriptURL(name) || IsAbsolutePath(name)) {
    417    return name;
    418  }
    419 
    420  // Treat |name| as a relative path if it starts with either "./" or "../".
    421  bool isRelative =
    422      StringStartsWith(name, u"./") || StringStartsWith(name, u"../")
    423 #ifdef XP_WIN
    424      || StringStartsWith(name, u".\\") || StringStartsWith(name, u"..\\")
    425 #endif
    426      ;
    427 
    428  RootedString path(cx, loadPathStr);
    429 
    430  if (isRelative) {
    431    if (referencingInfo.isUndefined()) {
    432      JS_ReportErrorASCII(cx, "No referencing module for relative import");
    433      return nullptr;
    434    }
    435 
    436    Rooted<JSLinearString*> refPath(cx);
    437    if (!getScriptPath(cx, referencingInfo, &refPath)) {
    438      return nullptr;
    439    }
    440 
    441    if (!refPath) {
    442      JS_ReportErrorASCII(cx, "No path set for referencing module");
    443      return nullptr;
    444    }
    445 
    446    int32_t sepIndex = LastIndexOf(refPath, u'/');
    447 #ifdef XP_WIN
    448    sepIndex = std::max(sepIndex, LastIndexOf(refPath, u'\\'));
    449 #endif
    450    if (sepIndex >= 0) {
    451      path = SubString(cx, refPath, 0, sepIndex);
    452      if (!path) {
    453        return nullptr;
    454      }
    455    }
    456  }
    457 
    458  RootedString result(cx);
    459  RootedString pathSep(cx, pathSeparatorStr);
    460  result = JS_ConcatStrings(cx, path, pathSep);
    461  if (!result) {
    462    return nullptr;
    463  }
    464 
    465  result = JS_ConcatStrings(cx, result, name);
    466  if (!result) {
    467    return nullptr;
    468  }
    469 
    470  Rooted<JSLinearString*> linear(cx, JS_EnsureLinearString(cx, result));
    471  if (!linear) {
    472    return nullptr;
    473  }
    474  return normalizePath(cx, linear);
    475 }
    476 
    477 JSObject* ModuleLoader::loadAndParse(JSContext* cx, HandleString pathArg,
    478                                     JS::HandleObject moduleRequestArg) {
    479  Rooted<JSLinearString*> path(cx, JS_EnsureLinearString(cx, pathArg));
    480  if (!path) {
    481    return nullptr;
    482  }
    483 
    484  path = normalizePath(cx, path);
    485  if (!path) {
    486    return nullptr;
    487  }
    488 
    489  JS::ModuleType moduleType = JS::ModuleType::JavaScript;
    490  if (moduleRequestArg) {
    491    moduleType = moduleRequestArg->as<ModuleRequestObject>().moduleType();
    492  }
    493 
    494  RootedObject module(cx);
    495  if (!lookupModuleInRegistry(cx, moduleType, path, &module)) {
    496    return nullptr;
    497  }
    498 
    499  if (module) {
    500    return module;
    501  }
    502 
    503  UniqueChars filename = JS_EncodeStringToUTF8(cx, path);
    504  if (!filename) {
    505    return nullptr;
    506  }
    507 
    508  JS::CompileOptions options(cx);
    509  options.setFileAndLine(filename.get(), 1);
    510 
    511  RootedString source(cx, fetchSource(cx, path));
    512  if (!source) {
    513    return nullptr;
    514  }
    515 
    516  JS::AutoStableStringChars linearChars(cx);
    517  if (!linearChars.initTwoByte(cx, source)) {
    518    return nullptr;
    519  }
    520 
    521  JS::SourceText<char16_t> srcBuf;
    522  if (!srcBuf.initMaybeBorrowed(cx, linearChars)) {
    523    return nullptr;
    524  }
    525 
    526  switch (moduleType) {
    527    case JS::ModuleType::Unknown:
    528    case JS::ModuleType::Bytes:
    529      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    530                                JSMSG_BAD_MODULE_TYPE);
    531      return nullptr;
    532    case JS::ModuleType::JavaScript: {
    533      module = JS::CompileModule(cx, options, srcBuf);
    534      if (!module) {
    535        return nullptr;
    536      }
    537 
    538      RootedObject info(cx, js::CreateScriptPrivate(cx, path));
    539      if (!info) {
    540        return nullptr;
    541      }
    542 
    543      JS::SetModulePrivate(module, ObjectValue(*info));
    544    } break;
    545    case JS::ModuleType::JSON:
    546      module = JS::CompileJsonModule(cx, options, srcBuf);
    547      if (!module) {
    548        return nullptr;
    549      }
    550      break;
    551    case JS::ModuleType::CSS:
    552      // We don't support CSS modules in the shell because we don't have access
    553      // to a CSS parser in standalone shell builds.
    554      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    555                                JSMSG_BAD_MODULE_TYPE);
    556      return nullptr;
    557  }
    558 
    559  if (!addModuleToRegistry(cx, moduleType, path, module)) {
    560    return nullptr;
    561  }
    562 
    563  return module;
    564 }
    565 
    566 bool ModuleLoader::lookupModuleInRegistry(JSContext* cx,
    567                                          JS::ModuleType moduleType,
    568                                          HandleString path,
    569                                          MutableHandleObject moduleOut) {
    570  moduleOut.set(nullptr);
    571 
    572  RootedObject registry(cx, getOrCreateModuleRegistry(cx, moduleType));
    573  if (!registry) {
    574    return false;
    575  }
    576 
    577  RootedValue pathValue(cx, StringValue(path));
    578  RootedValue moduleValue(cx);
    579  if (!JS::MapGet(cx, registry, pathValue, &moduleValue)) {
    580    return false;
    581  }
    582 
    583  if (!moduleValue.isUndefined()) {
    584    moduleOut.set(&moduleValue.toObject());
    585  }
    586 
    587  return true;
    588 }
    589 
    590 bool ModuleLoader::addModuleToRegistry(JSContext* cx, JS::ModuleType moduleType,
    591                                       HandleString path, HandleObject module) {
    592  RootedObject registry(cx, getOrCreateModuleRegistry(cx, moduleType));
    593  if (!registry) {
    594    return false;
    595  }
    596 
    597  RootedValue pathValue(cx, StringValue(path));
    598  RootedValue moduleValue(cx, ObjectValue(*module));
    599  return JS::MapSet(cx, registry, pathValue, moduleValue);
    600 }
    601 
    602 static ArrayObject* GetOrCreateRootRegistry(JSContext* cx) {
    603  Handle<GlobalObject*> global = cx->global();
    604  RootedValue value(cx, global->getReservedSlot(GlobalAppSlotModuleRegistry));
    605  if (!value.isUndefined()) {
    606    return &value.toObject().as<ArrayObject>();
    607  }
    608 
    609  uint32_t numberOfModuleTypes = uint32_t(JS::ModuleType::Limit) + 1;
    610 
    611  Rooted<ArrayObject*> registry(
    612      cx, NewDenseFullyAllocatedArray(cx, numberOfModuleTypes, TenuredObject));
    613  if (!registry) {
    614    return nullptr;
    615  }
    616  registry->ensureDenseInitializedLength(0, numberOfModuleTypes);
    617 
    618  Rooted<JSObject*> innerRegistry(cx);
    619  for (size_t i = 0; i < numberOfModuleTypes; ++i) {
    620    innerRegistry = JS::NewMapObject(cx);
    621    if (!innerRegistry) {
    622      return nullptr;
    623    }
    624    registry->initDenseElement(i, ObjectValue(*innerRegistry));
    625  }
    626 
    627  global->setReservedSlot(GlobalAppSlotModuleRegistry, ObjectValue(*registry));
    628 
    629  return registry;
    630 }
    631 
    632 JSObject* ModuleLoader::getOrCreateModuleRegistry(JSContext* cx,
    633                                                  JS::ModuleType moduleType) {
    634  Rooted<ArrayObject*> rootRegistry(cx, GetOrCreateRootRegistry(cx));
    635  if (!rootRegistry) {
    636    return nullptr;
    637  }
    638 
    639  uint32_t index = uint32_t(moduleType);
    640  MOZ_ASSERT(rootRegistry->containsDenseElement(index));
    641  return &rootRegistry->getDenseElement(index).toObject();
    642 }
    643 
    644 bool ModuleLoader::getScriptPath(JSContext* cx, HandleValue privateValue,
    645                                 MutableHandle<JSLinearString*> pathOut) {
    646  pathOut.set(nullptr);
    647 
    648  RootedObject infoObj(cx, &privateValue.toObject());
    649  RootedValue pathValue(cx);
    650  if (!JS_GetProperty(cx, infoObj, "path", &pathValue)) {
    651    return false;
    652  }
    653 
    654  if (pathValue.isUndefined()) {
    655    return true;
    656  }
    657 
    658  RootedString path(cx, pathValue.toString());
    659  pathOut.set(JS_EnsureLinearString(cx, path));
    660  return pathOut;
    661 }
    662 
    663 JSLinearString* ModuleLoader::normalizePath(JSContext* cx,
    664                                            Handle<JSLinearString*> pathArg) {
    665  Rooted<JSLinearString*> path(cx, pathArg);
    666 
    667  if (IsJavaScriptURL(path)) {
    668    return path;
    669  }
    670 
    671 #ifdef XP_WIN
    672  // Replace all forward slashes with backward slashes.
    673  path = ReplaceCharGlobally(cx, path, u'/', PathSeparator);
    674  if (!path) {
    675    return nullptr;
    676  }
    677 
    678  // Remove the drive letter, if present.
    679  Rooted<JSLinearString*> drive(cx);
    680  if (path->length() > 2 && mozilla::IsAsciiAlpha(CharAt(path, 0)) &&
    681      CharAt(path, 1) == u':' && CharAt(path, 2) == u'\\') {
    682    drive = SubString(cx, path, 0, 2);
    683    path = SubString(cx, path, 2);
    684    if (!drive || !path) {
    685      return nullptr;
    686    }
    687  }
    688 #endif  // XP_WIN
    689 
    690  // Normalize the path by removing redundant path components.
    691  Rooted<GCVector<JSLinearString*>> components(cx, cx);
    692  size_t lastSep = 0;
    693  while (lastSep < path->length()) {
    694    int32_t i = IndexOf(path, PathSeparator, lastSep);
    695    if (i < 0) {
    696      i = path->length();
    697    }
    698 
    699    Rooted<JSLinearString*> part(cx, SubString(cx, path, lastSep, i));
    700    if (!part) {
    701      return nullptr;
    702    }
    703 
    704    lastSep = i + 1;
    705 
    706    // Remove "." when preceded by a path component.
    707    if (StringEquals(part, u".") && !components.empty()) {
    708      continue;
    709    }
    710 
    711    if (StringEquals(part, u"..") && !components.empty()) {
    712      // Replace "./.." with "..".
    713      if (StringEquals(components.back(), u".")) {
    714        components.back() = part;
    715        continue;
    716      }
    717 
    718      // When preceded by a non-empty path component, remove ".." and the
    719      // preceding component, unless the preceding component is also "..".
    720      if (!StringEquals(components.back(), u"") &&
    721          !StringEquals(components.back(), u"..")) {
    722        components.popBack();
    723        continue;
    724      }
    725    }
    726 
    727    if (!components.append(part)) {
    728      return nullptr;
    729    }
    730  }
    731 
    732  Rooted<JSLinearString*> pathSep(cx, pathSeparatorStr);
    733  RootedString normalized(cx, JoinStrings(cx, components, pathSep));
    734  if (!normalized) {
    735    return nullptr;
    736  }
    737 
    738 #ifdef XP_WIN
    739  if (drive) {
    740    normalized = JS_ConcatStrings(cx, drive, normalized);
    741    if (!normalized) {
    742      return nullptr;
    743    }
    744  }
    745 #endif
    746 
    747  return JS_EnsureLinearString(cx, normalized);
    748 }
    749 
    750 JSString* ModuleLoader::fetchSource(JSContext* cx,
    751                                    Handle<JSLinearString*> path) {
    752  if (IsJavaScriptURL(path)) {
    753    return ExtractJavaScriptURLSource(cx, path);
    754  }
    755 
    756  RootedString resolvedPath(cx, ResolvePath(cx, path, RootRelative));
    757  if (!resolvedPath) {
    758    return nullptr;
    759  }
    760 
    761  return FileAsString(cx, resolvedPath);
    762 }