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,161 @@ 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
111+ * a) are present in the {@link forceLoad forceLoad array}, and b) have not
112+ * yet been loaded.
113+ *
114+ * Will not load the "attenuators" `Compartment`, nor will it load any
115+ * `Compartment` having a non-empty value in `sources` (since it is presumed
116+ * it has already been loaded).
117+ *
118+ * @param {Record<string, Compartment> } compartments
119+ * @returns {Promise<void> } Resolves when all appropriate compartments are
120+ * loaded.
121+ */
122+ const forceLoadCompartments = async compartments => {
123+ const compartmentsToLoad = forceLoad . reduce ( ( acc , compartmentName ) => {
124+ // skip; should already be loaded
125+ if (
126+ compartmentName === ATTENUATORS_COMPARTMENT ||
127+ compartmentName === compartmentMap . entry . compartment
128+ ) {
129+ return acc ;
130+ }
131+
132+ const compartmentDescriptor =
133+ compartmentMap . compartments [ compartmentName ] ;
134+
135+ if ( ! compartmentDescriptor ) {
136+ throw new ReferenceError (
137+ `Failed attempting to force-load unknown compartment ${ q ( compartmentName ) } ` ,
138+ ) ;
139+ }
140+
141+ const compartmentSources = sources [ compartmentName ] ;
142+
143+ if ( keys ( compartmentSources ) . length ) {
144+ log (
145+ `Refusing to force-load Compartment ${ q ( compartmentName ) } ; already loaded` ,
146+ ) ;
147+ return acc ;
148+ }
149+
150+ const compartment = compartments [ compartmentName ] ;
151+ if ( ! compartment ) {
152+ throw new ReferenceError (
153+ `No compartment found for ${ q ( compartmentName ) } ` ,
154+ ) ;
155+ }
156+ const compartmentOwnModuleDescriptor =
157+ compartmentDescriptor . modules [ compartmentDescriptor . name ] ;
158+
159+ if ( ! compartmentOwnModuleDescriptor ?. module ) {
160+ throw new Error (
161+ `Cannot determine entry point of ${ q ( compartmentName ) } ` ,
162+ ) ;
163+ }
164+ acc . push ( [
165+ compartmentName ,
166+ compartment ,
167+ compartmentOwnModuleDescriptor . module ,
168+ ] ) ;
169+
170+ return acc ;
171+ } , /** @type {[compartmentName: string, compartment: Compartment, moduleSpecifier: string][] } */ ( [ ] ) ) ;
172+
173+ const { length : compartmentsToLoadCount } = compartmentsToLoad ;
174+ /**
175+ * This index increments in the order in which compartments finish
176+ * loading—_not_ the order in which they began loading.
177+ */
178+ let loadedCompartmentIndex = 0 ;
179+ await Promise . all (
180+ compartmentsToLoad . map (
181+ async ( [ compartmentName , compartment , moduleSpecifier ] ) => {
182+ await compartment . load ( moduleSpecifier ) ;
183+ log (
184+ `Force-loaded Compartment: ${ q ( compartmentName ) } (${ ( loadedCompartmentIndex += 1 ) } /${ compartmentsToLoadCount } )` ,
185+ ) ;
186+ } ,
187+ ) ,
188+ ) ;
189+ } ;
190+
191+ /**
192+ * Loads, in order:
193+ *
194+ * 1. The entry compartment
195+ * 2. The attenuators compartment (_if and only if_ `policy` was provided)
196+ * 3. All compartments in the `compartmentMap` that have the `load` bit set.
197+ *
198+ * @param {Record<string, Compartment> } linkedCompartments
199+ * @param {Compartment } entryCompartment
200+ * @param {Compartment } attenuatorsCompartment
201+ * @returns {Promise<void> } Resolves when all compartments are loaded.
202+ */
203+ const loadCompartments = async (
204+ linkedCompartments ,
205+ entryCompartment ,
206+ attenuatorsCompartment ,
207+ ) => {
208+ await entryCompartment . load ( entryModuleSpecifier ) ;
209+
210+ if ( policy ) {
211+ // retain all attenuators.
212+ await Promise . all (
213+ detectAttenuators ( policy ) . map ( attenuatorSpecifier =>
214+ attenuatorsCompartment . load ( attenuatorSpecifier ) ,
215+ ) ,
216+ ) ;
217+ }
218+
219+ await forceLoadCompartments ( linkedCompartments ) ;
220+ } ;
221+
222+ return loadCompartments ;
223+ } ;
224+
225+ /**
226+ * "Captures" the compartment map descriptors and sources from a partially
227+ * completed compartment map—_without_ creating an archive.
228+ *
229+ * The resulting compartment map represents a well-formed dependency graph,
230+ * laden with useful metadata. This, for example, could be used for automatic
231+ * policy generation.
232+ *
233+ * @param {ReadFn | ReadPowers } readPowers Powers
83234 * @param {CompartmentMapDescriptor } compartmentMap
84235 * @param {CaptureLiteOptions } [options]
85236 * @returns {Promise<CaptureResult> }
86237 */
87- export const captureFromMap = async ( powers , compartmentMap , options = { } ) => {
238+ export const captureFromMap = async (
239+ readPowers ,
240+ compartmentMap ,
241+ options = { } ,
242+ ) => {
88243 const {
89244 moduleTransforms,
90245 syncModuleTransforms,
@@ -94,14 +249,15 @@ export const captureFromMap = async (powers, compartmentMap, options = {}) => {
94249 policy = undefined ,
95250 sourceMapHook = undefined ,
96251 parserForLanguage : parserForLanguageOption = { } ,
97- Compartment = defaultCompartment ,
252+ Compartment : CompartmentOption = DefaultCompartment ,
253+ log = noop ,
254+ forceLoad = [ ] ,
98255 } = options ;
99-
100256 const parserForLanguage = freeze (
101257 assign ( create ( null ) , parserForLanguageOption ) ,
102258 ) ;
103259
104- const { read, computeSha512 } = unpackReadPowers ( powers ) ;
260+ const { read, computeSha512 } = unpackReadPowers ( readPowers ) ;
105261
106262 const {
107263 compartments,
@@ -111,6 +267,12 @@ export const captureFromMap = async (powers, compartmentMap, options = {}) => {
111267 /** @type {Sources } */
112268 const sources = Object . create ( null ) ;
113269
270+ const loadCompartments = makeLoadCompartments ( compartmentMap , sources , {
271+ log,
272+ policy,
273+ forceLoad,
274+ } ) ;
275+
114276 const consolidatedExitModuleImportHook = exitModuleImportHookMaker ( {
115277 modules : exitModules ,
116278 exitModuleImportHook,
@@ -128,25 +290,27 @@ export const captureFromMap = async (powers, compartmentMap, options = {}) => {
128290 importHook : consolidatedExitModuleImportHook ,
129291 sourceMapHook,
130292 } ) ;
293+
131294 // Induce importHook to record all the necessary modules to import the given module specifier.
132- const { compartment, attenuatorsCompartment } = link ( compartmentMap , {
295+ const {
296+ compartment : entryCompartment ,
297+ compartments : linkedCompartments ,
298+ attenuatorsCompartment,
299+ } = link ( compartmentMap , {
133300 resolve,
134301 makeImportHook,
135302 moduleTransforms,
136303 syncModuleTransforms,
137304 parserForLanguage,
138305 archiveOnly : true ,
139- Compartment,
306+ Compartment : CompartmentOption ,
140307 } ) ;
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- }
308+
309+ await loadCompartments (
310+ linkedCompartments ,
311+ entryCompartment ,
312+ attenuatorsCompartment ,
313+ ) ;
150314
151315 return captureCompartmentMap ( compartmentMap , sources ) ;
152316} ;
0 commit comments