3636 * CaptureLiteOptions,
3737 * CaptureResult,
3838 * CompartmentMapDescriptor,
39+ * ForceLoadOption,
40+ * LogFn,
41+ * LogOptions,
42+ * PolicyOption,
3943 * ReadFn,
4044 * ReadPowers,
4145 * Sources,
4246 * } from './types.js'
4347 */
4448
49+ import { digestCompartmentMap } from './digest.js' ;
4550import {
4651 exitModuleImportHookMaker ,
4752 makeImportHookMaker ,
4853} from './import-hook.js' ;
4954import { link } from './link.js' ;
5055import { resolve } from './node-module-specifier.js' ;
56+ import { ATTENUATORS_COMPARTMENT } from './policy-format.js' ;
5157import { detectAttenuators } from './policy.js' ;
5258import { unpackReadPowers } from './powers.js' ;
53- import { digestCompartmentMap } from './digest.js' ;
5459
55- const { freeze, assign, create } = Object ;
60+ const { freeze, assign, create, keys } = Object ;
61+ const { stringify : q } = JSON ;
5662
57- const defaultCompartment = Compartment ;
63+ const DefaultCompartment = Compartment ;
5864
5965/**
6066 * @param {CompartmentMapDescriptor } compartmentMap
@@ -79,12 +85,156 @@ const captureCompartmentMap = (compartmentMap, sources) => {
7985} ;
8086
8187/**
82- * @param {ReadFn | ReadPowers } powers
88+ * @type {LogFn }
89+ */
90+ const noop = ( ) => { } ;
91+
92+ /**
93+ * Factory for a function that loads compartments.
94+ *
95+ * @param {CompartmentMapDescriptor } compartmentMap Compartment map
96+ * @param {Sources } sources Sources
97+ * @param {LogOptions & PolicyOption & ForceLoadOption } [options]
98+ * @returns {(linkedCompartments: Record<string, Compartment>, entryCompartment: Compartment, attenuatorsCompartment: Compartment) => Promise<void> }
99+ */
100+ const makeLoadCompartments = (
101+ compartmentMap ,
102+ sources ,
103+ { log = noop , policy, forceLoad = [ ] } = { } ,
104+ ) => {
105+ const {
106+ entry : { module : entryModuleSpecifier } ,
107+ } = compartmentMap ;
108+
109+ /**
110+ * Given {@link CompartmentDescriptor CompartmentDescriptors}, loads any which a) are present in the {@link forceLoad forceLoad array}, and b) have not yet been loaded.
111+ *
112+ * Will not load the "attenuators" `Compartment`, nor will it load any
113+ * `Compartment` having a non-empty value in `sources` (since it is presumed it
114+ * has already been loaded).
115+ *
116+ * @param {Record<string, Compartment> } compartments
117+ * @returns {Promise<void> } Resolves when all appropriate compartments are loaded.
118+ */
119+ const forceLoadCompartments = async compartments => {
120+ const compartmentsToLoad = forceLoad . reduce ( ( acc , compartmentName ) => {
121+ // skip; should already be loaded
122+ if (
123+ compartmentName === ATTENUATORS_COMPARTMENT ||
124+ compartmentName === compartmentMap . entry . compartment
125+ ) {
126+ return acc ;
127+ }
128+
129+ const compartmentDescriptor =
130+ compartmentMap . compartments [ compartmentName ] ;
131+
132+ if ( ! compartmentDescriptor ) {
133+ throw ReferenceError (
134+ `Failed attempting to force-load unknown compartment ${ q ( compartmentName ) } ` ,
135+ ) ;
136+ }
137+
138+ const compartmentSources = sources [ compartmentName ] ;
139+
140+ if ( keys ( compartmentSources ) . length ) {
141+ log (
142+ `Refusing to force-load Compartment ${ q ( compartmentName ) } ; already loaded` ,
143+ ) ;
144+ return acc ;
145+ }
146+
147+ const compartment = compartments [ compartmentName ] ;
148+ if ( ! compartment ) {
149+ throw ReferenceError ( `No compartment found for ${ q ( compartmentName ) } ` ) ;
150+ }
151+ const compartmentOwnModuleDescriptor =
152+ compartmentDescriptor . modules [ compartmentDescriptor . name ] ;
153+
154+ if ( ! compartmentOwnModuleDescriptor ?. module ) {
155+ throw Error ( `Cannot determine entry point of ${ q ( compartmentName ) } ` ) ;
156+ }
157+ acc . push ( [
158+ compartmentName ,
159+ compartment ,
160+ compartmentOwnModuleDescriptor . module ,
161+ ] ) ;
162+
163+ // could delete the `load` flag here, but it gets dropped during
164+ // capture anyway
165+ return acc ;
166+ } , /** @type {[compartmentName: string, compartment: Compartment, moduleSpecifier: string][] } */ ( [ ] ) ) ;
167+
168+ const { length : compartmentsToLoadCount } = compartmentsToLoad ;
169+ /**
170+ * This index increments in the order in which compartments finish
171+ * loading—_not_ the order in which they began loading.
172+ */
173+ let loadedCompartmentIndex = 0 ;
174+ await Promise . all (
175+ compartmentsToLoad . map (
176+ async ( [ compartmentName , compartment , moduleSpecifier ] ) => {
177+ await compartment . load ( moduleSpecifier ) ;
178+ log (
179+ `Force-loaded Compartment: ${ q ( compartmentName ) } (${ ( loadedCompartmentIndex += 1 ) } /${ compartmentsToLoadCount } )` ,
180+ ) ;
181+ } ,
182+ ) ,
183+ ) ;
184+ } ;
185+
186+ /**
187+ * Loads, in order:
188+ *
189+ * 1. The entry compartment
190+ * 2. The attenuators compartment (_if and only if_ `policy` was provided)
191+ * 3. All compartments in the `compartmentMap` that have the `load` bit set.
192+ *
193+ * @param {Record<string, Compartment> } linkedCompartments
194+ * @param {Compartment } entryCompartment
195+ * @param {Compartment } attenuatorsCompartment
196+ * @returns {Promise<void> } Resolves when all compartments are loaded.
197+ */
198+ const loadCompartments = async (
199+ linkedCompartments ,
200+ entryCompartment ,
201+ attenuatorsCompartment ,
202+ ) => {
203+ await entryCompartment . load ( entryModuleSpecifier ) ;
204+
205+ if ( policy ) {
206+ // retain all attenuators.
207+ await Promise . all (
208+ detectAttenuators ( policy ) . map ( attenuatorSpecifier =>
209+ attenuatorsCompartment . load ( attenuatorSpecifier ) ,
210+ ) ,
211+ ) ;
212+ }
213+
214+ await forceLoadCompartments ( linkedCompartments ) ;
215+ } ;
216+
217+ return loadCompartments ;
218+ } ;
219+
220+ /**
221+ * "Captures" the compartment map descriptors and sources from a partially
222+ * completed compartment map—_without_ creating an archive.
223+ *
224+ * The resulting compartment map represents a well-formed dependency graph,
225+ * laden with useful metadata. This, for example, could be used for automatic
226+ * policy generation.
227+ *
228+ * @param {ReadFn | ReadPowers } readPowers Powers
83229 * @param {CompartmentMapDescriptor } compartmentMap
84230 * @param {CaptureLiteOptions } [options]
85231 * @returns {Promise<CaptureResult> }
86232 */
87- export const captureFromMap = async ( powers , compartmentMap , options = { } ) => {
233+ export const captureFromMap = async (
234+ readPowers ,
235+ compartmentMap ,
236+ options = { } ,
237+ ) => {
88238 const {
89239 moduleTransforms,
90240 syncModuleTransforms,
@@ -94,14 +244,15 @@ export const captureFromMap = async (powers, compartmentMap, options = {}) => {
94244 policy = undefined ,
95245 sourceMapHook = undefined ,
96246 parserForLanguage : parserForLanguageOption = { } ,
97- Compartment = defaultCompartment ,
247+ Compartment : CompartmentOption = DefaultCompartment ,
248+ log = noop ,
249+ forceLoad = [ ] ,
98250 } = options ;
99-
100251 const parserForLanguage = freeze (
101252 assign ( create ( null ) , parserForLanguageOption ) ,
102253 ) ;
103254
104- const { read, computeSha512 } = unpackReadPowers ( powers ) ;
255+ const { read, computeSha512 } = unpackReadPowers ( readPowers ) ;
105256
106257 const {
107258 compartments,
@@ -111,6 +262,12 @@ export const captureFromMap = async (powers, compartmentMap, options = {}) => {
111262 /** @type {Sources } */
112263 const sources = Object . create ( null ) ;
113264
265+ const loadCompartments = makeLoadCompartments ( compartmentMap , sources , {
266+ log,
267+ policy,
268+ forceLoad,
269+ } ) ;
270+
114271 const consolidatedExitModuleImportHook = exitModuleImportHookMaker ( {
115272 modules : exitModules ,
116273 exitModuleImportHook,
@@ -128,25 +285,27 @@ export const captureFromMap = async (powers, compartmentMap, options = {}) => {
128285 importHook : consolidatedExitModuleImportHook ,
129286 sourceMapHook,
130287 } ) ;
288+
131289 // Induce importHook to record all the necessary modules to import the given module specifier.
132- const { compartment, attenuatorsCompartment } = link ( compartmentMap , {
290+ const {
291+ compartment : entryCompartment ,
292+ compartments : linkedCompartments ,
293+ attenuatorsCompartment,
294+ } = link ( compartmentMap , {
133295 resolve,
134296 makeImportHook,
135297 moduleTransforms,
136298 syncModuleTransforms,
137299 parserForLanguage,
138300 archiveOnly : true ,
139- Compartment,
301+ Compartment : CompartmentOption ,
140302 } ) ;
141- await compartment . load ( entryModuleSpecifier ) ;
142- if ( policy ) {
143- // retain all attenuators.
144- await Promise . all (
145- detectAttenuators ( policy ) . map ( attenuatorSpecifier =>
146- attenuatorsCompartment . load ( attenuatorSpecifier ) ,
147- ) ,
148- ) ;
149- }
303+
304+ await loadCompartments (
305+ linkedCompartments ,
306+ entryCompartment ,
307+ attenuatorsCompartment ,
308+ ) ;
150309
151310 return captureCompartmentMap ( compartmentMap , sources ) ;
152311} ;
0 commit comments