mirror of
https://github.com/zotero/zotero.git
synced 2026-08-28 05:25:31 +00:00
fx140: Add working require.js, more progress toward startup
This commit is contained in:
parent
14f4881d04
commit
c506e41072
10 changed files with 923 additions and 1509 deletions
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
var { FilePicker } = ChromeUtils.importESModule('chrome://zotero/content/modules/filePicker.mjs');
|
||||
|
||||
const { BlockingObserver } = ChromeUtils.import("chrome://zotero/content/BlockingObserver.jsm");
|
||||
const { BlockingObserver } = ChromeUtils.importESModule("chrome://zotero/content/BlockingObserver.sys.mjs");
|
||||
|
||||
const ZipReader = Components.Constructor(
|
||||
"@mozilla.org/libjar/zip-reader;1",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
Components.utils.import("resource://zotero/pathparser.jsm", Zotero);
|
||||
Zotero.Router = Zotero.PathParser;
|
||||
delete Zotero.PathParser;
|
||||
const { PathParser } = ChromeUtils.importESModule("resource://zotero/pathparser.mjs");
|
||||
Zotero.Router = PathParser;
|
||||
|
||||
Zotero.Router.Utilities = {
|
||||
convertControllerToObjectType: function (params) {
|
||||
|
|
|
|||
|
|
@ -1810,7 +1810,7 @@ Zotero.Utilities.Internal = {
|
|||
},
|
||||
|
||||
serial: function (fn) {
|
||||
Components.utils.import("resource://zotero/concurrentCaller.js");
|
||||
const { ConcurrentCaller } = ChromeUtils.importESModule("resource://zotero/concurrentCaller.mjs");
|
||||
var caller = new ConcurrentCaller({
|
||||
numConcurrent: 1,
|
||||
onError: e => Zotero.logError(e)
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ export default [
|
|||
"resource/citeproc_rs*",
|
||||
"resource/csl-validator.js",
|
||||
"resource/jspath.js",
|
||||
"resource/loader.jsm",
|
||||
"resource/loader.mjs",
|
||||
"resource/pako.js",
|
||||
"resource/PluralForm.jsm",
|
||||
"resource/prop-types.js",
|
||||
|
|
|
|||
|
|
@ -23,20 +23,6 @@
|
|||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
var EXPORTED_SYMBOLS = ["ConcurrentCaller"];
|
||||
|
||||
if (!(typeof process === 'object' && process + '' === '[object process]')) {
|
||||
// Components.utils.import('resource://zotero/require.js');
|
||||
// Not using Cu.import here since we don't want the require module to be cached
|
||||
// for includes within ZoteroPane or other code where we want the window instance available to modules.
|
||||
Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Components.interfaces.mozIJSSubScriptLoader)
|
||||
.loadSubScript('resource://zotero/require.js');
|
||||
var Promise = require('resource://zotero/bluebird.js');
|
||||
} else {
|
||||
Promise = require('bluebird');
|
||||
}
|
||||
|
||||
/**
|
||||
* Call a fixed number of functions at once, queueing the rest until slots
|
||||
* open and returning a promise for the final completion. The functions do
|
||||
|
|
@ -69,7 +55,7 @@ if (!(typeof process === 'object' && process + '' === '[object process]')) {
|
|||
* @param {Object} [options.Promise] The Zotero instance of Promise to allow
|
||||
* stubbing/spying in tests
|
||||
*/
|
||||
var ConcurrentCaller = function (options = {}) {
|
||||
export var ConcurrentCaller = function (options = {}) {
|
||||
if (typeof options == 'number') {
|
||||
this._log("ConcurrentCaller now takes an object rather than a number");
|
||||
options = {
|
||||
|
|
@ -286,20 +272,16 @@ ConcurrentCaller.prototype._getIntervalNeeded = function () {
|
|||
* Wait until the specified interval has elapsed or the current pause (if there is one) is over,
|
||||
* whichever is longer
|
||||
*/
|
||||
ConcurrentCaller.prototype._waitForPause = Promise.coroutine(function* () {
|
||||
ConcurrentCaller.prototype._waitForPause = async function () {
|
||||
var interval = this._getIntervalNeeded();
|
||||
if (interval == 0) return;
|
||||
this._pausing = true;
|
||||
yield Promise.delay(interval);
|
||||
await new Promise(resolve => setTimeout(resolve, interval));
|
||||
this._pausing = false;
|
||||
});
|
||||
};
|
||||
|
||||
ConcurrentCaller.prototype._log = function (msg) {
|
||||
if (this._logger) {
|
||||
this._logger("[ConcurrentCaller] " + (this._id ? `[${this._id}] ` : "") + msg);
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof process === 'object' && process + '' === '[object process]'){
|
||||
module.exports = ConcurrentCaller;
|
||||
}
|
||||
1250
resource/loader.jsm
1250
resource/loader.jsm
File diff suppressed because it is too large
Load diff
653
resource/loader.sys.mjs
Normal file
653
resource/loader.sys.mjs
Normal file
|
|
@ -0,0 +1,653 @@
|
|||
/* 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/. */
|
||||
|
||||
/* exported Loader, resolveURI, Module, Require, unload */
|
||||
|
||||
const systemPrincipal = Components.Constructor(
|
||||
"@mozilla.org/systemprincipal;1",
|
||||
"nsIPrincipal"
|
||||
)();
|
||||
|
||||
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
||||
|
||||
const lazy = {};
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(
|
||||
lazy,
|
||||
"resProto",
|
||||
"@mozilla.org/network/protocol;1?name=resource",
|
||||
"nsIResProtocolHandler"
|
||||
);
|
||||
|
||||
ChromeUtils.defineESModuleGetters(
|
||||
lazy,
|
||||
{
|
||||
NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
|
||||
},
|
||||
{ global: "contextual" }
|
||||
);
|
||||
|
||||
const VENDOR_URI = "resource://devtools/client/shared/vendor/";
|
||||
const REACT_ESM_MODULES = new Set([
|
||||
VENDOR_URI + "react-dev.js",
|
||||
VENDOR_URI + "react.js",
|
||||
VENDOR_URI + "react-dom-dev.js",
|
||||
VENDOR_URI + "react-dom.js",
|
||||
VENDOR_URI + "react-dom-factories.js",
|
||||
VENDOR_URI + "react-dom-server-dev.js",
|
||||
VENDOR_URI + "react-dom-server.js",
|
||||
VENDOR_URI + "react-prop-types-dev.js",
|
||||
VENDOR_URI + "react-prop-types.js",
|
||||
VENDOR_URI + "react-test-renderer.js",
|
||||
]);
|
||||
|
||||
// Define some shortcuts.
|
||||
function* getOwnIdentifiers(x) {
|
||||
yield* Object.getOwnPropertyNames(x);
|
||||
yield* Object.getOwnPropertySymbols(x);
|
||||
}
|
||||
|
||||
function isJSONURI(uri) {
|
||||
return uri.endsWith(".json");
|
||||
}
|
||||
function isESMURI(uri) {
|
||||
return uri.endsWith(".mjs");
|
||||
}
|
||||
function isJSURI(uri) {
|
||||
return uri.endsWith(".js");
|
||||
}
|
||||
const AbsoluteRegExp = /^(resource|chrome|file|jar):/;
|
||||
function isAbsoluteURI(uri) {
|
||||
return AbsoluteRegExp.test(uri);
|
||||
}
|
||||
function isRelative(id) {
|
||||
return id.startsWith(".");
|
||||
}
|
||||
|
||||
function readURI(uri) {
|
||||
const nsURI = lazy.NetUtil.newURI(uri);
|
||||
if (nsURI.scheme == "resource") {
|
||||
// Resolve to a real URI, this will catch any obvious bad paths without
|
||||
// logging assertions in debug builds, see bug 1135219
|
||||
uri = lazy.resProto.resolveURI(nsURI);
|
||||
}
|
||||
|
||||
const stream = lazy.NetUtil.newChannel({
|
||||
uri: lazy.NetUtil.newURI(uri, "UTF-8"),
|
||||
loadUsingSystemPrincipal: true,
|
||||
}).open();
|
||||
const count = stream.available();
|
||||
const data = lazy.NetUtil.readInputStreamToString(stream, count, {
|
||||
charset: "UTF-8",
|
||||
});
|
||||
|
||||
stream.close();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// Combines all arguments into a resolved, normalized path
|
||||
function join(base, ...paths) {
|
||||
// If this is an absolute URL, we need to normalize only the path portion,
|
||||
// or we wind up stripping too many slashes and producing invalid URLs.
|
||||
const match = /^((?:resource|file|chrome)\:\/\/[^\/]*|jar:[^!]+!)(.*)/.exec(
|
||||
base
|
||||
);
|
||||
if (match) {
|
||||
return match[1] + normalize([match[2], ...paths].join("/"));
|
||||
}
|
||||
|
||||
return normalize([base, ...paths].join("/"));
|
||||
}
|
||||
|
||||
// Function takes set of options and returns a JS sandbox. Function may be
|
||||
// passed set of options:
|
||||
// - `name`: A string value which identifies the sandbox in about:memory. Will
|
||||
// throw exception if omitted.
|
||||
// - `prototype`: Ancestor for the sandbox that will be created. Defaults to
|
||||
// `{}`.
|
||||
function Sandbox(options) {
|
||||
// Normalize options and rename to match `Cu.Sandbox` expectations.
|
||||
const sandboxOptions = {
|
||||
// This will allow exposing Components as well as Cu, Ci and Cr.
|
||||
wantComponents: true,
|
||||
|
||||
// By default, Sandbox come with a very limited set of global.
|
||||
// The list of all available symbol names is available over there:
|
||||
// https://searchfox.org/mozilla-central/rev/31368c7795f44b7a15531d6c5e52dc97f82cf2d5/js/xpconnect/src/Sandbox.cpp#905-997
|
||||
// Request to expose all meaningful global here:
|
||||
wantGlobalProperties: [
|
||||
"AbortController",
|
||||
"atob",
|
||||
"btoa",
|
||||
"Blob",
|
||||
"crypto",
|
||||
"ChromeUtils",
|
||||
"CSS",
|
||||
"CSSRule",
|
||||
"CustomStateSet",
|
||||
"DOMParser",
|
||||
"Element",
|
||||
"Event",
|
||||
"FileReader",
|
||||
"FormData",
|
||||
"Headers",
|
||||
"InspectorCSSParser",
|
||||
"InspectorUtils",
|
||||
"MIDIInputMap",
|
||||
"MIDIOutputMap",
|
||||
"Node",
|
||||
"TextDecoder",
|
||||
"TextEncoder",
|
||||
"TrustedHTML",
|
||||
"TrustedScript",
|
||||
"TrustedScriptURL",
|
||||
"URL",
|
||||
"URLSearchParams",
|
||||
"Window",
|
||||
"XMLHttpRequest",
|
||||
],
|
||||
|
||||
sandboxName: options.name,
|
||||
sandboxPrototype: "prototype" in options ? options.prototype : {},
|
||||
freshCompartment: options.freshCompartment || false,
|
||||
};
|
||||
|
||||
return Cu.Sandbox(systemPrincipal, sandboxOptions);
|
||||
}
|
||||
|
||||
// This allows defining some modules in AMD format while retaining CommonJS
|
||||
// compatibility with this loader by allowing the factory function to have
|
||||
// access to general CommonJS functions, e.g.
|
||||
//
|
||||
// define(function(require, exports, module) {
|
||||
// ... code ...
|
||||
// });
|
||||
function define(factory) {
|
||||
factory(this.require, this.exports, this.module);
|
||||
}
|
||||
|
||||
// Populates `exports` of the given CommonJS `module` object, in the context
|
||||
// of the given `loader` by evaluating code associated with it.
|
||||
function load(loader, module) {
|
||||
const require = Require(loader, module);
|
||||
|
||||
// We expose set of properties defined by `CommonJS` specification via
|
||||
// prototype of the sandbox. Also globals are deeper in the prototype
|
||||
// chain so that each module has access to them as well.
|
||||
const properties = {
|
||||
require,
|
||||
module,
|
||||
exports: module.exports,
|
||||
};
|
||||
if (loader.supportAMDModules) {
|
||||
properties.define = define;
|
||||
}
|
||||
|
||||
// Create a new object in the shared global of the loader, that will be used
|
||||
// as the scope object for this particular module.
|
||||
const scopeFromSharedGlobal = new loader.sharedGlobal.Object();
|
||||
Object.assign(scopeFromSharedGlobal, properties);
|
||||
|
||||
const originalExports = module.exports;
|
||||
try {
|
||||
Services.scriptloader.loadSubScript(module.uri, scopeFromSharedGlobal);
|
||||
} catch (error) {
|
||||
// loadSubScript sometime throws string errors, which includes no stack.
|
||||
// At least provide the current stack by re-throwing a real Error object.
|
||||
if (typeof error == "string") {
|
||||
if (
|
||||
error.startsWith("Error creating URI") ||
|
||||
error.startsWith("Error opening input stream (invalid filename?)")
|
||||
) {
|
||||
throw new Error(
|
||||
`Module \`${module.id}\` is not found at ${module.uri}`
|
||||
);
|
||||
}
|
||||
throw new Error(
|
||||
`Error while loading module \`${module.id}\` at ${module.uri}:` +
|
||||
"\n" +
|
||||
error
|
||||
);
|
||||
}
|
||||
// Otherwise just re-throw everything else which should have a stack
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Only freeze the exports object if we created it ourselves. Modules
|
||||
// which completely replace the exports object and still want it
|
||||
// frozen need to freeze it themselves.
|
||||
if (module.exports === originalExports) {
|
||||
Object.freeze(module.exports);
|
||||
}
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
// Utility function to normalize module `uri`s so they have `.js` extension.
|
||||
function normalizeExt(uri) {
|
||||
if (isJSURI(uri) || isJSONURI(uri) || isESMURI(uri)) {
|
||||
return uri;
|
||||
}
|
||||
return uri + ".js";
|
||||
}
|
||||
|
||||
// Utility function to join paths. In common case `base` is a
|
||||
// `requirer.uri` but in some cases it may be `baseURI`. In order to
|
||||
// avoid complexity we require `baseURI` with a trailing `/`.
|
||||
function resolve(id, base) {
|
||||
if (!isRelative(id)) {
|
||||
return id;
|
||||
}
|
||||
|
||||
const baseDir = dirname(base);
|
||||
|
||||
let resolved;
|
||||
if (baseDir.includes(":")) {
|
||||
resolved = join(baseDir, id);
|
||||
} else {
|
||||
resolved = normalize(`${baseDir}/${id}`);
|
||||
}
|
||||
|
||||
// Joining and normalizing removes the "./" from relative files.
|
||||
// We need to ensure the resolution still has the root
|
||||
if (base.startsWith("./")) {
|
||||
resolved = "./" + resolved;
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
function compileMapping(paths) {
|
||||
// Make mapping array that is sorted from longest path to shortest path.
|
||||
const mapping = Object.keys(paths)
|
||||
.sort((a, b) => b.length - a.length)
|
||||
.map(path => [path, paths[path]]);
|
||||
|
||||
const PATTERN = /([.\\?+*(){}[\]^$])/g;
|
||||
const escapeMeta = str => str.replace(PATTERN, "\\$1");
|
||||
|
||||
const patterns = [];
|
||||
paths = {};
|
||||
|
||||
for (let [path, uri] of mapping) {
|
||||
// Strip off any trailing slashes to make comparisons simpler
|
||||
if (path.endsWith("/")) {
|
||||
path = path.slice(0, -1);
|
||||
uri = uri.replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
paths[path] = uri;
|
||||
|
||||
// We only want to match path segments explicitly. Examples:
|
||||
// * "foo/bar" matches for "foo/bar"
|
||||
// * "foo/bar" matches for "foo/bar/baz"
|
||||
// * "foo/bar" does not match for "foo/bar-1"
|
||||
// * "foo/bar/" does not match for "foo/bar"
|
||||
// * "foo/bar/" matches for "foo/bar/baz"
|
||||
//
|
||||
// Check for an empty path, an exact match, or a substring match
|
||||
// with the next character being a forward slash.
|
||||
if (path == "") {
|
||||
patterns.push("");
|
||||
} else {
|
||||
patterns.push(`${escapeMeta(path)}(?=$|/)`);
|
||||
}
|
||||
}
|
||||
|
||||
const pattern = new RegExp(`^(${patterns.join("|")})`);
|
||||
|
||||
// This will replace the longest matching path mapping at the start of
|
||||
// the ID string with its mapped value.
|
||||
return id => {
|
||||
return id.replace(pattern, (m0, m1) => paths[m1]);
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveURI(id, mapping) {
|
||||
// Do not resolve if already a resource URI
|
||||
if (isAbsoluteURI(id)) {
|
||||
return normalizeExt(id);
|
||||
}
|
||||
|
||||
return normalizeExt(mapping(id));
|
||||
}
|
||||
|
||||
// Creates version of `require` that will be exposed to the given `module`
|
||||
// in the context of the given `loader`. Each module gets own limited copy
|
||||
// of `require` that is allowed to load only a modules that are associated
|
||||
// with it during link time.
|
||||
export function Require(loader, requirer) {
|
||||
const { modules, mapping, mappingCache, requireHook } = loader;
|
||||
|
||||
function require(id) {
|
||||
if (!id) {
|
||||
// Throw if `id` is not passed.
|
||||
throw Error(
|
||||
"You must provide a module name when calling require() from " +
|
||||
requirer.id,
|
||||
requirer.uri
|
||||
);
|
||||
}
|
||||
|
||||
if (requireHook) {
|
||||
return requireHook(id, _require);
|
||||
}
|
||||
|
||||
return _require(id);
|
||||
}
|
||||
|
||||
function _require(id) {
|
||||
let { uri, requirement } = getRequirements(id);
|
||||
|
||||
// Load all react modules as ES Modules, in the Browser Loader global.
|
||||
// For this we have to ensure using ChromeUtils.importESModule with `global:"current"`,
|
||||
// but executed from the Loader global scope. `syncImport` does that.
|
||||
if (REACT_ESM_MODULES.has(uri)) {
|
||||
// All CommonJS modules are still importing the .js/CommonJS version,
|
||||
// but we hack these require() call to load the ESM version.
|
||||
uri = uri.replace(/.js$/, ".mjs");
|
||||
}
|
||||
|
||||
let module = null;
|
||||
// If module is already cached by loader then just use it.
|
||||
if (uri in modules) {
|
||||
module = modules[uri];
|
||||
} else if (isESMURI(uri)) {
|
||||
module = modules[uri] = Module(requirement, uri);
|
||||
const rv = ChromeUtils.importESModule(uri, {
|
||||
global: "contextual",
|
||||
});
|
||||
module.exports = rv.default || rv;
|
||||
} else if (isJSONURI(uri)) {
|
||||
let data;
|
||||
|
||||
// First attempt to load and parse json uri
|
||||
// ex: `test.json`
|
||||
// If that doesn"t exist, check for `test.json.js`
|
||||
// for node parity
|
||||
try {
|
||||
data = JSON.parse(readURI(uri));
|
||||
module = modules[uri] = Module(requirement, uri);
|
||||
module.exports = data;
|
||||
} catch (err) {
|
||||
// If error thrown from JSON parsing, throw that, do not
|
||||
// attempt to find .json.js file
|
||||
if (err && /JSON\.parse/.test(err.message)) {
|
||||
throw err;
|
||||
}
|
||||
uri = uri + ".js";
|
||||
}
|
||||
}
|
||||
|
||||
// If not yet cached, load and cache it.
|
||||
// We also freeze module to prevent it from further changes
|
||||
// at runtime.
|
||||
if (!(uri in modules)) {
|
||||
// Many of the loader's functionalities are dependent
|
||||
// on modules[uri] being set before loading, so we set it and
|
||||
// remove it if we have any errors.
|
||||
module = modules[uri] = Module(requirement, uri);
|
||||
try {
|
||||
Object.freeze(load(loader, module));
|
||||
} catch (e) {
|
||||
// Clear out modules cache so we can throw on a second invalid require
|
||||
delete modules[uri];
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
return module.exports;
|
||||
}
|
||||
|
||||
// Resolution function taking a module name/path and
|
||||
// returning a resourceURI and a `requirement` used by the loader.
|
||||
// Used by both `require` and `require.resolve`.
|
||||
function getRequirements(id) {
|
||||
if (!id) {
|
||||
// Throw if `id` is not passed.
|
||||
throw Error(
|
||||
"you must provide a module name when calling require() from " +
|
||||
requirer.id,
|
||||
requirer.uri
|
||||
);
|
||||
}
|
||||
|
||||
let requirement, uri;
|
||||
|
||||
if (modules[id]) {
|
||||
uri = requirement = id;
|
||||
} else if (requirer) {
|
||||
// Resolve `id` to its requirer if it's relative.
|
||||
requirement = resolve(id, requirer.id);
|
||||
} else {
|
||||
requirement = id;
|
||||
}
|
||||
|
||||
// Resolves `uri` of module using loaders resolve function.
|
||||
if (!uri) {
|
||||
if (mappingCache.has(requirement)) {
|
||||
uri = mappingCache.get(requirement);
|
||||
} else {
|
||||
uri = resolveURI(requirement, mapping);
|
||||
mappingCache.set(requirement, uri);
|
||||
}
|
||||
}
|
||||
|
||||
// Throw if `uri` can not be resolved.
|
||||
if (!uri) {
|
||||
throw Error(
|
||||
"Module: Can not resolve '" +
|
||||
id +
|
||||
"' module required by " +
|
||||
requirer.id +
|
||||
" located at " +
|
||||
requirer.uri,
|
||||
requirer.uri
|
||||
);
|
||||
}
|
||||
|
||||
return { uri, requirement };
|
||||
}
|
||||
|
||||
// Expose the `resolve` function for this `Require` instance
|
||||
require.resolve = _require.resolve = function (id) {
|
||||
const { uri } = getRequirements(id);
|
||||
return uri;
|
||||
};
|
||||
|
||||
// This is like webpack's require.context. It returns a new require
|
||||
// function that prepends the prefix to any requests.
|
||||
require.context = prefix => {
|
||||
return id => {
|
||||
return require(prefix + id);
|
||||
};
|
||||
};
|
||||
|
||||
return require;
|
||||
}
|
||||
|
||||
// Makes module object that is made available to CommonJS modules when they
|
||||
// are evaluated, along with `exports` and `require`.
|
||||
export function Module(id, uri) {
|
||||
return Object.create(null, {
|
||||
id: { enumerable: true, value: id },
|
||||
exports: {
|
||||
enumerable: true,
|
||||
writable: true,
|
||||
value: Object.create(null),
|
||||
configurable: true,
|
||||
},
|
||||
uri: { value: uri },
|
||||
});
|
||||
}
|
||||
|
||||
// Takes `loader`, and unload `reason` string and notifies all observers that
|
||||
// they should cleanup after them-self.
|
||||
export function unload(loader, reason) {
|
||||
// subject is a unique object created per loader instance.
|
||||
// This allows any code to cleanup on loader unload regardless of how
|
||||
// it was loaded. To handle unload for specific loader subject may be
|
||||
// asserted against loader.destructor or require("@loader/unload")
|
||||
// Note: We don not destroy loader's module cache or sandboxes map as
|
||||
// some modules may do cleanup in subsequent turns of event loop. Destroying
|
||||
// cache may cause module identity problems in such cases.
|
||||
const subject = { wrappedJSObject: loader.destructor };
|
||||
Services.obs.notifyObservers(subject, "devtools:loader:destroy", reason);
|
||||
}
|
||||
|
||||
// Function makes new loader that can be used to load CommonJS modules.
|
||||
// Loader takes following options:
|
||||
// - `paths`: Mandatory dictionary of require path mapped to absolute URIs.
|
||||
// Object keys are path prefix used in require(), values are URIs where each
|
||||
// prefix should be mapped to.
|
||||
// - `globals`: Optional map of globals, that all module scopes will inherit
|
||||
// from. Map is also exposed under `globals` property of the returned loader
|
||||
// so it can be extended further later. Defaults to `{}`.
|
||||
// - `sandboxName`: String, name of the sandbox displayed in about:memory.
|
||||
// - `sandboxPrototype`: Object used to define globals on all module's
|
||||
// sandboxes.
|
||||
// - `requireHook`: Optional function used to replace native require function
|
||||
// from loader. This function receive the module path as first argument,
|
||||
// and native require method as second argument.
|
||||
export function Loader(options) {
|
||||
let { paths, globals } = options;
|
||||
if (!globals) {
|
||||
globals = {};
|
||||
}
|
||||
|
||||
// We create an identity object that will be dispatched on an unload
|
||||
// event as subject. This way unload listeners will be able to assert
|
||||
// which loader is unloaded. Please note that we intentionally don"t
|
||||
// use `loader` as subject to prevent a loader access leakage through
|
||||
// observer notifications.
|
||||
const destructor = Object.create(null);
|
||||
|
||||
const mapping = compileMapping(paths);
|
||||
|
||||
// Define pseudo modules.
|
||||
const builtinModuleExports = {
|
||||
"@loader/unload": destructor,
|
||||
"@loader/options": options,
|
||||
};
|
||||
|
||||
const modules = {};
|
||||
for (const id of Object.keys(builtinModuleExports)) {
|
||||
// We resolve `uri` from `id` since modules are cached by `uri`.
|
||||
const uri = resolveURI(id, mapping);
|
||||
const module = Module(id, uri);
|
||||
|
||||
// Lazily expose built-in modules in order to
|
||||
// allow them to be loaded lazily.
|
||||
Object.defineProperty(module, "exports", {
|
||||
enumerable: true,
|
||||
get() {
|
||||
return builtinModuleExports[id];
|
||||
},
|
||||
});
|
||||
|
||||
modules[uri] = module;
|
||||
}
|
||||
|
||||
let sharedGlobal;
|
||||
if (options.sharedGlobal) {
|
||||
sharedGlobal = options.sharedGlobal;
|
||||
} else {
|
||||
// Create the unique sandbox we will be using for all modules,
|
||||
// so that we prevent creating a new compartment per module.
|
||||
// The side effect is that all modules will share the same
|
||||
// global objects.
|
||||
sharedGlobal = Sandbox({
|
||||
name: options.sandboxName || "Zotero",
|
||||
prototype: options.sandboxPrototype || globals,
|
||||
freshCompartment: options.freshCompartment,
|
||||
});
|
||||
}
|
||||
|
||||
if (options.sharedGlobal || options.sandboxPrototype) {
|
||||
// If we were given a sharedGlobal or a sandboxPrototype, we have to define
|
||||
// the globals on the shared global directly. Note that this will not work
|
||||
// for callers who depend on being able to add globals after the loader was
|
||||
// created.
|
||||
for (const name of getOwnIdentifiers(globals)) {
|
||||
Object.defineProperty(
|
||||
sharedGlobal,
|
||||
name,
|
||||
Object.getOwnPropertyDescriptor(globals, name)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Loader object is just a representation of a environment
|
||||
// state. We mark its properties non-enumerable
|
||||
// as they are pure implementation detail that no one should rely upon.
|
||||
const returnObj = {
|
||||
destructor: { enumerable: false, value: destructor },
|
||||
globals: { enumerable: false, value: globals },
|
||||
mapping: { enumerable: false, value: mapping },
|
||||
mappingCache: { enumerable: false, value: new Map() },
|
||||
// Map of module objects indexed by module URIs.
|
||||
modules: { enumerable: false, value: modules },
|
||||
sharedGlobal: { enumerable: false, value: sharedGlobal },
|
||||
supportAMDModules: {
|
||||
enumerable: false,
|
||||
value: options.supportAMDModules || false,
|
||||
},
|
||||
requireHook: {
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
value: options.requireHook,
|
||||
},
|
||||
};
|
||||
|
||||
return Object.create(null, returnObj);
|
||||
}
|
||||
|
||||
// NB: These methods are from the UNIX implementation of OS.Path. Refactoring
|
||||
// this module to not use path methods on stringly-typed URIs is
|
||||
// non-trivial.
|
||||
function dirname(path) {
|
||||
let index = path.lastIndexOf("/");
|
||||
if (index == -1) {
|
||||
return ".";
|
||||
}
|
||||
while (index >= 0 && path[index] == "/") {
|
||||
--index;
|
||||
}
|
||||
return path.slice(0, index + 1);
|
||||
}
|
||||
|
||||
function normalize(path) {
|
||||
const stack = [];
|
||||
let absolute;
|
||||
if (path.length >= 0 && path[0] == "/") {
|
||||
absolute = true;
|
||||
} else {
|
||||
absolute = false;
|
||||
}
|
||||
path.split("/").forEach(function (v) {
|
||||
switch (v) {
|
||||
case "":
|
||||
case ".": // fallthrough
|
||||
break;
|
||||
case "..":
|
||||
if (!stack.length) {
|
||||
if (absolute) {
|
||||
throw new Error("Path is ill-formed: attempting to go past root");
|
||||
} else {
|
||||
stack.push("..");
|
||||
}
|
||||
} else if (stack[stack.length - 1] == "..") {
|
||||
stack.push("..");
|
||||
} else {
|
||||
stack.pop();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
stack.push(v);
|
||||
}
|
||||
});
|
||||
const string = stack.join("/");
|
||||
return absolute ? "/" + string : string;
|
||||
}
|
||||
|
|
@ -1,126 +0,0 @@
|
|||
/**
|
||||
* pathparser.js - tiny URL parser/router
|
||||
*
|
||||
* Copyright (c) 2014 Dan Stillman
|
||||
* License: MIT
|
||||
* https://github.com/dstillman/pathparser.js
|
||||
*/
|
||||
(function (root, factory) {
|
||||
// AMD/RequireJS
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define(factory);
|
||||
// CommonJS/Node
|
||||
} else if (typeof exports === 'object') {
|
||||
module.exports = factory();
|
||||
// Mozilla JSM
|
||||
} else if (typeof Components != 'undefined'
|
||||
&& typeof Components.utils != 'undefined'
|
||||
&& typeof Components.utils.import == 'function') {
|
||||
root.EXPORTED_SYMBOLS = ["PathParser"];
|
||||
root.PathParser = factory();
|
||||
// Browser global
|
||||
} else {
|
||||
root.PathParser = factory();
|
||||
}
|
||||
}(this, function () {
|
||||
"use strict";
|
||||
|
||||
var PathParser = function (params) {
|
||||
this.rules = [];
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
PathParser.prototype = (function () {
|
||||
function getParamsFromRule(rule, pathParts, queryParts) {
|
||||
var params = {};
|
||||
var missingParams = {};
|
||||
|
||||
if (!rule.allowMissingParams && rule.parts.length != pathParts.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse path components
|
||||
for (var i = 0; i < rule.parts.length; i++) {
|
||||
var rulePart = rule.parts[i];
|
||||
var part = pathParts[i];
|
||||
|
||||
if (part !== undefined) {
|
||||
if (rulePart.charAt(0) == ':') {
|
||||
params[rulePart.substr(1)] = part;
|
||||
continue;
|
||||
}
|
||||
else if (rulePart !== part) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (rulePart.charAt(0) != ':') {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
missingParams[rulePart.substr(1)] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse query strings
|
||||
for (var i = 0; i < queryParts.length; ++i) {
|
||||
var nameValue = queryParts[i].split('=', 2);
|
||||
var key = nameValue[0];
|
||||
// But ignore empty parameters and don't override named parameters
|
||||
if (nameValue.length == 2 && !params[key] && !missingParams[key]) {
|
||||
params[key] = nameValue[1];
|
||||
}
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
return {
|
||||
add: function (route, handler, autoPopulateOnMatch = true, allowMissingParams = true) {
|
||||
this.rules.push({
|
||||
parts: route.replace(/^\//, '').split('/'),
|
||||
handler: handler,
|
||||
autoPopulateOnMatch,
|
||||
allowMissingParams
|
||||
});
|
||||
},
|
||||
|
||||
run: function (url) {
|
||||
if (url && url.length) {
|
||||
url = url
|
||||
// Remove redundant slashes
|
||||
.replace(/\/+/g, '/')
|
||||
// Strip leading and trailing '/' (at end or before query string)
|
||||
.replace(/^\/|\/($|\?)/, '')
|
||||
// Strip fragment identifiers
|
||||
.replace(/#.*$/, '');
|
||||
}
|
||||
|
||||
var urlSplit = url.split('?', 2);
|
||||
var pathParts = urlSplit[0].split('/', 50);
|
||||
var queryParts = urlSplit[1] ? urlSplit[1].split('&', 50) : [];
|
||||
|
||||
for (var i=0; i < this.rules.length; i++) {
|
||||
var rule = this.rules[i];
|
||||
var params = getParamsFromRule(rule, pathParts, queryParts);
|
||||
if (params) {
|
||||
params.url = url;
|
||||
// Automatic parameter assignment
|
||||
if (rule.autoPopulateOnMatch && this.params) {
|
||||
for (var param in params) {
|
||||
this.params[param] = params[param];
|
||||
}
|
||||
}
|
||||
// Call handler with 'this' bound to parameter object
|
||||
if (rule.handler) {
|
||||
rule.handler.call(params);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
return PathParser;
|
||||
}));
|
||||
106
resource/pathparser.mjs
Normal file
106
resource/pathparser.mjs
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
/**
|
||||
* pathparser.js - tiny URL parser/router
|
||||
*
|
||||
* Copyright (c) 2014 Dan Stillman
|
||||
* License: MIT
|
||||
* https://github.com/dstillman/pathparser.js
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
export var PathParser = function (params) {
|
||||
this.rules = [];
|
||||
this.params = params;
|
||||
};
|
||||
|
||||
PathParser.prototype = (function () {
|
||||
function getParamsFromRule(rule, pathParts, queryParts) {
|
||||
var params = {};
|
||||
var missingParams = {};
|
||||
|
||||
if (!rule.allowMissingParams && rule.parts.length != pathParts.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse path components
|
||||
for (var i = 0; i < rule.parts.length; i++) {
|
||||
var rulePart = rule.parts[i];
|
||||
var part = pathParts[i];
|
||||
|
||||
if (part !== undefined) {
|
||||
if (rulePart.charAt(0) == ':') {
|
||||
params[rulePart.substr(1)] = part;
|
||||
continue;
|
||||
}
|
||||
else if (rulePart !== part) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (rulePart.charAt(0) != ':') {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
missingParams[rulePart.substr(1)] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse query strings
|
||||
for (var i = 0; i < queryParts.length; ++i) {
|
||||
var nameValue = queryParts[i].split('=', 2);
|
||||
var key = nameValue[0];
|
||||
// But ignore empty parameters and don't override named parameters
|
||||
if (nameValue.length == 2 && !params[key] && !missingParams[key]) {
|
||||
params[key] = nameValue[1];
|
||||
}
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
return {
|
||||
add: function (route, handler, autoPopulateOnMatch = true, allowMissingParams = true) {
|
||||
this.rules.push({
|
||||
parts: route.replace(/^\//, '').split('/'),
|
||||
handler: handler,
|
||||
autoPopulateOnMatch,
|
||||
allowMissingParams
|
||||
});
|
||||
},
|
||||
|
||||
run: function (url) {
|
||||
if (url && url.length) {
|
||||
url = url
|
||||
// Remove redundant slashes
|
||||
.replace(/\/+/g, '/')
|
||||
// Strip leading and trailing '/' (at end or before query string)
|
||||
.replace(/^\/|\/($|\?)/, '')
|
||||
// Strip fragment identifiers
|
||||
.replace(/#.*$/, '');
|
||||
}
|
||||
|
||||
var urlSplit = url.split('?', 2);
|
||||
var pathParts = urlSplit[0].split('/', 50);
|
||||
var queryParts = urlSplit[1] ? urlSplit[1].split('&', 50) : [];
|
||||
|
||||
for (var i=0; i < this.rules.length; i++) {
|
||||
var rule = this.rules[i];
|
||||
var params = getParamsFromRule(rule, pathParts, queryParts);
|
||||
if (params) {
|
||||
params.url = url;
|
||||
// Automatic parameter assignment
|
||||
if (rule.autoPopulateOnMatch && this.params) {
|
||||
for (var param in params) {
|
||||
this.params[param] = params[param];
|
||||
}
|
||||
}
|
||||
// Call handler with 'this' bound to parameter object
|
||||
if (rule.handler) {
|
||||
rule.handler.call(params);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
|
@ -1,114 +1,164 @@
|
|||
'use strict';
|
||||
|
||||
var require = (function() {
|
||||
var win, cons, Zotero;
|
||||
Components.utils.import('resource://zotero/loader.jsm');
|
||||
var requirer = Module('/', '/');
|
||||
var _runningTimers = {};
|
||||
if (typeof window != 'undefined') {
|
||||
win = window;
|
||||
} else {
|
||||
win = {};
|
||||
/* 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/. */
|
||||
|
||||
win.setTimeout = function (func, ms) {
|
||||
var id = Math.floor(Math.random() * (1000000000000 - 1)) + 1
|
||||
var useMethodjit = Components.utils.methodjit;
|
||||
var timer = Components.classes["@mozilla.org/timer;1"]
|
||||
.createInstance(Components.interfaces.nsITimer);
|
||||
timer.initWithCallback({"notify":function() {
|
||||
// Remove timer from object so it can be garbage collected
|
||||
delete _runningTimers[id];
|
||||
|
||||
// Execute callback function
|
||||
try {
|
||||
func();
|
||||
} catch(err) {
|
||||
// Rethrow errors that occur so that they appear in the error
|
||||
// console with the appropriate name and line numbers. While the
|
||||
// the errors appear without this, the line numbers get eaten.
|
||||
var scriptError = Components.classes["@mozilla.org/scripterror;1"]
|
||||
.createInstance(Components.interfaces.nsIScriptError);
|
||||
scriptError.init(
|
||||
err.message || err.toString(),
|
||||
err.fileName || err.filename || null,
|
||||
null,
|
||||
err.lineNumber || null,
|
||||
null,
|
||||
scriptError.errorFlag,
|
||||
'component javascript'
|
||||
);
|
||||
Components.classes["@mozilla.org/consoleservice;1"]
|
||||
.getService(Components.interfaces.nsIConsoleService)
|
||||
.logMessage(scriptError);
|
||||
typeof Zotero !== 'undefined' && Zotero.debug(err.stack, 1);
|
||||
}
|
||||
}}, ms, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
|
||||
_runningTimers[id] = timer;
|
||||
return id;
|
||||
};
|
||||
|
||||
win.clearTimeout = function (id) {
|
||||
var timer = _runningTimers[id];
|
||||
if (timer) {
|
||||
timer.cancel();
|
||||
}
|
||||
delete _runningTimers[id];
|
||||
};
|
||||
/**
|
||||
* Manages the base loader (loader.sys.mjs) instance used to load CJS modules.
|
||||
*/
|
||||
|
||||
win.debug = function (msg) {
|
||||
dump(msg + "\n\n");
|
||||
};
|
||||
}
|
||||
|
||||
function getZotero() {
|
||||
if (win.Zotero) Zotero = win.Zotero;
|
||||
|
||||
if (typeof Zotero === 'undefined') {
|
||||
try {
|
||||
Zotero = Components.classes["@zotero.org/Zotero;1"]
|
||||
.getService(Components.interfaces.nsISupports).wrappedJSObject;
|
||||
} catch (e) {}
|
||||
}
|
||||
return Zotero || {};
|
||||
const {
|
||||
Loader,
|
||||
Require,
|
||||
resolveURI,
|
||||
unload,
|
||||
} = ChromeUtils.importESModule("resource://zotero/loader.sys.mjs");
|
||||
|
||||
const DEFAULT_SANDBOX_NAME = "Zotero (Module loader)";
|
||||
|
||||
var gNextLoaderID = 0;
|
||||
|
||||
/**
|
||||
* The main loader API. The standard instance of this loader is exported as
|
||||
* |loader| below, but if a fresh copy of the loader is needed, then a new
|
||||
* one can also be created.
|
||||
*
|
||||
* The two following boolean flags are used to control the sandboxes into
|
||||
* which the modules are loaded.
|
||||
* @param freshCompartment boolean
|
||||
* If true, the modules will be forced to be loaded in a distinct
|
||||
* compartment. It is typically used to load the modules in a distinct
|
||||
* system compartment, different from the main one, which is shared by
|
||||
* all ESMs, XPCOMs and modules loaded with this flag set to true.
|
||||
* We use this in order to debug modules loaded in this shared system
|
||||
* compartment. The debugger actor has to be running in a distinct
|
||||
* compartment than the context it is debugging.
|
||||
* @param useLoaderGlobal boolean
|
||||
* If true, the loader will reuse the current global to load other
|
||||
* modules instead of creating a sandbox with custom options. Cannot be
|
||||
* used with freshCompartment.
|
||||
*/
|
||||
function ZoteroLoader({
|
||||
freshCompartment = false,
|
||||
useLoaderGlobal = false,
|
||||
} = {}) {
|
||||
if (useLoaderGlobal && freshCompartment) {
|
||||
throw new Error(
|
||||
"Loader cannot use freshCompartment if useLoaderGlobal is true"
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof win.console !== 'undefined') {
|
||||
cons = console;
|
||||
}
|
||||
if (!cons) {
|
||||
cons = {};
|
||||
for (let key of ['log', 'warn', 'error']) {
|
||||
cons[key] = text => {getZotero(); typeof Zotero !== 'undefined' && false && Zotero.debug(`console.${key}: ${text}`)};
|
||||
}
|
||||
}
|
||||
if (!win.console) {
|
||||
win.console = cons;
|
||||
}
|
||||
let globals = {
|
||||
window: win,
|
||||
document: typeof win.document !== 'undefined' && win.document || {},
|
||||
console: cons,
|
||||
navigator: typeof win.navigator !== 'undefined' && win.navigator || {},
|
||||
setTimeout: win.setTimeout,
|
||||
clearTimeout: win.clearTimeout,
|
||||
requestAnimationFrame: win.setTimeout,
|
||||
cancelAnimationFrame: win.clearTimeout,
|
||||
IOUtils: IOUtils,
|
||||
PathUtils: PathUtils,
|
||||
TextEncoder: TextEncoder,
|
||||
TextDecoder: TextDecoder,
|
||||
const paths = {
|
||||
'': 'resource://zotero/',
|
||||
'containers/': 'chrome://zotero/content/containers/',
|
||||
'components/': 'chrome://zotero/content/components/',
|
||||
'zotero/': 'chrome://zotero/content/'
|
||||
};
|
||||
Object.defineProperty(globals, 'Zotero', { get: getZotero });
|
||||
var loader = Loader({
|
||||
id: 'zotero/require',
|
||||
paths: {
|
||||
'': 'resource://zotero/',
|
||||
'containers/': 'chrome://zotero/content/containers/',
|
||||
'components/': 'chrome://zotero/content/components/',
|
||||
'zotero/': 'chrome://zotero/content/',
|
||||
|
||||
// In case the Loader ESM is loaded in the existing global,
|
||||
// also reuse this global for all CommonJS modules.
|
||||
const sharedGlobal =
|
||||
useLoaderGlobal ||
|
||||
// eslint-disable-next-line mozilla/reject-globalThis-modification
|
||||
Cu.getRealmLocation(globalThis) == "Zotero global"
|
||||
? Cu.getGlobalForObject({})
|
||||
: undefined;
|
||||
this.loader = new Loader({
|
||||
paths,
|
||||
sharedGlobal,
|
||||
freshCompartment,
|
||||
sandboxName: useLoaderGlobal
|
||||
? "Zotero (Server Module Loader)"
|
||||
: DEFAULT_SANDBOX_NAME,
|
||||
// Make sure `define` function exists. JSON Viewer needs modules in AMD
|
||||
// format, as it currently uses RequireJS from a content document and
|
||||
// can't access our usual loaders. So, any modules shared with the JSON
|
||||
// Viewer should include a define wrapper:
|
||||
//
|
||||
// // Make this available to both AMD and CJS environments
|
||||
// define(function(require, exports, module) {
|
||||
// ... code ...
|
||||
// });
|
||||
//
|
||||
supportAMDModules: true,
|
||||
requireHook: (id, require) => {
|
||||
// if (id.startsWith("raw!") || id.startsWith("theme-loader!")) {
|
||||
// return requireRawId(id, require);
|
||||
// }
|
||||
return require(id);
|
||||
},
|
||||
globals
|
||||
});
|
||||
let require = Require(loader, requirer);
|
||||
return require;
|
||||
})();
|
||||
|
||||
this.require = Require(this.loader, { id: "zotero" });
|
||||
|
||||
// Various globals are available from ESM, but not from sandboxes,
|
||||
// inject them into the globals list.
|
||||
// Changes here should be mirrored to .eslintrc.
|
||||
const injectedGlobals = {
|
||||
BrowsingContext,
|
||||
CanonicalBrowsingContext,
|
||||
ChromeWorker,
|
||||
console,
|
||||
DebuggerNotificationObserver,
|
||||
DOMPoint,
|
||||
DOMQuad,
|
||||
DOMRect,
|
||||
fetch,
|
||||
HeapSnapshot,
|
||||
IOUtils,
|
||||
L10nRegistry,
|
||||
Localization,
|
||||
NamedNodeMap,
|
||||
NodeFilter,
|
||||
PathUtils,
|
||||
Services,
|
||||
StructuredCloneHolder,
|
||||
WebExtensionPolicy,
|
||||
WebSocket,
|
||||
WindowGlobalChild,
|
||||
WindowGlobalParent,
|
||||
};
|
||||
for (const name in injectedGlobals) {
|
||||
this.loader.globals[name] = injectedGlobals[name];
|
||||
}
|
||||
|
||||
// Fetch custom pseudo modules and globals
|
||||
const { modules, globals } = {
|
||||
// TODO: TEMP: Stub this out
|
||||
modules: {},
|
||||
globals: {},
|
||||
}
|
||||
|
||||
// Register custom pseudo modules to the current loader instance
|
||||
for (const id in modules) {
|
||||
const uri = resolveURI(id, this.loader.mapping);
|
||||
this.loader.modules[uri] = {
|
||||
get exports() {
|
||||
return modules[id];
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Register custom globals to the current loader instance
|
||||
Object.defineProperties(
|
||||
this.loader.sharedGlobal,
|
||||
Object.getOwnPropertyDescriptors(globals)
|
||||
);
|
||||
|
||||
this.id = gNextLoaderID++;
|
||||
}
|
||||
|
||||
ZoteroLoader.prototype = {
|
||||
destroy(reason = "shutdown") {
|
||||
unload(this.loader, reason);
|
||||
delete this.loader;
|
||||
},
|
||||
};
|
||||
|
||||
// Export the standard instance of ZoteroLoader used by the tools.
|
||||
// TODO: Zotero: Not making require.js an ESM for now, so this isn't exposed
|
||||
// Should it be?
|
||||
let loader = new ZoteroLoader();
|
||||
|
||||
var require = loader.require;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue