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,160 @@ 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 new 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 new ReferenceError (
150+ `No compartment found for ${ q ( compartmentName ) } ` ,
151+ ) ;
152+ }
153+ const compartmentOwnModuleDescriptor =
154+ compartmentDescriptor . modules [ compartmentDescriptor . name ] ;
155+
156+ if ( ! compartmentOwnModuleDescriptor ?. module ) {
157+ throw new Error (
158+ `Cannot determine entry point of ${ q ( compartmentName ) } ` ,
159+ ) ;
160+ }
161+ acc . push ( [
162+ compartmentName ,
163+ compartment ,
164+ compartmentOwnModuleDescriptor . module ,
165+ ] ) ;
166+
167+ // could delete the `load` flag here, but it gets dropped during
168+ // capture anyway
169+ return acc ;
170+ } , /** @type {[compartmentName: string, compartment: Compartment, moduleSpecifier: string][] } */ ( [ ] ) ) ;
171+
172+ const { length : compartmentsToLoadCount } = compartmentsToLoad ;
173+ /**
174+ * This index increments in the order in which compartments finish
175+ * loading—_not_ the order in which they began loading.
176+ */
177+ let loadedCompartmentIndex = 0 ;
178+ await Promise . all (
179+ compartmentsToLoad . map (
180+ async ( [ compartmentName , compartment , moduleSpecifier ] ) => {
181+ await compartment . load ( moduleSpecifier ) ;
182+ log (
183+ `Force-loaded Compartment: ${ q ( compartmentName ) } (${ ( loadedCompartmentIndex += 1 ) } /${ compartmentsToLoadCount } )` ,
184+ ) ;
185+ } ,
186+ ) ,
187+ ) ;
188+ } ;
189+
190+ /**
191+ * Loads, in order:
192+ *
193+ * 1. The entry compartment
194+ * 2. The attenuators compartment (_if and only if_ `policy` was provided)
195+ * 3. All compartments in the `compartmentMap` that have the `load` bit set.
196+ *
197+ * @param {Record<string, Compartment> } linkedCompartments
198+ * @param {Compartment } entryCompartment
199+ * @param {Compartment } attenuatorsCompartment
200+ * @returns {Promise<void> } Resolves when all compartments are loaded.
201+ */
202+ const loadCompartments = async (
203+ linkedCompartments ,
204+ entryCompartment ,
205+ attenuatorsCompartment ,
206+ ) => {
207+ await entryCompartment . load ( entryModuleSpecifier ) ;
208+
209+ if ( policy ) {
210+ // retain all attenuators.
211+ await Promise . all (
212+ detectAttenuators ( policy ) . map ( attenuatorSpecifier =>
213+ attenuatorsCompartment . load ( attenuatorSpecifier ) ,
214+ ) ,
215+ ) ;
216+ }
217+
218+ await forceLoadCompartments ( linkedCompartments ) ;
219+ } ;
220+
221+ return loadCompartments ;
222+ } ;
223+
224+ /**
225+ * "Captures" the compartment map descriptors and sources from a partially
226+ * completed compartment map—_without_ creating an archive.
227+ *
228+ * The resulting compartment map represents a well-formed dependency graph,
229+ * laden with useful metadata. This, for example, could be used for automatic
230+ * policy generation.
231+ *
232+ * @param {ReadFn | ReadPowers } readPowers Powers
83233 * @param {CompartmentMapDescriptor } compartmentMap
84234 * @param {CaptureLiteOptions } [options]
85235 * @returns {Promise<CaptureResult> }
86236 */
87- export const captureFromMap = async ( powers , compartmentMap , options = { } ) => {
237+ export const captureFromMap = async (
238+ readPowers ,
239+ compartmentMap ,
240+ options = { } ,
241+ ) => {
88242 const {
89243 moduleTransforms,
90244 syncModuleTransforms,
@@ -94,14 +248,15 @@ export const captureFromMap = async (powers, compartmentMap, options = {}) => {
94248 policy = undefined ,
95249 sourceMapHook = undefined ,
96250 parserForLanguage : parserForLanguageOption = { } ,
97- Compartment = defaultCompartment ,
251+ Compartment : CompartmentOption = DefaultCompartment ,
252+ log = noop ,
253+ forceLoad = [ ] ,
98254 } = options ;
99-
100255 const parserForLanguage = freeze (
101256 assign ( create ( null ) , parserForLanguageOption ) ,
102257 ) ;
103258
104- const { read, computeSha512 } = unpackReadPowers ( powers ) ;
259+ const { read, computeSha512 } = unpackReadPowers ( readPowers ) ;
105260
106261 const {
107262 compartments,
@@ -111,6 +266,12 @@ export const captureFromMap = async (powers, compartmentMap, options = {}) => {
111266 /** @type {Sources } */
112267 const sources = Object . create ( null ) ;
113268
269+ const loadCompartments = makeLoadCompartments ( compartmentMap , sources , {
270+ log,
271+ policy,
272+ forceLoad,
273+ } ) ;
274+
114275 const consolidatedExitModuleImportHook = exitModuleImportHookMaker ( {
115276 modules : exitModules ,
116277 exitModuleImportHook,
@@ -128,25 +289,27 @@ export const captureFromMap = async (powers, compartmentMap, options = {}) => {
128289 importHook : consolidatedExitModuleImportHook ,
129290 sourceMapHook,
130291 } ) ;
292+
131293 // Induce importHook to record all the necessary modules to import the given module specifier.
132- const { compartment, attenuatorsCompartment } = link ( compartmentMap , {
294+ const {
295+ compartment : entryCompartment ,
296+ compartments : linkedCompartments ,
297+ attenuatorsCompartment,
298+ } = link ( compartmentMap , {
133299 resolve,
134300 makeImportHook,
135301 moduleTransforms,
136302 syncModuleTransforms,
137303 parserForLanguage,
138304 archiveOnly : true ,
139- Compartment,
305+ Compartment : CompartmentOption ,
140306 } ) ;
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- }
307+
308+ await loadCompartments (
309+ linkedCompartments ,
310+ entryCompartment ,
311+ attenuatorsCompartment ,
312+ ) ;
150313
151314 return captureCompartmentMap ( compartmentMap , sources ) ;
152315} ;
0 commit comments