Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  ModuleLoader.cpp   Sprache: C

 
/* -*- Mode: javascript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4
 * -*- */

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "shell/ModuleLoader.h"

#include "mozilla/DebugOnly.h"
#include "mozilla/TextUtils.h"

#include "jsapi.h"
#include "NamespaceImports.h"

#include "builtin/TestingUtility.h"  // js::CreateScriptPrivate
#include "js/Conversions.h"
#include "js/MapAndSet.h"
#include "js/Modules.h"
#include "js/PropertyAndElement.h"  // JS_DefineProperty, JS_GetProperty
#include "js/SourceText.h"
#include "js/StableStringChars.h"
#include "shell/jsshell.h"
#include "shell/OSObject.h"
#include "shell/StringUtils.h"
#include "util/Text.h"
#include "vm/JSAtomUtils.h"  // AtomizeString, PinAtom
#include "vm/JSContext.h"
#include "vm/StringType.h"

#include "vm/NativeObject-inl.h"

using namespace js;
using namespace js::shell;

static constexpr char16_t JavaScriptScheme[] = u"javascript:";

static bool IsJavaScriptURL(Handle<JSLinearString*> path) {
  return StringStartsWith(path, JavaScriptScheme);
}

static JSString* ExtractJavaScriptURLSource(JSContext* cx,
                                            Handle<JSLinearString*> path) {
  MOZ_ASSERT(IsJavaScriptURL(path));

  const size_t schemeLength = js_strlen(JavaScriptScheme);
  return SubString(cx, path, schemeLength);
}

bool ModuleLoader::init(JSContext* cx, HandleString loadPath) {
  loadPathStr = AtomizeString(cx, loadPath);
  if (!loadPathStr || !PinAtom(cx, loadPathStr)) {
    return false;
  }

  MOZ_ASSERT(IsAbsolutePath(loadPathStr));

  char16_t sep = PathSeparator;
  pathSeparatorStr = AtomizeChars(cx, &sep, 1);
  if (!pathSeparatorStr || !PinAtom(cx, pathSeparatorStr)) {
    return false;
  }

  JSRuntime* rt = cx->runtime();
  JS::SetModuleResolveHook(rt, ModuleLoader::ResolveImportedModule);
  JS::SetModuleMetadataHook(rt, ModuleLoader::GetImportMetaProperties);
  JS::SetModuleDynamicImportHook(rt, ModuleLoader::ImportModuleDynamically);
  return true;
}

// static
JSObject* ModuleLoader::ResolveImportedModule(
    JSContext* cx, JS::HandleValue referencingPrivate,
    JS::HandleObject moduleRequest) {
  ShellContext* scx = GetShellContext(cx);
  return scx->moduleLoader->resolveImportedModule(cx, referencingPrivate,
                                                  moduleRequest);
}

// static
bool ModuleLoader::GetImportMetaProperties(JSContext* cx,
                                           JS::HandleValue privateValue,
                                           JS::HandleObject metaObject) {
  ShellContext* scx = GetShellContext(cx);
  return scx->moduleLoader->populateImportMeta(cx, privateValue, metaObject);
}

bool ModuleLoader::ImportMetaResolve(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  RootedValue modulePrivate(
      cx, js::GetFunctionNativeReserved(&args.callee(), ModulePrivateSlot));

  // https://html.spec.whatwg.org/#hostgetimportmetaproperties
  // Step 4.1. Set specifier to ? ToString(specifier).
  //
  // https://tc39.es/ecma262/#sec-tostring
  RootedValue v(cx, args.get(ImportMetaResolveSpecifierArg));
  RootedString specifier(cx, JS::ToString(cx, v));
  if (!specifier) {
    return false;
  }

  // Step 4.2, 4.3 are implemented in importMetaResolve.
  ShellContext* scx = GetShellContext(cx);
  RootedString url(cx);
  if (!scx->moduleLoader->importMetaResolve(cx, modulePrivate, specifier,
                                            &url)) {
    return false;
  }

  // Step 4.4. Return the serialization of url.
  args.rval().setString(url);
  return true;
}

