Bug 1637529 - Rewrite the shell module loader in C++ r=jandem

Sorry for the big patch. This is a straight rewrite of shell/ModuleLoader.js in C++. It's mostly straigtforward but there were a couple of clunky parts: using promises/closures from C++ was rather verbose and I had to write some string utilities.

Differential Revision: https://phabricator.services.mozilla.com/D75271
This commit is contained in:
Jon Coppeard 2020-05-15 16:51:27 +00:00
Родитель 74a8ec946b
Коммит 7bc7b5ed18
9 изменённых файлов: 841 добавлений и 629 удалений

Просмотреть файл

@ -0,0 +1,533 @@
/* -*- 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/ArrayUtils.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/TextUtils.h"
#include "NamespaceImports.h"
#include "js/Modules.h"
#include "js/SourceText.h"
#include "js/StableStringChars.h"
#include "shell/jsshell.h"
#include "shell/OSObject.h"
#include "shell/StringUtils.h"
#include "vm/JSAtom.h"
#include "vm/JSContext.h"
#include "vm/StringType.h"
using namespace js;
using namespace js::shell;
static constexpr char16_t JavaScriptScheme[] = u"javascript:";
static bool IsJavaScriptURL(HandleLinearString path) {
return StringStartsWith(path, JavaScriptScheme);
}
static JSString* ExtractJavaScriptURLSource(JSContext* cx,
HandleLinearString path) {
MOZ_ASSERT(IsJavaScriptURL(path));
const size_t schemeLength = mozilla::ArrayLength(JavaScriptScheme) - 1;
return SubString(cx, path, schemeLength);
}
bool ModuleLoader::init(JSContext* cx, HandleString loadPath) {
loadPathStr = AtomizeString(cx, loadPath, PinAtom);
if (!loadPathStr) {
return false;
}
MOZ_ASSERT(IsAbsolutePath(loadPathStr));
char16_t sep = PathSeparator;
pathSeparatorStr = AtomizeChars(cx, &sep, 1);
if (!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::HandleString specifier) {
ShellContext* scx = GetShellContext(cx);
return scx->moduleLoader->resolveImportedModule(cx, referencingPrivate,
specifier);
}
// static
bool ModuleLoader::GetImportMetaProperties(JSContext* cx,
JS::HandleValue privateValue,
JS::HandleObject metaObject) {
ShellContext* scx = GetShellContext(cx);
return scx->moduleLoader->populateImportMeta(cx, privateValue, metaObject);
}
// static
bool ModuleLoader::ImportModuleDynamically(JSContext* cx,
JS::HandleValue referencingPrivate,
JS::HandleString specifier,
JS::HandleObject promise) {
ShellContext* scx = GetShellContext(cx);
return scx->moduleLoader->dynamicImport(cx, referencingPrivate, specifier,
promise);
}
bool ModuleLoader::loadRootModule(JSContext* cx, HandleString path) {
return loadAndExecute(cx, path);
}
bool ModuleLoader::loadAndExecute(JSContext* cx, HandleString path) {
RootedObject module(cx, loadAndParse(cx, path));
if (!module) {
return false;
}
return JS::ModuleInstantiate(cx, module) && JS::ModuleEvaluate(cx, module);
}
JSObject* ModuleLoader::resolveImportedModule(
JSContext* cx, JS::HandleValue referencingPrivate,
JS::HandleString specifier) {
RootedLinearString path(cx, resolve(cx, specifier, referencingPrivate));
if (!path) {
return nullptr;
}
return loadAndParse(cx, path);
}
bool ModuleLoader::populateImportMeta(JSContext* cx,
JS::HandleValue privateValue,
JS::HandleObject metaObject) {
RootedLinearString 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));
return JS_DefineProperty(cx, metaObject, "url", pathValue, JSPROP_ENUMERATE);
}
bool ModuleLoader::dynamicImport(JSContext* cx,
JS::HandleValue referencingPrivate,
JS::HandleString specifier,
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 specifierValue(cx, StringValue(specifier));
RootedValue promiseValue(cx, ObjectValue(*promise));
RootedObject closure(cx, JS_NewPlainObject(cx));
if (!closure ||
!JS_DefineProperty(cx, closure, "referencingPrivate", referencingPrivate,
JSPROP_ENUMERATE) ||
!JS_DefineProperty(cx, closure, "specifier", specifierValue,
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 specifierValue(cx);
RootedValue promiseValue(cx);
if (!JS_GetProperty(cx, closure, "referencingPrivate", &referencingPrivate) ||
!JS_GetProperty(cx, closure, "specifier", &specifierValue) ||
!JS_GetProperty(cx, closure, "promise", &promiseValue)) {
return false;
}
RootedString specifier(cx, specifierValue.toString());
RootedObject promise(cx, &promiseValue.toObject());
ShellContext* scx = GetShellContext(cx);
return scx->moduleLoader->doDynamicImport(cx, referencingPrivate, specifier,
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::HandleString specifier,
JS::HandleObject promise) {
// Exceptions during dynamic import are handled by calling
// FinishDynamicModuleImport with a pending exception on the context.
mozilla::DebugOnly<bool> ok =
tryDynamicImport(cx, referencingPrivate, specifier, promise);
MOZ_ASSERT_IF(!ok, JS_IsExceptionPending(cx));
return JS::FinishDynamicModuleImport(cx, referencingPrivate, specifier,
promise);
}
bool ModuleLoader::tryDynamicImport(JSContext* cx,
JS::HandleValue referencingPrivate,
JS::HandleString specifier,
JS::HandleObject promise) {
RootedLinearString path(cx, resolve(cx, specifier, referencingPrivate));
if (!path) {
return false;
}
return loadAndExecute(cx, path);
}
JSLinearString* ModuleLoader::resolve(JSContext* cx, HandleString nameArg,
HandleValue referencingInfo) {
if (nameArg->length() == 0) {
JS_ReportErrorASCII(cx, "Invalid module specifier");
return nullptr;
}
RootedLinearString name(cx, JS_EnsureLinearString(cx, nameArg));
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;
}
RootedLinearString 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;
}
return JS_EnsureLinearString(cx, result);
}
JSObject* ModuleLoader::loadAndParse(JSContext* cx, HandleString pathArg) {
RootedLinearString path(cx, JS_EnsureLinearString(cx, pathArg));
if (!path) {
return nullptr;
}
path = normalizePath(cx, path);
if (!path) {
return nullptr;
}
RootedObject module(cx);
if (!lookupModuleInRegistry(cx, path, &module)) {
return nullptr;
}
if (module) {
return module;
}
UniqueChars filename = JS_EncodeStringToLatin1(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 stableChars(cx);
if (!stableChars.initTwoByte(cx, source)) {
return nullptr;
}
const char16_t* chars = stableChars.twoByteRange().begin().get();
JS::SourceText<char16_t> srcBuf;
if (!srcBuf.init(cx, chars, source->length(),
JS::SourceOwnership::Borrowed)) {
return nullptr;
}
module = JS::CompileModule(cx, options, srcBuf);
if (!module) {
return nullptr;
}
RootedObject info(cx, CreateScriptPrivate(cx, path));
if (!info) {
return nullptr;
}
JS::SetModulePrivate(module, ObjectValue(*info));
if (!addModuleToRegistry(cx, path, module)) {
return nullptr;
}
return module;
}
bool ModuleLoader::lookupModuleInRegistry(JSContext* cx, HandleString path,
MutableHandleObject moduleOut) {
moduleOut.set(nullptr);
RootedObject registry(cx, getOrCreateModuleRegistry(cx));
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, HandleString path,
HandleObject module) {
RootedObject registry(cx, getOrCreateModuleRegistry(cx));
if (!registry) {
return false;
}
RootedValue pathValue(cx, StringValue(path));
RootedValue moduleValue(cx, ObjectValue(*module));
return JS::MapSet(cx, registry, pathValue, moduleValue);
}
JSObject* ModuleLoader::getOrCreateModuleRegistry(JSContext* cx) {
Handle<GlobalObject*> global = cx->global();
RootedValue value(cx, global->getReservedSlot(GlobalAppSlotModuleRegistry));
if (!value.isUndefined()) {
return &value.toObject();
}
JSObject* registry = JS::NewMapObject(cx);
if (!registry) {
return nullptr;
}
global->setReservedSlot(GlobalAppSlotModuleRegistry, ObjectValue(*registry));
return registry;
}
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,
HandleLinearString pathArg) {
RootedLinearString 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.
RootedLinearString 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);
size_t lastSep = 0;
while (lastSep < path->length()) {
int32_t i = IndexOf(path, PathSeparator, lastSep);
if (i < 0) {
i = path->length();
}
RootedLinearString 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;
}
}
RootedLinearString 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, HandleLinearString path) {
if (IsJavaScriptURL(path)) {
return ExtractJavaScriptURLSource(cx, path);
}
RootedString resolvedPath(cx, ResolvePath(cx, path, RootRelative));
if (!resolvedPath) {
return nullptr;
}
return FileAsString(cx, resolvedPath);
}

Просмотреть файл

@ -0,0 +1,69 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* 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/. */
#ifndef shell_ModuleLoader_h
#define shell_ModuleLoader_h
#include "gc/Rooting.h"
#include "js/RootingAPI.h"
namespace js {
namespace shell {
class ModuleLoader {
public:
bool init(JSContext* cx, HandleString loadPath);
bool loadRootModule(JSContext* cx, HandleString path);
private:
static JSObject* ResolveImportedModule(JSContext* cx,
HandleValue referencingPrivate,
HandleString specifier);
static bool GetImportMetaProperties(JSContext* cx, HandleValue privateValue,
HandleObject metaObject);
static bool ImportModuleDynamically(JSContext* cx,
HandleValue referencingPrivate,
HandleString specifier,
HandleObject promise);
static bool DynamicImportDelayFulfilled(JSContext* cx, unsigned argc,
Value* vp);
static bool DynamicImportDelayRejected(JSContext* cx, unsigned argc,
Value* vp);
bool loadAndExecute(JSContext* cx, HandleString path);
JSObject* resolveImportedModule(JSContext* cx, HandleValue referencingPrivate,
HandleString specifier);
bool populateImportMeta(JSContext* cx, HandleValue privateValue,
HandleObject metaObject);
bool dynamicImport(JSContext* cx, HandleValue referencingPrivate,
HandleString specifier, HandleObject promise);
bool doDynamicImport(JSContext* cx, HandleValue referencingPrivate,
HandleString specifier, HandleObject promise);
bool tryDynamicImport(JSContext* cx, HandleValue referencingPrivate,
HandleString specifier, HandleObject promise);
JSObject* loadAndParse(JSContext* cx, HandleString path);
bool lookupModuleInRegistry(JSContext* cx, HandleString path,
MutableHandleObject moduleOut);
bool addModuleToRegistry(JSContext* cx, HandleString path,
HandleObject module);
JSLinearString* resolve(JSContext* cx, HandleString name,
HandleValue referencingInfo);
bool getScriptPath(JSContext* cx, HandleValue privateValue,
MutableHandle<JSLinearString*> pathOut);
JSLinearString* normalizePath(JSContext* cx, HandleLinearString path);
JSObject* getOrCreateModuleRegistry(JSContext* cx);
JSString* fetchSource(JSContext* cx, HandleLinearString path);
// The following are used for pinned atoms which do not need rooting.
JSAtom* loadPathStr = nullptr;
JSAtom* pathSeparatorStr = nullptr;
} JS_HAZ_NON_GC_POINTER;
} // namespace shell
} // namespace js
#endif // shell_ModuleLoader_h

Просмотреть файл

@ -1,246 +0,0 @@
/* -*- 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/. */
/* global getModuleLoadPath setModuleLoadHook setModuleResolveHook setModuleMetadataHook */
/* global getModulePrivate setModulePrivate parseModule os */
/* global setModuleDynamicImportHook finishDynamicModuleImport abortDynamicModuleImport */
// A basic synchronous module loader for testing the shell.
//
// Supports loading files and 'javascript:' URLs that embed JS source text.
{
// Save standard built-ins before scripts can modify them.
const ArrayPrototypeJoin = Array.prototype.join;
const MapPrototypeGet = Map.prototype.get;
const MapPrototypeHas = Map.prototype.has;
const MapPrototypeSet = Map.prototype.set;
const ObjectDefineProperty = Object.defineProperty;
const ReflectApply = Reflect.apply;
const StringPrototypeIndexOf = String.prototype.indexOf;
const StringPrototypeLastIndexOf = String.prototype.lastIndexOf;
const StringPrototypeStartsWith = String.prototype.startsWith;
const StringPrototypeSubstring = String.prototype.substring;
const ErrorClass = Error;
const PromiseClass = Promise;
const PromiseResolve = Promise.resolve;
const JAVASCRIPT_SCHEME = "javascript:";
const ReflectLoader = new class {
constructor() {
this.registry = new Map();
this.loadPath = getModuleLoadPath();
}
isJavascriptURL(name) {
return ReflectApply(StringPrototypeStartsWith, name, [JAVASCRIPT_SCHEME]);
}
resolve(name, referencingInfo) {
if (name === "") {
throw new ErrorClass("Invalid module specifier");
}
if (this.isJavascriptURL(name) || os.path.isAbsolute(name)) {
return name;
}
let loadPath = this.loadPath;
// Treat |name| as a relative path if it starts with either "./"
// or "../".
let isRelative = ReflectApply(StringPrototypeStartsWith, name, ["./"])
|| ReflectApply(StringPrototypeStartsWith, name, ["../"])
#ifdef XP_WIN
|| ReflectApply(StringPrototypeStartsWith, name, [".\\"])
|| ReflectApply(StringPrototypeStartsWith, name, ["..\\"])
#endif
;
// If |name| is a relative path and the referencing module's path is
// available, load |name| relative to the that path.
if (isRelative) {
if (!referencingInfo) {
throw new ErrorClass("No referencing module for relative import");
}
let path = referencingInfo.path;
let sepIndex = ReflectApply(StringPrototypeLastIndexOf, path, ["/"]);
#ifdef XP_WIN
let otherSepIndex = ReflectApply(StringPrototypeLastIndexOf, path, ["\\"]);
if (otherSepIndex > sepIndex)
sepIndex = otherSepIndex;
#endif
if (sepIndex >= 0)
loadPath = ReflectApply(StringPrototypeSubstring, path, [0, sepIndex]);
}
return os.path.join(loadPath, name);
}
normalize(path) {
if (this.isJavascriptURL(path)) {
return path;
}
#ifdef XP_WIN
// Replace all forward slashes with backward slashes.
// NB: It may be tempting to replace this loop with a call to
// String.prototype.replace, but user scripts may have modified
// String.prototype or RegExp.prototype built-in functions, which makes
// it unsafe to call String.prototype.replace.
let newPath = "";
let lastSlash = 0;
while (true) {
let i = ReflectApply(StringPrototypeIndexOf, path, ["/", lastSlash]);
if (i < 0) {
newPath += ReflectApply(StringPrototypeSubstring, path, [lastSlash]);
break;
}
newPath += ReflectApply(StringPrototypeSubstring, path, [lastSlash, i]) + "\\";
lastSlash = i + 1;
}
path = newPath;
// Remove the drive letter, if present.
let isalpha = c => ("A" <= c && c <= "Z") || ("a" <= c && c <= "z");
let drive = "";
if (path.length > 2 && isalpha(path[0]) && path[1] === ":" && path[2] === "\\") {
drive = ReflectApply(StringPrototypeSubstring, path, [0, 2]);
path = ReflectApply(StringPrototypeSubstring, path, [2]);
}
#endif
const pathsep =
#ifdef XP_WIN
"\\";
#else
"/";
#endif
let n = 0;
let components = [];
// Normalize the path by removing redundant path components.
// NB: See above for why we don't call String.prototype.split here.
let lastSep = 0;
while (lastSep < path.length) {
let i = ReflectApply(StringPrototypeIndexOf, path, [pathsep, lastSep]);
if (i < 0)
i = path.length;
let part = ReflectApply(StringPrototypeSubstring, path, [lastSep, i]);
lastSep = i + 1;
// Remove "." when preceded by a path component.
if (part === "." && n > 0)
continue;
if (part === ".." && n > 0) {
// Replace "./.." with "..".
if (components[n - 1] === ".") {
components[n - 1] = "..";
continue;
}
// When preceded by a non-empty path component, remove ".." and
// the preceding component, unless the preceding component is also
// "..".
if (components[n - 1] !== "" && components[n - 1] !== "..") {
components.length = --n;
continue;
}
}
ObjectDefineProperty(components, n++, {
__proto__: null,
value: part,
writable: true, enumerable: true, configurable: true,
});
}
let normalized = ReflectApply(ArrayPrototypeJoin, components, [pathsep]);
#ifdef XP_WIN
normalized = drive + normalized;
#endif
return normalized;
}
fetch(path) {
if (this.isJavascriptURL(path)) {
return ReflectApply(StringPrototypeSubstring, path, [JAVASCRIPT_SCHEME.length]);
}
return os.file.readFile(path);
}
loadAndParse(path) {
let normalized = this.normalize(path);
if (ReflectApply(MapPrototypeHas, this.registry, [normalized]))
return ReflectApply(MapPrototypeGet, this.registry, [normalized]);
let source = this.fetch(path);
let module = parseModule(source, path);
let moduleInfo = { path: normalized };
setModulePrivate(module, moduleInfo);
ReflectApply(MapPrototypeSet, this.registry, [normalized, module]);
return module;
}
loadAndExecute(path) {
let module = this.loadAndParse(path);
module.declarationInstantiation();
return module.evaluation();
}
importRoot(path) {
return this.loadAndExecute(path);
}
["import"](name, referencingInfo) {
let path = this.resolve(name, null);
return this.loadAndExecute(path);
}
populateImportMeta(moduleInfo, metaObject) {
// For the shell, use the module's normalized path as the base URL.
let path;
if (moduleInfo) {
path = moduleInfo.path;
} else {
path = "(unknown)";
}
metaObject.url = path;
}
dynamicImport(referencingInfo, specifier, promise) {
ReflectApply(PromiseResolve, PromiseClass, [])
.then(_ => {
let path = ReflectLoader.resolve(specifier, referencingInfo);
ReflectLoader.loadAndExecute(path);
finishDynamicModuleImport(referencingInfo, specifier, promise);
}).catch(err => {
abortDynamicModuleImport(referencingInfo, specifier, promise, err);
});
}
};
setModuleLoadHook((path) => ReflectLoader.importRoot(path));
setModuleResolveHook((referencingInfo, requestName) => {
let path = ReflectLoader.resolve(requestName, referencingInfo);
return ReflectLoader.loadAndParse(path);
});
setModuleMetadataHook((module, metaObject) => {
ReflectLoader.populateImportMeta(module, metaObject);
});
setModuleDynamicImportHook((referencingInfo, specifier, promise) => {
ReflectLoader.dynamicImport(referencingInfo, specifier, promise);
});
}

Просмотреть файл

@ -35,6 +35,7 @@
#include "js/PropertySpec.h"
#include "js/Wrapper.h"
#include "shell/jsshell.h"
#include "shell/StringUtils.h"
#include "util/StringBuffer.h"
#include "util/Text.h"
#include "util/Windows.h"
@ -57,18 +58,8 @@ using js::shell::RCFile;
namespace js {
namespace shell {
#ifdef XP_WIN
const char PathSeparator = '\\';
#else
const char PathSeparator = '/';
#endif
static bool IsAbsolutePath(const UniqueChars& filename) {
const char* pathname = filename.get();
if (pathname[0] == PathSeparator) {
return true;
}
bool IsAbsolutePath(JSLinearString* filename) {
size_t length = filename->length();
#ifdef XP_WIN
// On Windows there are various forms of absolute paths (see
@ -79,16 +70,16 @@ static bool IsAbsolutePath(const UniqueChars& filename) {
// "\\..."
// "C:\..."
//
// The first two cases are handled by the test above so we only need a test
// for the last one here.
// The first two cases are handled by the common test below so we only need a
// specific test for the last one here.
if ((strlen(pathname) > 3 && mozilla::IsAsciiAlpha(pathname[0]) &&
pathname[1] == ':' && pathname[2] == '\\')) {
if (length > 3 && mozilla::IsAsciiAlpha(CharAt(filename, 0)) &&
CharAt(filename, 1) == u':' && CharAt(filename, 2) == u'\\') {
return true;
}
#endif
return false;
return length > 0 && CharAt(filename, 0) == PathSeparator;
}
/*
@ -109,13 +100,18 @@ JSString* ResolvePath(JSContext* cx, HandleString filenameStr,
#endif
}
UniqueChars filename = JS_EncodeStringToLatin1(cx, filenameStr);
if (!filename) {
RootedLinearString str(cx, JS_EnsureLinearString(cx, filenameStr));
if (!str) {
return nullptr;
}
if (IsAbsolutePath(filename)) {
return filenameStr;
if (IsAbsolutePath(str)) {
return str;
}
UniqueChars filename = JS_EncodeStringToLatin1(cx, str);
if (!filename) {
return nullptr;
}
JS::AutoFilename scriptFilename;
@ -757,12 +753,12 @@ static bool ospath_isAbsolute(JSContext* cx, unsigned argc, Value* vp) {
return false;
}
UniqueChars path = JS_EncodeStringToLatin1(cx, args[0].toString());
if (!path) {
RootedLinearString str(cx, JS_EnsureLinearString(cx, args[0].toString()));
if (!str) {
return false;
}
args.rval().setBoolean(IsAbsolutePath(path));
args.rval().setBoolean(IsAbsolutePath(str));
return true;
}
@ -786,14 +782,19 @@ static bool ospath_join(JSContext* cx, unsigned argc, Value* vp) {
return false;
}
UniqueChars path = JS_EncodeStringToLatin1(cx, args[i].toString());
if (!path) {
RootedLinearString str(cx, JS_EnsureLinearString(cx, args[i].toString()));
if (!str) {
return false;
}
if (IsAbsolutePath(path)) {
if (IsAbsolutePath(str)) {
MOZ_ALWAYS_TRUE(buffer.resize(0));
} else if (i != 0) {
UniqueChars path = JS_EncodeStringToLatin1(cx, str);
if (!path) {
return false;
}
if (!buffer.append(PathSeparator)) {
return false;
}

Просмотреть файл

@ -14,6 +14,12 @@
namespace js {
namespace shell {
#ifdef XP_WIN
constexpr char PathSeparator = '\\';
#else
constexpr char PathSeparator = '/';
#endif
struct RCFile;
/* Define an os object on the given global object. */
@ -22,6 +28,8 @@ bool DefineOS(JSContext* cx, JS::HandleObject global, bool fuzzingSafe,
enum PathResolutionMode { RootRelative, ScriptRelative };
bool IsAbsolutePath(JSLinearString* filename);
JSString* ResolvePath(JSContext* cx, JS::HandleString filenameStr,
PathResolutionMode resolveMode);

147
js/src/shell/StringUtils.h Normal file
Просмотреть файл

@ -0,0 +1,147 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* 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/. */
/* String utility functions used by the module loader. */
#ifndef shell_StringUtils_h
#define shell_StringUtils_h
#include "jsapi.h"
#include "js/StableStringChars.h"
namespace js {
namespace shell {
inline char16_t CharAt(JSLinearString* str, size_t index) {
return str->latin1OrTwoByteChar(index);
}
inline JSLinearString* SubString(JSContext* cx, JSLinearString* str,
size_t start, size_t end) {
MOZ_ASSERT(start <= str->length());
MOZ_ASSERT(end >= start && end <= str->length());
return NewDependentString(cx, str, start, end - start);
}
inline JSLinearString* SubString(JSContext* cx, JSLinearString* str,
size_t start) {
return SubString(cx, str, start, str->length());
}
template <size_t NullTerminatedLength>
bool StringStartsWith(JSLinearString* str,
const char16_t (&chars)[NullTerminatedLength]) {
MOZ_ASSERT(NullTerminatedLength > 0);
const size_t length = NullTerminatedLength - 1;
MOZ_ASSERT(chars[length] == '\0');
if (str->length() < length) {
return false;
}
for (size_t i = 0; i < length; i++) {
if (CharAt(str, i) != chars[i]) {
return false;
}
}
return true;
}
template <size_t NullTerminatedLength>
bool StringEquals(JSLinearString* str,
const char16_t (&chars)[NullTerminatedLength]) {
MOZ_ASSERT(NullTerminatedLength > 0);
const size_t length = NullTerminatedLength - 1;
MOZ_ASSERT(chars[length] == '\0');
return str->length() == length && StringStartsWith(str, chars);
}
inline int32_t IndexOf(HandleLinearString str, char16_t target,
size_t start = 0) {
int32_t length = str->length();
for (int32_t i = start; i < length; i++) {
if (CharAt(str, i) == target) {
return i;
}
}
return -1;
}
inline int32_t LastIndexOf(HandleLinearString str, char16_t target) {
int32_t length = str->length();
for (int32_t i = length - 1; i >= 0; i--) {
if (CharAt(str, i) == target) {
return i;
}
}
return -1;
}
inline JSLinearString* ReplaceCharGlobally(JSContext* cx,
HandleLinearString str,
char16_t target,
char16_t replacement) {
int32_t i = IndexOf(str, target);
if (i == -1) {
return str;
}
JS::AutoStableStringChars chars(cx);
if (!chars.initTwoByte(cx, str)) {
return nullptr;
}
Vector<char16_t> buf(cx);
if (!buf.append(chars.twoByteChars(), str->length())) {
return nullptr;
}
for (; i < int32_t(buf.length()); i++) {
if (buf[i] == target) {
buf[i] = replacement;
}
}
RootedString result(cx, JS_NewUCStringCopyN(cx, buf.begin(), buf.length()));
if (!result) {
return nullptr;
}
return JS_EnsureLinearString(cx, result);
}
inline JSString* JoinStrings(JSContext* cx,
Handle<GCVector<JSLinearString*>> strings,
HandleLinearString separator) {
RootedString result(cx, JS_GetEmptyString(cx));
for (size_t i = 0; i < strings.length(); i++) {
HandleString str = strings[i];
if (i != 0) {
result = JS_ConcatStrings(cx, result, separator);
if (!result) {
return nullptr;
}
}
result = JS_ConcatStrings(cx, result, str);
if (!result) {
return nullptr;
}
}
return result;
}
} // namespace shell
} // namespace js
#endif // shell_StringUtils_h

Просмотреть файл

@ -64,7 +64,6 @@
# include "prerror.h"
# include "prlink.h"
#endif
#include "shellmoduleloader.out.h"
#include "builtin/Array.h"
#include "builtin/MapObject.h"
@ -193,17 +192,6 @@ enum JSShellExitCode {
EXITCODE_TIMEOUT = 6
};
// Define use of application-specific slots on the shell's global object.
enum GlobalAppSlot {
GlobalAppSlotModuleLoadHook, // Shell-specific; load a module graph
GlobalAppSlotModuleResolveHook, // HostResolveImportedModule
GlobalAppSlotModuleMetadataHook, // HostPopulateImportMeta
GlobalAppSlotModuleDynamicImportHook, // HostImportModuleDynamically
GlobalAppSlotCount
};
static_assert(GlobalAppSlotCount <= JSCLASS_GLOBAL_APPLICATION_SLOTS,
"Too many applications slots defined for shell global");
/*
* Note: This limit should match the stack limit set by the browser in
* js/xpconnect/src/XPCJSContext.cpp
@ -861,6 +849,22 @@ void EnvironmentPreparer::invoke(HandleObject global, Closure& closure) {
}
}
JSObject* js::shell::CreateScriptPrivate(JSContext* cx, HandleString path) {
RootedObject info(cx, JS_NewPlainObject(cx));
if (!info) {
return nullptr;
}
if (path) {
RootedValue pathValue(cx, StringValue(path));
if (!JS_DefineProperty(cx, info, "path", pathValue, JSPROP_ENUMERATE)) {
return nullptr;
}
}
return info;
}
static bool RegisterScriptPathWithModuleLoader(JSContext* cx,
HandleScript script,
const char* filename) {
@ -873,20 +877,9 @@ static bool RegisterScriptPathWithModuleLoader(JSContext* cx,
return false;
}
RootedObject infoObject(cx);
Value val = JS::GetScriptPrivate(script);
if (val.isUndefined()) {
infoObject = JS_NewPlainObject(cx);
if (!infoObject) {
return false;
}
} else {
infoObject = val.toObjectOrNull();
}
RootedValue pathValue(cx, StringValue(path));
if (!JS_DefineProperty(cx, infoObject, "path", pathValue, 0)) {
MOZ_ASSERT(JS::GetScriptPrivate(script).isUndefined());
RootedObject infoObject(cx, CreateScriptPrivate(cx, path));
if (!infoObject) {
return false;
}
@ -1002,63 +995,9 @@ static MOZ_MUST_USE bool RunBinAST(JSContext* cx, const char* filename,
#endif // JS_BUILD_BINAST
static bool InitModuleLoader(JSContext* cx) {
// Decompress and evaluate the embedded module loader source to initialize
// the module loader for the current compartment.
uint32_t srcLen = moduleloader::GetRawScriptsSize();
auto src = cx->make_pod_array<char>(srcLen);
if (!src ||
!DecompressString(moduleloader::compressedSources,
moduleloader::GetCompressedSize(),
reinterpret_cast<unsigned char*>(src.get()), srcLen)) {
return false;
}
CompileOptions options(cx);
options.setIntroductionType("shell module loader");
options.setFileAndLine("shell/ModuleLoader.js", 1);
options.setSelfHostingMode(false);
options.setForceFullParse();
options.setForceStrictMode();
JS::SourceText<Utf8Unit> srcBuf;
if (!srcBuf.init(cx, std::move(src), srcLen)) {
return false;
}
RootedValue rv(cx);
return JS::Evaluate(cx, options, srcBuf, &rv);
}
static bool GetModuleImportHook(JSContext* cx,
MutableHandleFunction resultOut) {
Handle<GlobalObject*> global = cx->global();
RootedValue hookValue(cx,
global->getReservedSlot(GlobalAppSlotModuleLoadHook));
if (hookValue.isUndefined()) {
JS_ReportErrorASCII(cx, "Module load hook not set");
return false;
}
if (!hookValue.isObject() || !hookValue.toObject().is<JSFunction>()) {
JS_ReportErrorASCII(cx, "Module load hook is not a function");
return false;
}
resultOut.set(&hookValue.toObject().as<JSFunction>());
return true;
}
static MOZ_MUST_USE bool RunModule(JSContext* cx, const char* filename,
FILE* file, bool compileOnly) {
// Execute a module by calling the module loader's import hook on the
// resolved filename.
RootedFunction importFun(cx);
if (!GetModuleImportHook(cx, &importFun)) {
return false;
}
bool compileOnly) {
ShellContext* sc = GetShellContext(cx);
RootedString path(cx, JS_NewStringCopyZ(cx, filename));
if (!path) {
@ -1070,11 +1009,7 @@ static MOZ_MUST_USE bool RunModule(JSContext* cx, const char* filename,
return false;
}
JS::RootedValueArray<1> args(cx);
args[0].setString(path);
RootedValue value(cx);
return JS_CallFunction(cx, nullptr, importFun, args, &value);
return sc->moduleLoader->loadRootModule(cx, path);
}
static void ShellCleanupFinalizationRegistryCallback(JSObject* registry,
@ -1584,7 +1519,7 @@ static MOZ_MUST_USE bool Process(JSContext* cx, const char* filename,
}
break;
case FileModule:
if (!RunModule(cx, filename, file, compileOnly)) {
if (!RunModule(cx, filename, compileOnly)) {
return false;
}
break;
@ -1931,7 +1866,7 @@ static bool ParseCompileOptions(JSContext* cx, CompileOptions& options,
return false;
}
if (v.isObject()) {
RootedObject infoObject(cx, JS_NewPlainObject(cx));
RootedObject infoObject(cx, CreateScriptPrivate(cx));
RootedValue elementValue(cx, v);
if (!JS_WrapValue(cx, &elementValue)) {
return false;
@ -5122,44 +5057,6 @@ static bool DecodeModule(JSContext* cx, unsigned argc, Value* vp) {
return true;
}
static bool SetModuleLoadHook(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.requireAtLeast(cx, "setModuleLoadHook", 1)) {
return false;
}
if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
const char* typeName = InformalValueTypeName(args[0]);
JS_ReportErrorASCII(cx, "expected hook function, got %s", typeName);
return false;
}
Handle<GlobalObject*> global = cx->global();
global->setReservedSlot(GlobalAppSlotModuleLoadHook, args[0]);
args.rval().setUndefined();
return true;
}
static bool SetModuleResolveHook(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.requireAtLeast(cx, "setModuleResolveHook", 1)) {
return false;
}
if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
const char* typeName = InformalValueTypeName(args[0]);
JS_ReportErrorASCII(cx, "expected hook function, got %s", typeName);
return false;
}
Handle<GlobalObject*> global = cx->global();
global->setReservedSlot(GlobalAppSlotModuleResolveHook, args[0]);
args.rval().setUndefined();
return true;
}
static JSObject* ShellModuleResolveHook(JSContext* cx,
HandleValue referencingPrivate,
HandleString specifier) {
@ -5189,9 +5086,9 @@ static JSObject* ShellModuleResolveHook(JSContext* cx,
return &result.toObject();
}
static bool SetModuleMetadataHook(JSContext* cx, unsigned argc, Value* vp) {
static bool SetModuleResolveHook(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.requireAtLeast(cx, "setModuleMetadataHook", 1)) {
if (!args.requireAtLeast(cx, "setModuleResolveHook", 1)) {
return false;
}
@ -5202,169 +5099,14 @@ static bool SetModuleMetadataHook(JSContext* cx, unsigned argc, Value* vp) {
}
Handle<GlobalObject*> global = cx->global();
global->setReservedSlot(GlobalAppSlotModuleMetadataHook, args[0]);
global->setReservedSlot(GlobalAppSlotModuleResolveHook, args[0]);
JS::SetModuleResolveHook(cx->runtime(), ShellModuleResolveHook);
args.rval().setUndefined();
return true;
}
static bool CallModuleMetadataHook(JSContext* cx, HandleValue modulePrivate,
HandleObject metaObject) {
Handle<GlobalObject*> global = cx->global();
RootedValue hookValue(
cx, global->getReservedSlot(GlobalAppSlotModuleMetadataHook));
if (hookValue.isUndefined()) {
JS_ReportErrorASCII(cx, "Module metadata hook not set");
return false;
}
MOZ_ASSERT(hookValue.toObject().is<JSFunction>());
JS::RootedValueArray<2> args(cx);
args[0].set(modulePrivate);
args[1].setObject(*metaObject);
RootedValue dummy(cx);
return JS_CallFunctionValue(cx, nullptr, hookValue, args, &dummy);
}
static bool ReportArgumentTypeError(JSContext* cx, HandleValue value,
const char* expected) {
const char* typeName = InformalValueTypeName(value);
JS_ReportErrorASCII(cx, "Expected %s, got %s", expected, typeName);
return false;
}
static bool ShellSetModulePrivate(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.requireAtLeast(cx, "setModulePrivate", 2)) {
return false;
}
if (!args[0].isObject() || !args[0].toObject().is<ModuleObject>()) {
return ReportArgumentTypeError(cx, args[0], "module object");
}
JS::SetModulePrivate(&args[0].toObject(), args[1]);
args.rval().setUndefined();
return true;
}
static bool ShellGetModulePrivate(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.requireAtLeast(cx, "getModulePrivate", 1)) {
return false;
}
if (!args[0].isObject() || !args[0].toObject().is<ModuleObject>()) {
return ReportArgumentTypeError(cx, args[0], "module object");
}
args.rval().set(JS::GetModulePrivate(&args[0].toObject()));
return true;
}
static bool SetModuleDynamicImportHook(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.requireAtLeast(cx, "setModuleDynamicImportHook", 1)) {
return false;
}
if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
const char* typeName = InformalValueTypeName(args[0]);
JS_ReportErrorASCII(cx, "expected hook function, got %s", typeName);
return false;
}
Handle<GlobalObject*> global = cx->global();
global->setReservedSlot(GlobalAppSlotModuleDynamicImportHook, args[0]);
args.rval().setUndefined();
return true;
}
static bool FinishDynamicModuleImport(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.requireAtLeast(cx, "finishDynamicModuleImport", 3)) {
return false;
}
if (!args[1].isString()) {
return ReportArgumentTypeError(cx, args[1], "String");
}
if (!args[2].isObject() || !args[2].toObject().is<PromiseObject>()) {
return ReportArgumentTypeError(cx, args[2], "PromiseObject");
}
RootedString specifier(cx, args[1].toString());
Rooted<PromiseObject*> promise(cx, &args[2].toObject().as<PromiseObject>());
return js::FinishDynamicModuleImport(cx, args[0], specifier, promise);
}
static bool AbortDynamicModuleImport(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.requireAtLeast(cx, "abortDynamicModuleImport", 4)) {
return false;
}
if (!args[1].isString()) {
return ReportArgumentTypeError(cx, args[1], "String");
}
if (!args[2].isObject() || !args[2].toObject().is<PromiseObject>()) {
return ReportArgumentTypeError(cx, args[2], "PromiseObject");
}
RootedString specifier(cx, args[1].toString());
Rooted<PromiseObject*> promise(cx, &args[2].toObject().as<PromiseObject>());
cx->setPendingExceptionAndCaptureStack(args[3]);
return js::FinishDynamicModuleImport(cx, args[0], specifier, promise);
}
static bool ShellModuleDynamicImportHook(JSContext* cx,
HandleValue referencingPrivate,
HandleString specifier,
HandleObject promise) {
Handle<GlobalObject*> global = cx->global();
RootedValue hookValue(
cx, global->getReservedSlot(GlobalAppSlotModuleDynamicImportHook));
if (hookValue.isUndefined()) {
JS_ReportErrorASCII(cx, "Module resolve hook not set");
return false;
}
MOZ_ASSERT(hookValue.toObject().is<JSFunction>());
JS::RootedValueArray<3> args(cx);
args[0].set(referencingPrivate);
args[1].setString(specifier);
args[2].setObject(*promise);
RootedValue result(cx);
return JS_CallFunctionValue(cx, nullptr, hookValue, args, &result);
}
static bool GetModuleLoadPath(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
ShellContext* sc = GetShellContext(cx);
if (sc->moduleLoadPath) {
JSString* str = JS_NewStringCopyZ(cx, sc->moduleLoadPath.get());
if (!str) {
return false;
}
args.rval().setString(str);
} else {
args.rval().setNull();
}
return true;
}
#if defined(JS_BUILD_BINAST)
template <typename Tok>
@ -8924,52 +8666,11 @@ static const JSFunctionSpecWithHelp shell_functions[] = {
"decodeModule(code)",
" Takes a XDR bytecode representation of an uninstantiated ModuleObject and returns a ModuleObject."),
JS_FN_HELP("setModuleLoadHook", SetModuleLoadHook, 1, 0,
"setModuleLoadHook(function(path))",
" Set the shell specific module load hook to |function|.\n"
" This hook is used to load a module graph. It should be implemented by the\n"
" module loader."),
JS_FN_HELP("setModuleResolveHook", SetModuleResolveHook, 1, 0,
"setModuleResolveHook(function(referrer, specifier))",
" Set the HostResolveImportedModule hook to |function|.\n"
" This hook is used to look up a previously loaded module object. It should\n"
" be implemented by the module loader."),
JS_FN_HELP("setModuleMetadataHook", SetModuleMetadataHook, 1, 0,
"setModuleMetadataHook(function(module) {})",
" Set the HostPopulateImportMeta hook to |function|.\n"
" This hook is used to create the metadata object returned by import.meta for\n"
" a module. It should be implemented by the module loader."),
JS_FN_HELP("setModuleDynamicImportHook", SetModuleDynamicImportHook, 1, 0,
"setModuleDynamicImportHook(function(referrer, specifier, promise))",
" Set the HostImportModuleDynamically hook to |function|.\n"
" This hook is used to dynamically import a module. It should\n"
" be implemented by the module loader."),
JS_FN_HELP("finishDynamicModuleImport", FinishDynamicModuleImport, 3, 0,
"finishDynamicModuleImport(referrer, specifier, promise)",
" The module loader's dynamic import hook should call this when the module has"
" been loaded successfully."),
JS_FN_HELP("abortDynamicModuleImport", AbortDynamicModuleImport, 4, 0,
"abortDynamicModuleImport(referrer, specifier, promise, error)",
" The module loader's dynamic import hook should call this when the module "
" import has failed."),
JS_FN_HELP("setModulePrivate", ShellSetModulePrivate, 2, 0,
"setModulePrivate(scriptObject, privateValue)",
" Associate a private value with a module object.\n"),
JS_FN_HELP("getModulePrivate", ShellGetModulePrivate, 2, 0,
"getModulePrivate(scriptObject)",
" Get the private value associated with a module object.\n"),
JS_FN_HELP("getModuleLoadPath", GetModuleLoadPath, 0, 0,
"getModuleLoadPath()",
" Return any --module-load-path argument passed to the shell. Used by the\n"
" module loader.\n"),
" Set the HostResolveImportedModule hook to |function|, overriding the shell\n"
" module loader for testing purposes. This hook is used to look up a\n"
" previously loaded module object."),
#if defined(JS_BUILD_BINAST)
@ -10367,27 +10068,25 @@ static MOZ_MUST_USE bool ProcessArgs(JSContext* cx, OptionParser* op) {
return Process(cx, nullptr, forceTTY, FileScript);
}
if (const char* path = op->getStringOption("module-load-path")) {
RootedString jspath(cx, JS_NewStringCopyZ(cx, path));
RootedString moduleLoadPath(cx);
if (const char* option = op->getStringOption("module-load-path")) {
RootedString jspath(cx, JS_NewStringCopyZ(cx, option));
if (!jspath) {
return false;
}
JSString* absolutePath = js::shell::ResolvePath(cx, jspath, RootRelative);
if (!absolutePath) {
return false;
}
sc->moduleLoadPath = JS_EncodeStringToLatin1(cx, absolutePath);
moduleLoadPath = js::shell::ResolvePath(cx, jspath, RootRelative);
} else {
sc->moduleLoadPath = js::shell::GetCWD();
UniqueChars cwd = js::shell::GetCWD();
moduleLoadPath = JS_NewStringCopyZ(cx, cwd.get());
}
if (!sc->moduleLoadPath) {
if (!moduleLoadPath) {
return false;
}
if (!InitModuleLoader(cx)) {
sc->moduleLoader = js::MakeUnique<ModuleLoader>();
if (!sc->moduleLoader || !sc->moduleLoader->init(cx, moduleLoadPath)) {
return false;
}
@ -11866,10 +11565,6 @@ int main(int argc, char** argv, char** envp) {
js::SetPreserveWrapperCallback(cx, DummyPreserveWrapperCallback);
JS::SetModuleResolveHook(cx->runtime(), ShellModuleResolveHook);
JS::SetModuleDynamicImportHook(cx->runtime(), ShellModuleDynamicImportHook);
JS::SetModuleMetadataHook(cx->runtime(), CallModuleMetadataHook);
if (op.getBoolOption("disable-wasm-huge-memory")) {
if (!sCompilerProcessFlags.append("--disable-wasm-huge-memory")) {
return EXIT_FAILURE;

Просмотреть файл

@ -16,6 +16,7 @@
#include "builtin/MapObject.h"
#include "js/GCVector.h"
#include "shell/ModuleLoader.h"
#include "threading/ConditionVariable.h"
#include "threading/LockGuard.h"
#include "threading/Mutex.h"
@ -32,6 +33,15 @@
namespace js {
namespace shell {
// Define use of application-specific slots on the shell's global object.
enum GlobalAppSlot {
GlobalAppSlotModuleRegistry,
GlobalAppSlotModuleResolveHook, // HostResolveImportedModule
GlobalAppSlotCount
};
static_assert(GlobalAppSlotCount <= JSCLASS_GLOBAL_APPLICATION_SLOTS,
"Too many applications slots defined for shell global");
enum JSShellErrNum {
#define MSG_DEF(name, count, exception, format) name,
#include "jsshell.msg"
@ -232,7 +242,7 @@ struct ShellContext {
UniquePtr<ProfilingStack> geckoProfilingStack;
JS::UniqueChars moduleLoadPath;
UniquePtr<ModuleLoader> moduleLoader;
UniquePtr<MarkBitObservers> markObservers;
@ -250,6 +260,9 @@ extern ShellContext* GetShellContext(JSContext* cx);
extern MOZ_MUST_USE bool PrintStackTrace(JSContext* cx,
JS::Handle<JSObject*> stackObj);
extern JSObject* CreateScriptPrivate(JSContext* cx,
HandleString path = nullptr);
} /* namespace shell */
} /* namespace js */

Просмотреть файл

@ -19,6 +19,7 @@ UNIFIED_SOURCES += [
'js.cpp',
'jsoptparse.cpp',
'jsshell.cpp',
'ModuleLoader.cpp',
'OSObject.cpp',
'WasmTesting.cpp'
]
@ -38,15 +39,6 @@ LOCAL_INCLUDES += [
OS_LIBS += CONFIG['EDITLINE_LIBS']
# Prepare module loader JS code for embedding
GeneratedFile('shellmoduleloader.out.h', 'shellmoduleloader.js',
script='../builtin/embedjs.py',
entry_point='generate_shellmoduleloader',
inputs=[
'../js.msg',
'ModuleLoader.js',
])
# Place a GDB Python auto-load file next to the shell executable, both in
# the build directory and in the dist/bin directory.
DEFINES['topsrcdir'] = '%s/js/src' % TOPSRCDIR