voting_schemes-electionguard 0.24.2 → 0.24.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/Gemfile.lock +100 -68
  4. data/app/assets/javascripts/voting_schemes/electionguard/electionguard.js +1 -1
  5. data/lib/voting_schemes/electionguard/version.rb +1 -1
  6. data/public/assets/electionguard/attrs.data +0 -0
  7. data/public/assets/electionguard/attrs.js +1 -1
  8. data/public/assets/electionguard/bulletin_board-electionguard.data +0 -0
  9. data/public/assets/electionguard/bulletin_board-electionguard.js +1 -1
  10. data/public/assets/electionguard/console.html +89 -61
  11. data/public/assets/electionguard/electionguard.js +1 -1
  12. data/public/assets/electionguard/gmpy2.data +0 -0
  13. data/public/assets/electionguard/gmpy2.js +1 -1
  14. data/public/assets/electionguard/hypothesis.js +1 -1
  15. data/public/assets/electionguard/jsons.js +1 -1
  16. data/public/assets/electionguard/micropip.data +0 -0
  17. data/public/assets/electionguard/micropip.js +1 -1
  18. data/public/assets/electionguard/packages.json +1 -1
  19. data/public/assets/electionguard/packaging.data +0 -0
  20. data/public/assets/electionguard/packaging.js +1 -0
  21. data/public/assets/electionguard/pyasn1.js +1 -1
  22. data/public/assets/electionguard/pyodide.asm.data +0 -0
  23. data/public/assets/electionguard/pyodide.asm.js +11 -11
  24. data/public/assets/electionguard/pyodide.asm.wasm +0 -0
  25. data/public/assets/electionguard/pyodide.js +781 -333
  26. data/public/assets/electionguard/pyparsing.data +0 -0
  27. data/public/assets/electionguard/pyparsing.js +1 -0
  28. data/public/assets/electionguard/rsa.js +1 -1
  29. data/public/assets/electionguard/six.data +0 -0
  30. data/public/assets/electionguard/six.js +1 -0
  31. data/public/assets/electionguard/sortedcontainers.js +1 -1
  32. data/public/assets/electionguard/test.data +0 -0
  33. data/public/assets/electionguard/test.html +21 -24
  34. data/public/assets/electionguard/test.js +1 -1
  35. data/public/assets/electionguard/typish.js +1 -1
  36. data/public/assets/electionguard/webworker.js +29 -21
  37. data/public/assets/electionguard/webworker_dev.js +14 -14
  38. metadata +8 -6
  39. data/public/assets/electionguard/distlib.data +0 -0
  40. data/public/assets/electionguard/distlib.js +0 -1
  41. data/public/assets/electionguard/pyodide.asm.data.js +0 -1
  42. data/public/assets/electionguard/renderedhtml.css +0 -209
@@ -2,270 +2,366 @@
2
2
  * The main bootstrap script for loading pyodide.
3
3
  */
4
4
 