// static
bool ModuleLoader::ImportModuleDynamically(JSContext* cx,
                                           JS::HandleValue referencingPrivate,
                                           JS::HandleObject moduleRequest,
                                           JS::HandleObject promise) {
  ShellContext* scx = GetShellContext(cx);
  return scx->moduleLoader->dynamicImport(cx, referencingPrivate, moduleRequest,
                                          promise);
}

bool ModuleLoader::loadRootModule(JSContext* cx, HandleString path) {
  RootedValue rval(cx);
  if (!loadAndExecute(cx, path, nullptr, &rval)) {
    return false;
  }

  RootedObject evaluationPromise(cx, &rval.toObject());
  if (evaluationPromise == nullptr) {
    return false;
  }

  return JS::ThrowOnModuleEvaluationFailure(cx, evaluationPromise);
}

bool ModuleLoader::registerTestModule(JSContext* cx, HandleObject moduleRequest,
                                      Handle<ModuleObject*> module) {
  Rooted<JSLinearString*> path(
      cx, resolve(cx, moduleRequest, UndefinedHandleValue));
  if (!path) {
    return false;
  }

  path = normalizePath(cx, path);
  if (!path) {
    return false;
  }

  JS::ModuleType moduleType =
      moduleRequest->as<ModuleRequestObject>().moduleType();

  return addModuleToRegistry(cx, moduleType, path, module);
}

void ModuleLoader::clearModules(JSContext* cx) {
  Handle<GlobalObject*> global = cx->global();
  global->setReservedSlot(GlobalAppSlotModuleRegistry, UndefinedValue());
}

bool ModuleLoader::loadAndExecute(JSContext* cx, HandleString path,
                                  HandleObject moduleRequestArg,
                                  MutableHandleValue rval) {
  RootedObject module(cx, loadAndParse(cx, path, moduleRequestArg));
  if (!module) {
    return false;
  }

  if (!JS::ModuleLink(cx, module)) {
    return false;
  }

  return JS::ModuleEvaluate(cx, module, rval);
}

JSObject* ModuleLoader::resolveImportedModule(
    JSContext* cx, JS::HandleValue referencingPrivate,
    JS::HandleObject moduleRequest) {
  Rooted<JSLinearString*> path(cx,
                               resolve(cx, moduleRequest, referencingPrivate));
  if (!path) {
    return nullptr;
  }

  return loadAndParse(cx, path, moduleRequest);
}

bool ModuleLoader::populateImportMeta(JSContext* cx,
                                      JS::HandleValue privateValue,
                                      JS::HandleObject metaObject) {
  Rooted<JSLinearString*> path(cx);
  if (!privateValue.isUndefined()) {
    if (!getScriptPath(cx, privateValue, &path)) {
      return false;
    }
  }

  if (!path) {
    path = NewStringCopyZ<CanGC>(cx, "(unknown)");
    if (!path) {
      return false;
    }
  }

  RootedValue pathValue(cx, StringValue(path));
  if (!JS_DefineProperty(cx, metaObject, "url", pathValue, JSPROP_ENUMERATE)) {
    return false;
  }

  JSFunction* resolveFunc = js::DefineFunctionWithReserved(
      cx, metaObject, "resolve", ImportMetaResolve, ImportMetaResolveNumArgs,
      JSPROP_ENUMERATE);
  if (!resolveFunc) {
    return false;
  }

  RootedObject resolveFuncObj(cx, JS_GetFunctionObject(resolveFunc));
  js::SetFunctionNativeReserved(resolveFuncObj, ModulePrivateSlot,
                                privateValue);

  return true;
}

bool ModuleLoader::importMetaResolve(JSContext* cx,
                                     JS::Handle<JS::Value> referencingPrivate,
                                     JS::Handle<JSString*> specifier,
                                     JS::MutableHandle<JSString*> urlOut) {
  Rooted<JSLinearString*> path(cx, resolve(cx, specifier, referencingPrivate));
  if (!path) {
    return false;
  }

  urlOut.set(path);
  return true;
}

bool ModuleLoader::dynamicImport(JSContext* cx,
                                 JS::HandleValue referencingPrivate,
                                 JS::HandleObject moduleRequest,
                                 JS::HandleObject promise) {
  // To make this more realistic, use a promise to delay the import and make it
  // happen asynchronously. This method packages up the arguments and creates a
  // resolved promise, which on fullfillment calls doDynamicImport with the
  // original arguments.

  MOZ_ASSERT(promise);
  RootedValue moduleRequestValue(cx, ObjectValue(*moduleRequest));
  RootedValue promiseValue(cx, ObjectValue(*promise));
  RootedObject closure(cx, JS_NewObjectWithGivenProto(cx, nullptr, nullptr));
  if (!closure ||
      !JS_DefineProperty(cx, closure, "referencingPrivate", referencingPrivate,
                         JSPROP_ENUMERATE) ||
      !JS_DefineProperty(cx, closure, "moduleRequest", moduleRequestValue,
                         JSPROP_ENUMERATE) ||
      !JS_DefineProperty(cx, closure, "promise", promiseValue,
                         JSPROP_ENUMERATE)) {
    return false;
  }

  RootedFunction onResolved(
      cx, NewNativeFunction(cx, DynamicImportDelayFulfilled, 1, nullptr));
  if (!onResolved) {
    return false;
  }

  RootedFunction onRejected(
      cx, NewNativeFunction(cx, DynamicImportDelayRejected, 1, nullptr));
  if (!onRejected) {
    return false;
  }

  RootedObject delayPromise(cx);
  RootedValue closureValue(cx, ObjectValue(*closure));
  delayPromise = PromiseObject::unforgeableResolve(cx, closureValue);
  if (!delayPromise) {
    return false;
  }

  return JS::AddPromiseReactions(cx, delayPromise, onResolved, onRejected);
}

bool ModuleLoader::DynamicImportDelayFulfilled(JSContext* cx, unsigned argc,
                                               Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  RootedObject closure(cx, &args[0].toObject());

  RootedValue referencingPrivate(cx);
  RootedValue moduleRequestValue(cx);
  RootedValue promiseValue(cx);
  if (!JS_GetProperty(cx, closure, "referencingPrivate", &referencingPrivate) ||
      !JS_GetProperty(cx, closure, "moduleRequest", &moduleRequestValue) ||
      !JS_GetProperty(cx, closure, "promise", &promiseValue)) {
    return false;
  }

  RootedObject moduleRequest(cx, &moduleRequestValue.toObject());
  RootedObject promise(cx, &promiseValue.toObject());

  ShellContext* scx = GetShellContext(cx);
  return scx->moduleLoader->doDynamicImport(cx, referencingPrivate,
                                            moduleRequest, promise);
}

bool ModuleLoader::DynamicImportDelayRejected(JSContext* cx, unsigned argc,
                                              Value* vp) {
  MOZ_CRASH("This promise should never be rejected");
}

bool ModuleLoader::doDynamicImport(JSContext* cx,
                                   JS::HandleValue referencingPrivate,
                                   JS::HandleObject moduleRequest,
                                   JS::HandleObject promise) {
  // Exceptions during dynamic import are handled by calling
  // FinishDynamicModuleImport with a pending exception on the context.
  RootedValue rval(cx);
  bool ok =
      tryDynamicImport(cx, referencingPrivate, moduleRequest, promise, &rval);
  JSObject* evaluationObject = ok ? &rval.toObject() : nullptr;
  RootedObject evaluationPromise(cx, evaluationObject);
  return JS::FinishDynamicModuleImport(
      cx, evaluationPromise, referencingPrivate, moduleRequest, promise);
}