5
- var languagePluginLoader = new Promise((resolve, reject) => {
6
- // Note: PYODIDE_BASE_URL is an environement variable replaced in
5
+ /**
6
+ * The :ref:`js-api-pyodide` module object. Must be present as a global variable
7
+ * called
8
+ * ``pyodide`` in order for package loading to work properly.
9
+ *
10
+ * @type Object
11
+ */
12
+ globalThis.pyodide = {};
13
+
14
+ /**
15
+ * Load the main Pyodide wasm module and initialize it. When finished stores the
16
+ * Pyodide module as a global object called ``pyodide``.
17
+ * @param {string} config.indexURL - The URL from which Pyodide will load
18
+ * packages
19
+ * @returns The Pyodide module.
20
+ * @async
21
+ */
22
+ globalThis.loadPyodide = async function(config = {}) {
23
+ if (globalThis.__pyodideLoading) {
24
+ if (globalThis.languagePluginURL) {
25
+ throw new Error(
26
+ "Pyodide is already loading because languagePluginURL is defined.");
27
+ } else {
28
+ throw new Error("Pyodide is already loading.");
29
+ }
30
+ }
31
+ globalThis.__pyodideLoading = true;
32
+ let Module = {};
33
+ // Note: PYODIDE_BASE_URL is an environment variable replaced in
7
34
  // in this template in the Makefile. It's recommended to always set
8
- // languagePluginUrl in any case.
9
- var baseURL = self.languagePluginUrl || './';
10
- baseURL = baseURL.substr(0, baseURL.lastIndexOf('/')) + '/';
35
+ // indexURL in any case.
36
+ let baseURL = config.indexURL || "./";
37
+ if (baseURL.endsWith(".js")) {
38
+ baseURL = baseURL.substr(0, baseURL.lastIndexOf('/'));
39
+ }
40
+ if (!baseURL.endsWith("/")) {
41
+ baseURL += '/';
42
+ }
11
43
 
12
44
  ////////////////////////////////////////////////////////////
13
45
  // Package loading
14
- let loadedPackages = {};
15
- var loadPackagePromise = new Promise((resolve) => resolve());
46
+ const DEFAULT_CHANNEL = "default channel";
47
+
16
48
  // Regexp for validating package name and URI
17
- var package_name_regexp = '[a-z0-9_][a-z0-9_\-]*'
18
- var package_uri_regexp =
19
- new RegExp('^https?://.*?(' + package_name_regexp + ').js$', 'i');
20
- var package_name_regexp = new RegExp('^' + package_name_regexp + '$', 'i');
21
-
22
- let _uri_to_package_name = (package_uri) => {
23
- // Generate a unique package name from URI
24
-
25
- if (package_name_regexp.test(package_uri)) {
26
- return package_uri;
27
- } else if (package_uri_regexp.test(package_uri)) {
28
- let match = package_uri_regexp.exec(package_uri);
29
- // Get the regexp group corresponding to the package name
49
+ const package_uri_regexp = /^.*?([^\/]*)\.js$/;
50
+
51
+ function _uri_to_package_name(package_uri) {
52
+ let match = package_uri_regexp.exec(package_uri);
53
+ if (match) {
30
54
  return match[1];
31
- } else {
32
- return null;
33
55
  }
34
56
  };
35
57
 
36
- // clang-format off
37
- let preloadWasm = () => {
38
- // On Chrome, we have to instantiate wasm asynchronously. Since that
39
- // can't be done synchronously within the call to dlopen, we instantiate
40
- // every .so that comes our way up front, caching it in the
41
- // `preloadedWasm` dictionary.
42
-
43
- let promise = new Promise((resolve) => resolve());
44
- let FS = pyodide._module.FS;
45
-
46
- function recurseDir(rootpath) {
47
- let dirs;
48
- try {
49
- dirs = FS.readdir(rootpath);
50
- } catch {
51
- return;
52
- }
53
- for (let entry of dirs) {
54
- if (entry.startsWith('.')) {
55
- continue;
56
- }
57
- const path = rootpath + entry;
58
- if (entry.endsWith('.so')) {
59
- if (Module['preloadedWasm'][path] === undefined) {
60
- promise = promise
61
- .then(() => Module['loadWebAssemblyModule'](
62
- FS.readFile(path), {loadAsync: true}))
63
- .then((module) => {
64
- Module['preloadedWasm'][path] = module;
65
- });
66
- }
67
- } else if (FS.isDir(FS.lookupPath(path).node.mode)) {
68
- recurseDir(path + '/');
69
- }
70
- }
71
- }
72
-
73
- recurseDir('/');
74
-
75
- return promise;
76
- }
77
- // clang-format on
78
-
79
- function loadScript(url, onload, onerror) {
80
- if (self.document) { // browser
58
+ let loadScript;
59
+ if (self.document) { // browser
60
+ loadScript = (url) => new Promise((res, rej) => {
81
61
  const script = self.document.createElement('script');
82
62
  script.src = url;
83
- script.onload = (e) => { onload(); };
84
- script.onerror = (e) => { onerror(); };
63
+ script.onload = res;
64
+ script.onerror = rej;
85
65
  self.document.head.appendChild(script);
86
- } else if (self.importScripts) { // webworker
87
- try {
88
- self.importScripts(url);
89
- onload();
90
- } catch {
91
- onerror();
92
- }
93
- }
94
- }
95
-
96
- let _loadPackage = (names, messageCallback, errorCallback) => {
97
- if (messageCallback == undefined) {
98
- messageCallback = () => {};
99
- }
100
- if (errorCallback == undefined) {
101
- errorCallback = () => {};
102
- }
103
- let _messageCallback = (msg) => {
104
- console.log(msg);
105
- messageCallback(msg);
106
- };
107
- let _errorCallback = (errMsg) => {
108
- console.error(errMsg);
109
- errorCallback(errMsg);
66
+ });
67
+ } else if (self.importScripts) { // webworker
68
+ loadScript = async (url) => { // This is async only for consistency
69
+ self.importScripts(url);
110
70
  };
71
+ } else {
72
+ throw new Error("Cannot determine runtime environment");
73
+ }
111
74
 
112
- // DFS to find all dependencies of the requested packages
113
- let packages = self.pyodide._module.packages.dependencies;
114
- let loadedPackages = self.pyodide.loadedPackages;
115
- let queue = [].concat(names || []);
116
- let toLoad = {};
117
- while (queue.length) {
118
- let package_uri = queue.pop();
119
-
120
- const pkg = _uri_to_package_name(package_uri);
75
+ function recursiveDependencies(names, _messageCallback, errorCallback,
76
+ sharedLibsOnly) {
77
+ const packages = Module.packages.dependencies;
78
+ const loadedPackages = Module.loadedPackages;
79
+ const sharedLibraries = Module.packages.shared_library;
80
+ const toLoad = new Map();
121
81
 
122
- if (pkg == null) {
123
- _errorCallback(`Invalid package name or URI '${package_uri}'`);
82
+ const addPackage = (pkg) => {
83
+ if (toLoad.has(pkg)) {
124
84
  return;
125
- } else if (pkg == package_uri) {
126
- package_uri = 'default channel';
127
85
  }
128
-
129
- if (pkg in loadedPackages) {
130
- if (package_uri != loadedPackages[pkg]) {
131
- _errorCallback(`URI mismatch, attempting to load package ` +
132
- `${pkg} from ${package_uri} while it is already ` +
133
- `loaded from ${loadedPackages[pkg]}!`);
134
- return;
135
- } else {
136
- _messageCallback(`${pkg} already loaded from ${loadedPackages[pkg]}`)
137
- }
138
- } else if (pkg in toLoad) {
139
- if (package_uri != toLoad[pkg]) {
140
- _errorCallback(`URI mismatch, attempting to load package ` +
141
- `${pkg} from ${package_uri} while it is already ` +
142
- `being loaded from ${toLoad[pkg]}!`);
143
- return;
86
+ toLoad.set(pkg, DEFAULT_CHANNEL);
87
+ // If the package is already loaded, we don't add dependencies, but warn
88
+ // the user later. This is especially important if the loaded package is
89
+ // from a custom url, in which case adding dependencies is wrong.
90
+ if (loadedPackages[pkg] !== undefined) {
91
+ return;
92
+ }
93
+ for (let dep of packages[pkg]) {
94
+ addPackage(dep);
95
+ }
96
+ };
97
+ for (let name of names) {
98
+ const pkgname = _uri_to_package_name(name);
99
+ if (pkgname !== undefined) {
100
+ if (toLoad.has(pkgname) && toLoad.get(pkgname) !== name) {
101
+ errorCallback(`Loading same package ${pkgname} from ${name} and ${
102
+ toLoad.get(pkgname)}`);
103
+ continue;
144
104
  }
105
+ toLoad.set(pkgname, name);
106
+ } else if (name in packages) {
107
+ addPackage(name);
145
108
  } else {
146
- console.log(
147
- `${pkg} to be loaded from ${package_uri}`); // debug level info.
148
-
149
- toLoad[pkg] = package_uri;
150
- if (packages.hasOwnProperty(pkg)) {
151
- packages[pkg].forEach((subpackage) => {
152
- if (!(subpackage in loadedPackages) && !(subpackage in toLoad)) {
153
- queue.push(subpackage);
154
- }
155
- });
156
- } else {
157
- _errorCallback(`Unknown package '${pkg}'`);
109
+ errorCallback(`Skipping unknown package '${name}'`);
110
+ }
111
+ }
112
+ if (sharedLibsOnly) {
113
+ let onlySharedLibs = new Map();
114
+ for (let c of toLoad) {
115
+ if (c[0] in sharedLibraries) {
116
+ onlySharedLibs.set(c[0], toLoad.get(c[0]));
158
117
  }
159
118
  }
119
+ return onlySharedLibs;
160
120
  }
121
+ return toLoad;
122
+ }
161
123
 
162
- self.pyodide._module.locateFile = (path) => {
124
+ async function _loadPackage(names, messageCallback, errorCallback) {
125
+ // toLoad is a map pkg_name => pkg_uri
126
+ let toLoad = recursiveDependencies(names, messageCallback, errorCallback);
127
+
128
+ // locateFile is the function used by the .js file to locate the .data
129
+ // file given the filename
130
+ Module.locateFile = (path) => {
163
131
  // handle packages loaded from custom URLs
164
132
  let pkg = path.replace(/\.data$/, "");
165
- if (pkg in toLoad) {
166
- let package_uri = toLoad[pkg];
167
- if (package_uri != 'default channel') {
133
+ if (toLoad.has(pkg)) {
134
+ let package_uri = toLoad.get(pkg);
135
+ if (package_uri != DEFAULT_CHANNEL) {
168
136
  return package_uri.replace(/\.js$/, ".data");
169
137
  };
170
138
  };
171
139
  return baseURL + path;
172
140
  };
173
141
 
174
- let promise = new Promise((resolve, reject) => {
175
- if (Object.keys(toLoad).length === 0) {
176
- resolve('No new packages to load');
177
- return;
178
- }
179
-
180
- let packageList = Array.from(Object.keys(toLoad));
181
- _messageCallback(`Loading ${packageList.join(', ')}`)
142
+ if (toLoad.size === 0) {
143
+ return Promise.resolve('No new packages to load');
144
+ } else {
145
+ let packageNames = Array.from(toLoad.keys()).join(', ');
146
+ messageCallback(`Loading ${packageNames}`);
147
+ }
182
148
 
183
- // monitorRunDependencies is called at the beginning and the end of each
184
- // package being loaded. We know we are done when it has been called
185
- // exactly "toLoad * 2" times.
186
- var packageCounter = Object.keys(toLoad).length * 2;
149
+ // If running in main browser thread, try to catch errors thrown when
150
+ // running a script. Since the script is added via a script tag, there is
151
+ // no good way to capture errors from the script only, so try to capture
152
+ // all errors them.
153
+ //
154
+ // windowErrorPromise rejects when any exceptions is thrown in the process
155
+ // of loading a script. The promise never resolves, and we combine it
156
+ // with other promises via Promise.race.
157
+ let windowErrorHandler;
158
+ let windowErrorPromise;
159
+ if (self.document) {
160
+ windowErrorPromise = new Promise((_res, rej) => {
161
+ windowErrorHandler = e => {
162
+ errorCallback(
163
+ "Unhandled error. We don't know what it is or whether it is related to 'loadPackage' but out of an abundance of caution we will assume that loading failed.");
164
+ errorCallback(e);
165
+ rej(e.message);
166
+ };
167
+ self.addEventListener('error', windowErrorHandler);
168
+ });
169
+ } else {
170
+ // This should be a promise that never resolves
171
+ windowErrorPromise = new Promise(() => {});
172
+ }
187
173
 
188
- self.pyodide._module.monitorRunDependencies = () => {
189
- packageCounter--;
190
- if (packageCounter === 0) {
191
- for (let pkg in toLoad) {
192
- self.pyodide.loadedPackages[pkg] = toLoad[pkg];
193
- }
194
- delete self.pyodide._module.monitorRunDependencies;
195
- self.removeEventListener('error', windowErrorHandler);
196
-
197
- let resolveMsg = `Loaded `;
198
- if (packageList.length > 0) {
199
- resolveMsg += packageList.join(', ');
200
- } else {
201
- resolveMsg += 'no packages'
202
- }
174
+ // This is a collection of promises that resolve when the package's JS file
175
+ // is loaded. The promises already handle error and never fail.
176
+ let scriptPromises = [];
177
+
178
+ for (let [pkg, uri] of toLoad) {
179
+ let loaded = Module.loadedPackages[pkg];
180
+ if (loaded !== undefined) {
181
+ // If uri is from the DEFAULT_CHANNEL, we assume it was added as a
182
+ // depedency, which was previously overridden.
183
+ if (loaded === uri || uri === DEFAULT_CHANNEL) {
184
+ messageCallback(`${pkg} already loaded from ${loaded}`);
185
+ continue;
186
+ } else {
187
+ errorCallback(
188
+ `URI mismatch, attempting to load package ${pkg} from ${uri} ` +
189
+ `while it is already loaded from ${
190
+ loaded}. To override a dependency, ` +
191
+ `load the custom package first.`);
192
+ continue;
193
+ }
194
+ }
195
+ let scriptSrc = uri === DEFAULT_CHANNEL ? `${baseURL}${pkg}.js` : uri;
196
+ messageCallback(`Loading ${pkg} from ${scriptSrc}`);
197
+ scriptPromises.push(loadScript(scriptSrc).catch(() => {
198
+ errorCallback(`Couldn't load package from URL ${scriptSrc}`);
199
+ toLoad.delete(pkg);
200
+ }));
201
+ }
203
202
 
204
- if (!isFirefox) {
205
- preloadWasm().then(() => {
206
- console.log(resolveMsg);
207
- resolve(resolveMsg);
208
- });
209
- } else {
210
- console.log(resolveMsg);
211
- resolve(resolveMsg);
203
+ // When the JS loads, it synchronously adds a runDependency to emscripten.
204
+ // It then loads the data file, and removes the runDependency from
205
+ // emscripten. This function returns a promise that resolves when there are
206
+ // no pending runDependencies.
207
+ function waitRunDependency() {
208
+ const promise = new Promise(r => {
209
+ Module.monitorRunDependencies = (n) => {
210
+ if (n === 0) {
211
+ r();
212
212
  }
213
- }
214
- };
213
+ };
214
+ });
215
+ // If there are no pending dependencies left, monitorRunDependencies will
216
+ // never be called. Since we can't check the number of dependencies,
217
+ // manually trigger a call.
218
+ Module.addRunDependency("dummy");
219
+ Module.removeRunDependency("dummy");
220
+ return promise;
221
+ }
215
222
 
216
- // Add a handler for any exceptions that are thrown in the process of
217
- // loading a package
218
- var windowErrorHandler = (err) => {
219
- delete self.pyodide._module.monitorRunDependencies;
223
+ // We must start waiting for runDependencies *after* all the JS files are
224
+ // loaded, since the number of runDependencies may happen to equal zero
225
+ // between package files loading.
226
+ let successPromise = Promise.all(scriptPromises).then(waitRunDependency);
227
+ try {
228
+ await Promise.race([ successPromise, windowErrorPromise ]);
229
+ } finally {
230
+ delete Module.monitorRunDependencies;
231
+ if (windowErrorHandler) {
220
232
  self.removeEventListener('error', windowErrorHandler);
221
- // Set up a new Promise chain, since this one failed
222
- loadPackagePromise = new Promise((resolve) => resolve());
223
- reject(err.message);
224
- };
225
- self.addEventListener('error', windowErrorHandler);
226
-
227
- for (let pkg in toLoad) {
228
- let scriptSrc;
229
- let package_uri = toLoad[pkg];
230
- if (package_uri == 'default channel') {
231
- scriptSrc = `${baseURL}${pkg}.js`;
232
- } else {
233
- scriptSrc = `${package_uri}`;
234
- }
235
- _messageCallback(`Loading ${pkg} from ${scriptSrc}`)
236
- loadScript(scriptSrc, () => {}, () => {
237
- // If the package_uri fails to load, call monitorRunDependencies twice
238
- // (so packageCounter will still hit 0 and finish loading), and remove
239
- // the package from toLoad so we don't mark it as loaded, and remove
240
- // the package from packageList so we don't say that it was loaded.
241
- _errorCallback(`Couldn't load package from URL ${scriptSrc}`);
242
- delete toLoad[pkg];
243
- let packageListIndex = packageList.indexOf(pkg);
244
- if (packageListIndex !== -1) {
245
- packageList.splice(packageListIndex, 1);
246
- }
247
- for (let i = 0; i < 2; i++) {
248
- self.pyodide._module.monitorRunDependencies();
249
- }
250
- });
251
233
  }
234
+ }
252
235
 
253
- // We have to invalidate Python's import caches, or it won't
254
- // see the new files. This is done here so it happens in parallel
255
- // with the fetching over the network.
256
- self.pyodide.runPython('import importlib as _importlib\n' +
257
- '_importlib.invalidate_caches()\n');
258
- });
236
+ let packageList = [];
237
+ for (let [pkg, uri] of toLoad) {
238
+ Module.loadedPackages[pkg] = uri;
239
+ packageList.push(pkg);
240
+ }
241
+
242
+ let resolveMsg;
243
+ if (packageList.length > 0) {
244
+ let packageNames = packageList.join(', ');
245
+ resolveMsg = `Loaded ${packageNames}`;
246
+ } else {
247
+ resolveMsg = 'No packages loaded';
248
+ }
249
+
250
+ Module.reportUndefinedSymbols();
251
+
252
+ messageCallback(resolveMsg);
259
253
 
260
- return promise;
254
+ // We have to invalidate Python's import caches, or it won't
255
+ // see the new files.
256
+ Module.runPythonSimple('import importlib\n' +
257
+ 'importlib.invalidate_caches()\n');
261
258
  };
262
259
 
263
- let loadPackage = (names, messageCallback, errorCallback) => {
264
- /* We want to make sure that only one loadPackage invocation runs at any
265
- * given time, so this creates a "chain" of promises. */
266
- loadPackagePromise = loadPackagePromise.then(
267
- () => _loadPackage(names, messageCallback, errorCallback));
268
- return loadPackagePromise;
260
+ // This is a promise that is resolved iff there are no pending package loads.
261
+ // It never fails.
262
+ let loadPackageChain = Promise.resolve();
263
+
264
+ /**
265
+ *
266
+ * The list of packages that Pyodide has loaded.
267
+ * Use ``Object.keys(pyodide.loadedPackages)`` to get the list of names of
268
+ * loaded packages, and ``pyodide.loadedPackages[package_name]`` to access
269
+ * install location for a particular ``package_name``.
270
+ *
271
+ * @type {object}
272
+ */
273
+ Module.loadedPackages = {};
274
+
275
+ /**
276
+ * Load a package or a list of packages over the network. This installs the
277
+ * package in the virtual filesystem. The package needs to be imported from
278
+ * Python before it can be used.
279
+ * @param {String | Array | PyProxy} names Either a single package name or URL
280
+ * or a list of them. URLs can be absolute or relative. The URLs must have
281
+ * file name
282
+ * ``<package-name>.js`` and there must be a file called
283
+ * ``<package-name>.data`` in the same directory. The argument can be a
284
+ * ``PyProxy`` of a list, in which case the list will be converted to
285
+ * Javascript and the ``PyProxy`` will be destroyed.
286
+ * @param {function} messageCallback A callback, called with progress messages
287
+ * (optional)
288
+ * @param {function} errorCallback A callback, called with error/warning
289
+ * messages (optional)
290
+ * @async
291
+ */
292
+ Module.loadPackage = async function(names, messageCallback, errorCallback) {
293
+ if (Module.isPyProxy(names)) {
294
+ let temp;
295
+ try {
296
+ temp = names.toJs();
297
+ } finally {
298
+ names.destroy();
299
+ }
300
+ names = temp;
301
+ }
302
+
303
+ if (!Array.isArray(names)) {
304
+ names = [ names ];
305
+ }
306
+ // get shared library packages and load those first
307
+ // otherwise bad things happen with linking them in firefox.
308
+ let sharedLibraryNames = [];
309
+ try {
310
+ let sharedLibraryPackagesToLoad =
311
+ recursiveDependencies(names, messageCallback, errorCallback, true);
312
+ for (let pkg of sharedLibraryPackagesToLoad) {
313
+ sharedLibraryNames.push(pkg[0]);
314
+ }
315
+ } catch (e) {
316
+ // do nothing - let the main load throw any errors
317
+ }
318
+ // override the load plugin so that it imports any dlls also
319
+ // this only needs to be done for shared library packages because
320
+ // we assume that if a package depends on a shared library
321
+ // it needs to have access to it.
322
+ // not needed for so in standard module because those are linked together
323
+ // correctly, it is only where linking goes across modules that it needs to
324
+ // be done. Hence we only put this extra preload plugin in during the shared
325
+ // library load
326
+ let oldPlugin;
327
+ for (let p in Module.preloadPlugins) {
328
+ if (Module.preloadPlugins[p].canHandle("test.so")) {
329
+ oldPlugin = Module.preloadPlugins[p];
330
+ break;
331
+ }
332
+ }
333
+ let dynamicLoadHandler = {
334
+ get : function(obj, prop) {
335
+ if (prop === 'handle') {
336
+ return function(bytes, name) {
337
+ obj[prop].apply(obj, arguments);
338
+ this["asyncWasmLoadPromise"] =
339
+ this["asyncWasmLoadPromise"].then(function() {
340
+ Module.loadDynamicLibrary(name,
341
+ {global : true, nodelete : true})
342
+ });
343
+ }
344
+ } else {
345
+ return obj[prop];
346
+ }
347
+ }
348
+ };
349
+ var loadPluginOverride = new Proxy(oldPlugin, dynamicLoadHandler);
350
+ // restore the preload plugin
351
+ Module.preloadPlugins.unshift(loadPluginOverride);
352
+
353
+ let promise = loadPackageChain.then(
354
+ () => _loadPackage(sharedLibraryNames, messageCallback || console.log,
355
+ errorCallback || console.error));
356
+ loadPackageChain = loadPackageChain.then(() => promise.catch(() => {}));
357
+ await promise;
358
+ Module.preloadPlugins.shift(loadPluginOverride);
359
+
360
+ promise = loadPackageChain.then(
361
+ () => _loadPackage(names, messageCallback || console.log,
362
+ errorCallback || console.error));
363
+ loadPackageChain = loadPackageChain.then(() => promise.catch(() => {}));
364
+ await promise;
269
365
  };
270
366
 
271
367
  ////////////////////////////////////////////////////////////
@@ -292,27 +388,35 @@ var languagePluginLoader = new Promise((resolve, reject) => {
292
388
  if (recursionLimit > 1000) {
293
389
  recursionLimit = 1000;
294
390
  }
295
- pyodide.runPython(
391
+ pyodide.runPythonSimple(
296
392
  `import sys; sys.setrecursionlimit(int(${recursionLimit}))`);
297
393
  };
298
394
 
299
395
  ////////////////////////////////////////////////////////////
300
396
  // Rearrange namespace for public API
397
+ // clang-format off
301
398
  let PUBLIC_API = [
302
399
  'globals',
400
+ 'pyodide_py',
401
+ 'version',
303
402
  'loadPackage',
403
+ 'loadPackagesFromImports',
304
404
  'loadedPackages',
405
+ 'isPyProxy',
305
406
  'pyimport',
306
- 'repr',
307
407
  'runPython',
308
408
  'runPythonAsync',
309
- 'checkABI',
310
- 'version',
311
- 'autocomplete',
409
+ 'registerJsModule',
410
+ 'unregisterJsModule',
411
+ 'setInterruptBuffer',
412
+ 'toPy',
413
+ 'PythonError',
312
414
  ];
415
+ // clang-format on
313
416
 
314
417
  function makePublicAPI(module, public_api) {
315
- var namespace = {_module : module};
418
+ let namespace = {_module : module};
419
+ module.public_api = namespace;
316
420
  for (let name of public_api) {
317
421
  namespace[name] = module[name];
318
422
  }
@@ -321,120 +425,464 @@ var languagePluginLoader = new Promise((resolve, reject) => {
321
425
 
322
426
  ////////////////////////////////////////////////////////////
323
427
  // Loading Pyodide
324
- let Module = {};
325
- self.Module = Module;
326
428
 
327
429
  Module.noImageDecoding = true;
328
430
  Module.noAudioDecoding = true;
329
- Module.noWasmDecoding = true;
431
+ Module.noWasmDecoding =
432
+ false; // we preload wasm using the built in plugin now
330
433
  Module.preloadedWasm = {};
331
- let isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
332
-
333
- Module.checkABI = function(ABI_number) {
334
- if (ABI_number !== parseInt('1')) {
335
- var ABI_mismatch_exception =
336
- `ABI numbers differ. Expected 1, got ${
337
- ABI_number}`;
338
- console.error(ABI_mismatch_exception);
339
- throw ABI_mismatch_exception;
434
+
435
+ let fatal_error_occurred = false;
436
+ Module.fatal_error = function(e) {
437
+ if (fatal_error_occurred) {
438
+ console.error("Recursive call to fatal_error. Inner error was:");
439
+ console.error(e);
440
+ return;
340
441
  }
341
- return true;
442
+ fatal_error_occurred = true;
443
+ console.error("Pyodide has suffered a fatal error. " +
444
+ "Please report this to the Pyodide maintainers.");
445
+ console.error("The cause of the fatal error was:")
446
+ console.error(e);
447
+ try {
448
+ let fd_stdout = 1;
449
+ Module.__Py_DumpTraceback(fd_stdout,
450
+ Module._PyGILState_GetThisThreadState());
451
+ for (let key of PUBLIC_API) {
452
+ if (key === "version") {
453
+ continue;
454
+ }
455
+ Object.defineProperty(Module.public_api, key, {
456
+ enumerable : true,
457
+ configurable : true,
458
+ get : () => {
459
+ throw new Error(
460
+ "Pyodide already fatally failed and can no longer be used.");
461
+ }
462
+ });
463
+ }
464
+ if (Module.on_fatal) {
465
+ Module.on_fatal(e);
466
+ }
467
+ } catch (e) {
468
+ console.error("Another error occurred while handling the fatal error:");
469
+ console.error(e);
470
+ }
471
+ throw e;
342
472
  };
343
473
 
344
- Module.autocomplete = function(path) {
345
- var pyodide_module = Module.pyimport("pyodide");
346
- return pyodide_module.get_completions(path);
474
+ /**
475
+ * An alias to the Python :py:mod:`pyodide` package.
476
+ *
477
+ * You can use this to call functions defined in the Pyodide Python package
478
+ * from Javascript.
479
+ *
480
+ * @type {PyProxy}
481
+ */
482
+ Module.pyodide_py = {}; // actually defined in runPythonSimple below
483
+
484
+ /**
485
+ *
486
+ * An alias to the global Python namespace.
487
+ *
488
+ * For example, to access a variable called ``foo`` in the Python global
489
+ * scope, use ``pyodide.globals.get("foo")``
490
+ *
491
+ * @type {PyProxy}
492
+ */
493
+ Module.globals = {}; // actually defined in runPythonSimple below
494
+
495
+ // clang-format off
496
+ /**
497
+ * A Javascript error caused by a Python exception.
498
+ *
499
+ * In order to reduce the risk of large memory leaks, the ``PythonError``
500
+ * contains no reference to the Python exception that caused it. You can find
501
+ * the actual Python exception that caused this error as `sys.last_value
502
+ * <https://docs.python.org/3/library/sys.html#sys.last_value>`_.
503
+ *
504
+ * See :ref:`type-translations-errors` for more information.
505
+ *
506
+ * .. admonition:: Avoid Stack Frames
507
+ * :class: warning
508
+ *
509
+ * If you make a :any:`PyProxy` of ``sys.last_value``, you should be
510
+ * especially careful to :any:`destroy() <PyProxy.destroy>` it when you are
511
+ * done. You may leak a large amount of memory including the local
512
+ * variables of all the stack frames in the traceback if you don't. The
513
+ * easiest way is to only handle the exception in Python.
514
+ *
515
+ * @class
516
+ */
517
+ Module.PythonError = class PythonError {
518
+ // actually defined in error_handling.c. TODO: would be good to move this
519
+ // documentation and the definition of PythonError to error_handling.js
520
+ constructor(){
521
+ /**
522
+ * The Python traceback.
523
+ * @type {string}
524
+ */
525
+ this.message;
526
+ }
347
527
  };
528
+ // clang-format on
348
529
 
349
- Module.locateFile = (path) => baseURL + path;
350
- var postRunPromise = new Promise((resolve, reject) => {
351
- Module.postRun = () => {
352
- delete self.Module;
353
- fetch(`${baseURL}packages.json`)
354
- .then((response) => response.json())
355
- .then((json) => {
356
- fixRecursionLimit(self.pyodide);
357
- self.pyodide.globals =
358
- self.pyodide.runPython('import sys\nsys.modules["__main__"]');
359
- self.pyodide = makePublicAPI(self.pyodide, PUBLIC_API);
360
- self.pyodide._module.packages = json;
361
- if (self.iodide !== undefined) {
362
- // Perform some completions immediately so there isn't a delay on
363
- // the first call to autocomplete
364
- self.pyodide.runPython('import pyodide');
365
- self.pyodide.runPython('pyodide.get_completions("")');
366
- }
367
- resolve();
368
- });
369
- };
370
- });
530
+ /**
531
+ *
532
+ * The Pyodide version.
533
+ *
534
+ * It can be either the exact release version (e.g. ``0.1.0``), or
535
+ * the latest release version followed by the number of commits since, and
536
+ * the git hash of the current commit (e.g. ``0.1.0-1-bd84646``).
537
+ *
538
+ * @type {string}
539
+ */
540
+ Module.version = ""; // Hack to make jsdoc behave
541
+
542
+ /**
543
+ * Run Python code in the simplest way possible. The primary purpose of this
544
+ * method is for bootstrapping. It is also useful for debugging: If the Python
545
+ * interpreter is initialized successfully then it should be possible to use
546
+ * this method to run Python code even if everything else in the Pyodide
547
+ * `core` module fails.
548
+ *
549
+ * The differences are:
550
+ * 1. `runPythonSimple` doesn't return anything (and so won't leak
551
+ * PyProxies)
552
+ * 2. `runPythonSimple` doesn't require access to any state on the
553
+ * Javascript `pyodide` module.
554
+ * 3. `runPython` uses `pyodide.eval_code`, whereas `runPythonSimple` uses
555
+ * `PyRun_String` which is the C API for `eval` / `exec`.
556
+ * 4. `runPythonSimple` runs with `globals` a separate dict which is called
557
+ * `init_dict` (keeps global state private)
558
+ * 5. `runPythonSimple` doesn't dedent the argument
559
+ *
560
+ * When `core` initialization is completed, the globals for `runPythonSimple`
561
+ * is made available as `Module.init_dict`.
562
+ *
563
+ * @private
564
+ */
565
+ Module.runPythonSimple = function(code) {
566
+ let code_c_string = Module.stringToNewUTF8(code);
567
+ let errcode;
568
+ try {
569
+ errcode = Module._run_python_simple_inner(code_c_string);
570
+ } catch (e) {
571
+ Module.fatal_error(e);
572
+ } finally {
573
+ Module._free(code_c_string);
574
+ }
575
+ if (errcode === -1) {
576
+ Module._pythonexc2js();
577
+ }
578
+ };
371
579
 
372
- var dataLoadPromise = new Promise((resolve, reject) => {
373
- Module.monitorRunDependencies =
374
- (n) => {
375
- if (n === 0) {
376
- delete Module.monitorRunDependencies;
377
- resolve();
378
- }
379
- }
380
- });
381
-
382
- Promise.all([ postRunPromise, dataLoadPromise ]).then(() => resolve());
383
-
384
- const data_script_src = `${baseURL}pyodide.asm.data.js`;
385
- loadScript(data_script_src, () => {
386
- const scriptSrc = `${baseURL}pyodide.asm.js`;
387
- loadScript(scriptSrc, () => {
388
- // The emscripten module needs to be at this location for the core
389
- // filesystem to install itself. Once that's complete, it will be replaced
390
- // by the call to `makePublicAPI` with a more limited public API.
391
- self.pyodide = pyodide(Module);
392
- self.pyodide.loadedPackages = {};
393
- self.pyodide.loadPackage = loadPackage;
394
- }, () => {});
395
- }, () => {});
580
+ /**
581
+ * Runs a string of Python code from Javascript.
582
+ *
583
+ * The last part of the string may be an expression, in which case, its value
584
+ * is returned.
585
+ *
586
+ * @param {string} code Python code to evaluate
587
+ * @param {dict} globals An optional Python dictionary to use as the globals.
588
+ * Defaults to :any:`pyodide.globals`. Uses the Python API
589
+ * :any:`pyodide.eval_code` to evaluate the code.
590
+ * @returns The result of the Python code translated to Javascript. See the
591
+ * documentation for :any:`pyodide.eval_code` for more info.
592
+ */
593
+ Module.runPython = function(code, globals = Module.globals) {
594
+ return Module.pyodide_py.eval_code(code, globals);
595
+ };
396
596
 
397
- ////////////////////////////////////////////////////////////
398
- // Iodide-specific functionality, that doesn't make sense
399
- // if not using with Iodide.
400
- if (self.iodide !== undefined) {
401
- // Load the custom CSS for Pyodide
402
- let link = document.createElement('link');
403
- link.rel = 'stylesheet';
404
- link.type = 'text/css';
405
- link.href = `${baseURL}renderedhtml.css`;
406
- document.getElementsByTagName('head')[0].appendChild(link);
407
-
408
- // Add a custom output handler for Python objects
409
- self.iodide.addOutputRenderer({
410
- shouldRender : (val) => {
411
- return (typeof val === 'function' &&
412
- pyodide._module.PyProxy.isPyProxy(val));
413
- },
414
-
415
- render : (val) => {
416
- let div = document.createElement('div');
417
- div.className = 'rendered_html';
418
- var element;
419
- if (val._repr_html_ !== undefined) {
420
- let result = val._repr_html_();
421
- if (typeof result === 'string') {
422
- div.appendChild(new DOMParser()
423
- .parseFromString(result, 'text/html')
424
- .body.firstChild);
425
- element = div;
426
- } else {
427
- element = result;
428
- }
429
- } else {
430
- let pre = document.createElement('pre');
431
- pre.textContent = val.toString();
432
- div.appendChild(pre);
433
- element = div;
434
- }
435
- return element.outerHTML;
597
+ // clang-format off
598
+ /**
599
+ * Inspect a Python code chunk and use :js:func:`pyodide.loadPackage` to
600
+ * install any known packages that the code chunk imports. Uses the Python API
601
+ * :func:`pyodide.find\_imports` to inspect the code.
602
+ *
603
+ * For example, given the following code as input
604
+ *
605
+ * .. code-block:: python
606
+ *
607
+ * import numpy as np x = np.array([1, 2, 3])
608
+ *
609
+ * :js:func:`loadPackagesFromImports` will call
610
+ * ``pyodide.loadPackage(['numpy'])``. See also :js:func:`runPythonAsync`.
611
+ *
612
+ * @param {string} code The code to inspect.
613
+ * @param {Function} messageCallback The ``messageCallback`` argument of
614
+ * :any:`pyodide.loadPackage` (optional).
615
+ * @param {Function} errorCallback The ``errorCallback`` argument of
616
+ * :any:`pyodide.loadPackage` (optional).
617
+ * @async
618
+ */
619
+ Module.loadPackagesFromImports = async function(code, messageCallback, errorCallback) {
620
+ let imports = Module.pyodide_py.find_imports(code).toJs();
621
+ if (imports.length === 0) {
622
+ return;
623
+ }
624
+ let packageNames = Module.packages.import_name_to_package_name;
625
+ let packages = new Set();
626
+ for (let name of imports) {
627
+ if (name in packageNames) {
628
+ packages.add(packageNames[name]);
436
629
  }
437
- });
438
- }
439
- });
440
- languagePluginLoader
630
+ }
631
+ if (packages.size) {
632
+ await Module.loadPackage(
633
+ Array.from(packages.keys()), messageCallback, errorCallback
634
+ );
635
+ }
636
+ };
637
+ // clang-format on
638
+
639
+ /**
640
+ * Access a Python object in the global namespace from Javascript.
641
+ *
642
+ * @deprecated This function will be removed in version 0.18.0. Use
643
+ * :any:`pyodide.globals.get('key') <pyodide.globals>` instead.
644
+ *
645
+ * @param {string} name Python variable name
646
+ * @returns The Python object translated to Javascript.
647
+ */
648
+ Module.pyimport = name => {
649
+ console.warn(
650
+ "Access to the Python global namespace via pyodide.pyimport is deprecated and " +
651
+ "will be removed in version 0.18.0. Use pyodide.globals.get('key') instead.");
652
+ return Module.globals.get(name);
653
+ };
654
+
655
+ /**
656
+ * Runs Python code, possibly asynchronously loading any known packages that
657
+ * the code imports. For example, given the following code
658
+ *
659
+ * .. code-block:: python
660
+ *
661
+ * import numpy as np
662
+ * x = np.array([1, 2, 3])
663
+ *
664
+ * Pyodide will first call :any:`pyodide.loadPackage(['numpy'])
665
+ * <pyodide.loadPackage>`, and then run the code using the Python API
666
+ * :any:`pyodide.eval_code_async`, returning the result. The code is compiled
667
+ * with `PyCF_ALLOW_TOP_LEVEL_AWAIT
668
+ * <https://docs.python.org/3/library/ast.html?highlight=pycf_allow_top_level_await#ast.PyCF_ALLOW_TOP_LEVEL_AWAIT>`_.
669
+ *
670
+ * For example:
671
+ *
672
+ * .. code-block:: pyodide
673
+ *
674
+ * let result = await pyodide.runPythonAsync(`
675
+ * # numpy will automatically be loaded by loadPackagesFromImports
676
+ * import numpy as np
677
+ * # we can use top level await
678
+ * from js import fetch
679
+ * response = await fetch("./packages.json")
680
+ * packages = await response.json()
681
+ * # If final statement is an expression, its value is returned to
682
+ * Javascript len(packages.dependencies.object_keys())
683
+ * `);
684
+ * console.log(result); // 72
685
+ *
686
+ * @param {string} code Python code to evaluate
687
+ * @param {Function} messageCallback The ``messageCallback`` argument of
688
+ * :any:`pyodide.loadPackage`.
689
+ * @param {Function} errorCallback The ``errorCallback`` argument of
690
+ * :any:`pyodide.loadPackage`.
691
+ * @returns The result of the Python code translated to Javascript.
692
+ * @async
693
+ */
694
+ Module.runPythonAsync = async function(code, messageCallback, errorCallback) {
695
+ await Module.loadPackagesFromImports(code, messageCallback, errorCallback);
696
+ let coroutine = Module.pyodide_py.eval_code_async(code, Module.globals);
697
+ try {
698
+ let result = await coroutine;
699
+ return result;
700
+ } finally {
701
+ coroutine.destroy();
702
+ }
703
+ };
704
+
705
+ // clang-format off
706
+ /**
707
+ * Registers the Javascript object ``module`` as a Javascript module named
708
+ * ``name``. This module can then be imported from Python using the standard
709
+ * Python import system. If another module by the same name has already been
710
+ * imported, this won't have much effect unless you also delete the imported
711
+ * module from ``sys.modules``. This calls the ``pyodide_py`` API
712
+ * :func:`pyodide.register_js_module`.
713
+ *
714
+ * @param {string} name Name of the Javascript module to add
715
+ * @param {object} module Javascript object backing the module
716
+ */
717
+ Module.registerJsModule = function(name, module) {
718
+ Module.pyodide_py.register_js_module(name, module);
719
+ };
720
+
721
+ /**
722
+ * Unregisters a Javascript module with given name that has been previously
723
+ * registered with :js:func:`pyodide.registerJsModule` or
724
+ * :func:`pyodide.register_js_module`. If a Javascript module with that name
725
+ * does not already exist, will throw an error. Note that if the module has
726
+ * already been imported, this won't have much effect unless you also delete
727
+ * the imported module from ``sys.modules``. This calls the ``pyodide_py`` API
728
+ * :func:`pyodide.unregister_js_module`.
729
+ *
730
+ * @param {string} name Name of the Javascript module to remove
731
+ */
732
+ Module.unregisterJsModule = function(name) {
733
+ Module.pyodide_py.unregister_js_module(name);
734
+ };
735
+ // clang-format on
736
+
737
+ /**
738
+ * Convert the Javascript object to a Python object as best as possible.
739
+ *
740
+ * This is similar to :any:`JsProxy.to_py` but for use from Javascript. If the
741
+ * object is immutable or a :any:`PyProxy`, it will be returned unchanged. If
742
+ * the object cannot be converted into Python, it will be returned unchanged.
743
+ *
744
+ * See :ref:`type-translations-jsproxy-to-py` for more information.
745
+ *
746
+ * @param {*} obj
747
+ * @param {number} depth Optional argument to limit the depth of the
748
+ * conversion.
749
+ * @returns {PyProxy} The object converted to Python.
750
+ */
751
+ Module.toPy = function(obj, depth = -1) {
752
+ // No point in converting these, it'd be dumb to proxy them so they'd just
753
+ // get converted back by `js2python` at the end
754
+ // clang-format off
755
+ switch (typeof obj) {
756
+ case "string":
757
+ case "number":
758
+ case "boolean":
759
+ case "bigint":
760
+ case "undefined":
761
+ return obj;
762
+ }
763
+ // clang-format on
764
+ if (!obj || Module.isPyProxy(obj)) {
765
+ return obj;
766
+ }
767
+ let obj_id = 0;
768
+ let py_result = 0;
769
+ let result = 0;
770
+ try {
771
+ obj_id = Module.hiwire.new_value(obj);
772
+ py_result = Module.__js2python_convert(obj_id, new Map(), depth);
773
+ // clang-format off
774
+ if(py_result === 0){
775
+ // clang-format on
776
+ Module._pythonexc2js();
777
+ }
778
+ if (Module._JsProxy_Check(py_result)) {
779
+ // Oops, just created a JsProxy. Return the original object.
780
+ return obj;
781
+ // return Module.pyproxy_new(py_result);
782
+ }
783
+ result = Module._python2js(py_result);
784
+ // clang-format off
785
+ if (result === 0) {
786
+ // clang-format on
787
+ Module._pythonexc2js();
788
+ }
789
+ } finally {
790
+ Module.hiwire.decref(obj_id);
791
+ Module._Py_DecRef(py_result);
792
+ }
793
+ return Module.hiwire.pop_value(result);
794
+ };
795
+ /**
796
+ * Is the argument a :any:`PyProxy`?
797
+ * @param jsobj {any} Object to test.
798
+ * @returns {bool} Is ``jsobj`` a :any:`PyProxy`?
799
+ */
800
+ Module.isPyProxy = function(jsobj) {
801
+ return !!jsobj && jsobj.$$ !== undefined && jsobj.$$.type === 'PyProxy';
802
+ };
803
+
804
+ Module.locateFile = (path) => baseURL + path;
805
+
806
+ let moduleLoaded = new Promise(r => Module.postRun = r);
807
+
808
+ const scriptSrc = `${baseURL}pyodide.asm.js`;
809
+
810
+ await loadScript(scriptSrc);
811
+
812
+ // _createPyodideModule is specified in the Makefile by the linker flag:
813
+ // `-s EXPORT_NAME="'_createPyodideModule'"`
814
+ await _createPyodideModule(Module);
815
+
816
+ // There is some work to be done between the module being "ready" and postRun
817
+ // being called.
818
+ await moduleLoaded;
819
+
820
+ // Bootstrap step: `runPython` needs access to `Module.globals` and
821
+ // `Module.pyodide_py`. Use `runPythonSimple` to add these. runPythonSimple
822
+ // doesn't dedent the argument so the indentation matters.
823
+ Module.runPythonSimple(`
824
+ def temp(Module):
825
+ import pyodide
826
+ import __main__
827
+ import builtins
828
+
829
+ globals = __main__.__dict__
830
+ globals.update(builtins.__dict__)
831
+
832
+ Module.version = pyodide.__version__
833
+ Module.globals = globals
834
+ Module.builtins = builtins.__dict__
835
+ Module.pyodide_py = pyodide
836
+ `);
837
+
838
+ Module.saveState = () => Module.pyodide_py._state.save_state();
839
+ Module.restoreState = (state) =>
840
+ Module.pyodide_py._state.restore_state(state);
841
+
842
+ Module.init_dict.get("temp")(Module);
843
+ // Module.runPython works starting from here!
844
+
845
+ // Wrap "globals" in a special Proxy that allows `pyodide.globals.x` access.
846
+ // TODO: Should we have this?
847
+ Module.globals = Module.wrapNamespace(Module.globals);
848
+
849
+ let response = await fetch(`${baseURL}packages.json`);
850
+ Module.packages = await response.json();
851
+
852
+ fixRecursionLimit(Module);
853
+ let pyodide = makePublicAPI(Module, PUBLIC_API);
854
+ Module.registerJsModule("js", globalThis);
855
+ Module.registerJsModule("pyodide_js", pyodide);
856
+ globalThis.pyodide = pyodide;
857
+ return pyodide;
858
+ };
859
+
860
+ if (globalThis.languagePluginUrl) {
861
+ console.warn(
862
+ "languagePluginUrl is deprecated and will be removed in version 0.18.0, " +
863
+ "instead use loadPyodide({ indexURL : <some_url>})");
864
+
865
+ /**
866
+ * A deprecated parameter that specifies the Pyodide ``indexURL``. If present,
867
+ * Pyodide will automatically invoke
868
+ * ``loadPyodide({indexURL : languagePluginUrl})``
869
+ * and will store the resulting promise in
870
+ * :any:`globalThis.languagePluginLoader`. Use :any:`loadPyodide`
871
+ * directly instead of defining this.
872
+ *
873
+ * @type String
874
+ * @deprecated Will be removed in version 0.18.0
875
+ */
876
+ globalThis.languagePluginUrl;
877
+
878
+ /**
879
+ * A deprecated promise that resolves to ``undefined`` when Pyodide is
880
+ * finished loading. Only created if :any:`languagePluginUrl` is
881
+ * defined. Instead use :any:`loadPyodide`.
882
+ *
883
+ * @type Promise
884
+ * @deprecated Will be removed in version 0.18.0
885
+ */
886
+ globalThis.languagePluginLoader =
887
+ loadPyodide({indexURL : globalThis.languagePluginUrl});
888
+ }