bool ModuleLoader::tryDynamicImport(JSContext* cx,
                                    JS::HandleValue referencingPrivate,
                                    JS::HandleObject moduleRequest,
                                    JS::HandleObject promise,
                                    JS::MutableHandleValue rval) {
  Rooted<JSLinearString*> path(cx,
                               resolve(cx, moduleRequest, referencingPrivate));
  if (!path) {
    return false;
  }

  return loadAndExecute(cx, path, moduleRequest, rval);
}

JSLinearString* ModuleLoader::resolve(JSContext* cx,
                                      HandleObject moduleRequestArg,
                                      HandleValue referencingInfo) {
  ModuleRequestObject* moduleRequest =
      &moduleRequestArg->as<ModuleRequestObject>();
  if (moduleRequest->specifier()->length() == 0) {
    JS_ReportErrorASCII(cx, "Invalid module specifier");
    return nullptr;
  }

  Rooted<JSLinearString*> name(
      cx, JS_EnsureLinearString(cx, moduleRequest->specifier()));
  if (!name) {
    return nullptr;
  }

  return resolve(cx, name, referencingInfo);
}

JSLinearString* ModuleLoader::resolve(JSContext* cx, HandleString specifier,
                                      HandleValue referencingInfo) {
  Rooted<JSLinearString*> name(cx, JS_EnsureLinearString(cx, specifier));
  if (!name) {
    return nullptr;
  }

  if (IsJavaScriptURL(name) || IsAbsolutePath(name)) {
    return name;
  }

  // Treat |name| as a relative path if it starts with either "./" or "../".
  bool isRelative =
      StringStartsWith(name, u"./") || StringStartsWith(name, u"../")
#ifdef XP_WIN
      || StringStartsWith(name, u".\\") || StringStartsWith(name, u"..\\")
#endif
      ;

  RootedString path(cx, loadPathStr);

  if (isRelative) {
    if (referencingInfo.isUndefined()) {
      JS_ReportErrorASCII(cx, "No referencing module for relative import");
      return nullptr;
    }

    Rooted<JSLinearString*> refPath(cx);
    if (!getScriptPath(cx, referencingInfo, &refPath)) {
      return nullptr;
    }

    if (!refPath) {
      JS_ReportErrorASCII(cx, "No path set for referencing module");
      return nullptr;
    }

    int32_t sepIndex = LastIndexOf(refPath, u'/');
#ifdef XP_WIN
    sepIndex = std::max(sepIndex, LastIndexOf(refPath, u'\\'));
#endif
    if (sepIndex >= 0) {
      path = SubString(cx, refPath, 0, sepIndex);
      if (!path) {
        return nullptr;
      }
    }
  }

  RootedString result(cx);
  RootedString pathSep(cx, pathSeparatorStr);
  result = JS_ConcatStrings(cx, path, pathSep);
  if (!result) {
    return nullptr;
  }

  result = JS_ConcatStrings(cx, result, name);
  if (!result) {
    return nullptr;
  }

  Rooted<JSLinearString*> linear(cx, JS_EnsureLinearString(cx, result));
  if (!linear) {
    return nullptr;
  }
  return normalizePath(cx, linear);
}

JSObject* ModuleLoader::loadAndParse(JSContext* cx, HandleString pathArg,
                                     JS::HandleObject moduleRequestArg) {
  Rooted<JSLinearString*> path(cx, JS_EnsureLinearString(cx, pathArg));
  if (!path) {
    return nullptr;
  }

  path = normalizePath(cx, path);
  if (!path) {
    return nullptr;
  }

  JS::ModuleType moduleType = JS::ModuleType::JavaScript;
  if (moduleRequestArg) {
    moduleType = moduleRequestArg->as<ModuleRequestObject>().moduleType();
  }

  RootedObject module(cx);
  if (!lookupModuleInRegistry(cx, moduleType, path, &module)) {
    return nullptr;
  }

  if (module) {
    return module;
  }

  UniqueChars filename = JS_EncodeStringToUTF8(cx, path);
  if (!filename) {
    return nullptr;
  }

  JS::CompileOptions options(cx);
  options.setFileAndLine(filename.get(), 1);

  RootedString source(cx, fetchSource(cx, path));
  if (!source) {
    return nullptr;
  }

  JS::AutoStableStringChars linearChars(cx);
  if (!linearChars.initTwoByte(cx, source)) {
    return nullptr;
  }

  JS::SourceText<char16_t> srcBuf;
  if (!srcBuf.initMaybeBorrowed(cx, linearChars)) {
    return nullptr;
  }

  switch (moduleType) {
    case JS::ModuleType::Unknown:
      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                JSMSG_BAD_MODULE_TYPE);
      return nullptr;
    case JS::ModuleType::JavaScript: {
      module = JS::CompileModule(cx, options, srcBuf);
      if (!module) {
        return nullptr;
      }

      RootedObject info(cx, js::CreateScriptPrivate(cx, path));
      if (!info) {
        return nullptr;
      }

      JS::SetModulePrivate(module, ObjectValue(*info));
    } break;
    case JS::ModuleType::JSON:
      module = JS::CompileJsonModule(cx, options, srcBuf);
      if (!module) {
        return nullptr;
      }
      break;
  }

  if (!addModuleToRegistry(cx, moduleType, path, module)) {
    return nullptr;
  }

  return module;
}

bool ModuleLoader::lookupModuleInRegistry(JSContext* cx,
                                          JS::ModuleType moduleType,
                                          HandleString path,
                                          MutableHandleObject moduleOut) {
  moduleOut.set(nullptr);

  RootedObject registry(cx, getOrCreateModuleRegistry(cx, moduleType));
  if (!registry) {
    return false;
  }

  RootedValue pathValue(cx, StringValue(path));
  RootedValue moduleValue(cx);
  if (!JS::MapGet(cx, registry, pathValue, &moduleValue)) {
    return false;
  }

  if (!moduleValue.isUndefined()) {
    moduleOut.set(&moduleValue.toObject());
  }

  return true;
}

bool ModuleLoader::addModuleToRegistry(JSContext* cx, JS::ModuleType moduleType,
                                       HandleString path, HandleObject module) {
  RootedObject registry(cx, getOrCreateModuleRegistry(cx, moduleType));
  if (!registry) {
    return false;
  }

  RootedValue pathValue(cx, StringValue(path));
  RootedValue moduleValue(cx, ObjectValue(*module));
  return JS::MapSet(cx, registry, pathValue, moduleValue);
}

static ArrayObject* GetOrCreateRootRegistry(JSContext* cx) {
  Handle<GlobalObject*> global = cx->global();
  RootedValue value(cx, global->getReservedSlot(GlobalAppSlotModuleRegistry));
  if (!value.isUndefined()) {
    return &value.toObject().as<ArrayObject>();
  }

  uint32_t numberOfModuleTypes = uint32_t(JS::ModuleType::Limit) + 1;

  Rooted<ArrayObject*> registry(
      cx, NewDenseFullyAllocatedArray(cx, numberOfModuleTypes, TenuredObject));
  if (!registry) {
    return nullptr;
  }
  registry->ensureDenseInitializedLength(0, numberOfModuleTypes);

  Rooted<JSObject*> innerRegistry(cx);
  for (size_t i = 0; i < numberOfModuleTypes; ++i) {
    innerRegistry = JS::NewMapObject(cx);
    if (!innerRegistry) {
      return nullptr;
    }
    registry->initDenseElement(i, ObjectValue(*innerRegistry));
  }

  global->setReservedSlot(GlobalAppSlotModuleRegistry, ObjectValue(*registry));

  return registry;
}

JSObject* ModuleLoader::getOrCreateModuleRegistry(JSContext* cx,
                                                  JS::ModuleType moduleType) {
  Rooted<ArrayObject*> rootRegistry(cx, GetOrCreateRootRegistry(cx));
  if (!rootRegistry) {
    return nullptr;
  }

  uint32_t index = uint32_t(moduleType);
  MOZ_ASSERT(rootRegistry->containsDenseElement(index));
  return &rootRegistry->getDenseElement(index).toObject();
}

bool ModuleLoader::getScriptPath(JSContext* cx, HandleValue privateValue,
                                 MutableHandle<JSLinearString*> pathOut) {
  pathOut.set(nullptr);

  RootedObject infoObj(cx, &privateValue.toObject());
  RootedValue pathValue(cx);
  if (!JS_GetProperty(cx, infoObj, "path", &pathValue)) {
    return false;
  }

  if (pathValue.isUndefined()) {
    return true;
  }

  RootedString path(cx, pathValue.toString());
  pathOut.set(JS_EnsureLinearString(cx, path));
  return pathOut;
}

JSLinearString* ModuleLoader::normalizePath(JSContext* cx,
                                            Handle<JSLinearString*> pathArg) {
  Rooted<JSLinearString*> path(cx, pathArg);

  if (IsJavaScriptURL(path)) {
    return path;
  }

#ifdef XP_WIN
  // Replace all forward slashes with backward slashes.
  path = ReplaceCharGlobally(cx, path, u'/', PathSeparator);
  if (!path) {
    return nullptr;
  }

  // Remove the drive letter, if present.
  Rooted<JSLinearString*> drive(cx);
  if (path->length() > 2 && mozilla::IsAsciiAlpha(CharAt(path, 0)) &&
      CharAt(path, 1) == u':' && CharAt(path, 2) == u'\\') {
    drive = SubString(cx, path, 0, 2);
    path = SubString(cx, path, 2);
    if (!drive || !path) {
      return nullptr;
    }
  }
#endif  // XP_WIN

  // Normalize the path by removing redundant path components.
  Rooted<GCVector<JSLinearString*>> components(cx, cx);
  size_t lastSep = 0;
  while (lastSep < path->length()) {
    int32_t i = IndexOf(path, PathSeparator, lastSep);
    if (i < 0) {
      i = path->length();
    }

    Rooted<JSLinearString*> part(cx, SubString(cx, path, lastSep, i));
    if (!part) {
      return nullptr;
    }

    lastSep = i + 1;

    // Remove "." when preceded by a path component.
    if (StringEquals(part, u".") && !components.empty()) {
      continue;
    }

    if (StringEquals(part, u"..") && !components.empty()) {
      // Replace "./.." with "..".
      if (StringEquals(components.back(), u".")) {
        components.back() = part;
        continue;
      }

      // When preceded by a non-empty path component, remove ".." and the
      // preceding component, unless the preceding component is also "..".
      if (!StringEquals(components.back(), u"") &&
          !StringEquals(components.back(), u"..")) {
        components.popBack();
        continue;
      }
    }

    if (!components.append(part)) {
      return nullptr;
    }
  }

  Rooted<JSLinearString*> pathSep(cx, pathSeparatorStr);
  RootedString normalized(cx, JoinStrings(cx, components, pathSep));
  if (!normalized) {
    return nullptr;
  }

#ifdef XP_WIN
  if (drive) {
    normalized = JS_ConcatStrings(cx, drive, normalized);
    if (!normalized) {
      return nullptr;
    }
  }
#endif

  return JS_EnsureLinearString(cx, normalized);
}

JSString* ModuleLoader::fetchSource(JSContext* cx,
                                    Handle<JSLinearString*> path) {
  if (IsJavaScriptURL(path)) {
    return ExtractJavaScriptURLSource(cx, path);
  }

  RootedString resolvedPath(cx, ResolvePath(cx, path, RootRelative));
  if (!resolvedPath) {
    return nullptr;
  }

  return FileAsString(cx, resolvedPath);
}

99%


¤ Dauer der Verarbeitung: 0.16 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge