@@ -16,7 +16,16 @@ interface HtmlRenderingHook {
16
16
*
17
17
* @return A new `HTMLElement` or `undefined` to continue using the provided element.
18
18
*/
19
- postRender ( outputItem : OutputItem , element : HTMLElement ) : HTMLElement | undefined ;
19
+ postRender ( outputItem : OutputItem , element : HTMLElement , signal : AbortSignal ) : HTMLElement | undefined | Promise < HTMLElement | undefined > ;
20
+ }
21
+
22
+ interface JavaScriptRenderingHook {
23
+ /**
24
+ * Invoked before the script is evaluated.
25
+ *
26
+ * @return A new string of JavaScript or `undefined` to continue using the provided string.
27
+ */
28
+ preEvaluate ( outputItem : OutputItem , element : string , signal : AbortSignal ) : string | undefined | Promise < string | undefined > ;
20
29
}
21
30
22
31
function clearContainer ( container : HTMLElement ) {
@@ -72,25 +81,37 @@ const domEval = (container: Element) => {
72
81
}
73
82
} ;
74
83
75
- function renderHTML ( outputInfo : OutputItem , container : HTMLElement , hooks : Iterable < HtmlRenderingHook > ) : void {
84
+ async function renderHTML ( outputInfo : OutputItem , container : HTMLElement , signal : AbortSignal , hooks : Iterable < HtmlRenderingHook > ) : Promise < void > {
76
85
clearContainer ( container ) ;
77
86
let element : HTMLElement = document . createElement ( 'div' ) ;
78
87
const htmlContent = outputInfo . text ( ) ;
79
88
const trustedHtml = ttPolicy ?. createHTML ( htmlContent ) ?? htmlContent ;
80
89
element . innerHTML = trustedHtml as string ;
81
90
82
91
for ( const hook of hooks ) {
83
- element = hook . postRender ( outputInfo , element ) ?? element ;
92
+ element = ( await hook . postRender ( outputInfo , element , signal ) ) ?? element ;
93
+ if ( signal . aborted ) {
94
+ return ;
95
+ }
84
96
}
85
97
86
98
container . appendChild ( element ) ;
87
99
domEval ( element ) ;
88
100
}
89
101
90
- function renderJavascript ( outputInfo : OutputItem , container : HTMLElement ) : void {
102
+ async function renderJavascript ( outputInfo : OutputItem , container : HTMLElement , signal : AbortSignal , hooks : Iterable < JavaScriptRenderingHook > ) : Promise < void > {
103
+ let scriptText = outputInfo . text ( ) ;
104
+
105
+ for ( const hook of hooks ) {
106
+ scriptText = ( await hook . preEvaluate ( outputInfo , scriptText , signal ) ) ?? scriptText ;
107
+ if ( signal . aborted ) {
108
+ return ;
109
+ }
110
+ }
111
+
91
112
const script = document . createElement ( 'script' ) ;
92
113
script . type = 'module' ;
93
- script . textContent = outputInfo . text ( ) ;
114
+ script . textContent = scriptText ;
94
115
95
116
const element = document . createElement ( 'div' ) ;
96
117
const trustedHtml = ttPolicy ?. createHTML ( script . outerHTML ) ?? script . outerHTML ;
@@ -177,12 +198,12 @@ function renderText(outputInfo: OutputItem, container: HTMLElement, ctx: Rendere
177
198
const text = outputInfo . text ( ) ;
178
199
truncatedArrayOfString ( outputInfo . id , [ text ] , ctx . settings . lineLimit , contentNode ) ;
179
200
container . appendChild ( contentNode ) ;
180
-
181
201
}
182
202
183
203
export const activate : ActivationFunction < void > = ( ctx ) => {
184
204
const disposables = new Map < string , IDisposable > ( ) ;
185
205
const htmlHooks = new Set < HtmlRenderingHook > ( ) ;
206
+ const jsHooks = new Set < JavaScriptRenderingHook > ( ) ;
186
207
187
208
const latestContext = ctx as ( RendererContext < void > & { readonly settings : { readonly lineLimit : number } } ) ;
188
209
@@ -229,27 +250,25 @@ export const activate: ActivationFunction<void> = (ctx) => {
229
250
document . body . appendChild ( style ) ;
230
251
231
252
return {
232
- renderOutputItem : ( outputInfo , element ) => {
253
+ renderOutputItem : async ( outputInfo , element , signal ?: AbortSignal ) => {
233
254
switch ( outputInfo . mime ) {
234
255
case 'text/html' :
235
- case 'image/svg+xml' :
236
- {
237
- if ( ! ctx . workspace . isTrusted ) {
238
- return ;
239
- }
240
-
241
- renderHTML ( outputInfo , element , htmlHooks ) ;
256
+ case 'image/svg+xml' : {
257
+ if ( ! ctx . workspace . isTrusted ) {
258
+ return ;
242
259
}
243
- break ;
244
- case 'application/javascript' :
245
- {
246
- if ( ! ctx . workspace . isTrusted ) {
247
- return ;
248
- }
249
260
250
- renderJavascript ( outputInfo , element ) ;
261
+ await renderHTML ( outputInfo , element , signal ! , htmlHooks ) ;
262
+ break ;
263
+ }
264
+ case 'application/javascript' : {
265
+ if ( ! ctx . workspace . isTrusted ) {
266
+ return ;
251
267
}
268
+
269
+ renderJavascript ( outputInfo , element , signal ! , jsHooks ) ;
252
270
break ;
271
+ }
253
272
case 'image/gif' :
254
273
case 'image/png' :
255
274
case 'image/jpeg' :
@@ -300,6 +319,14 @@ export const activate: ActivationFunction<void> = (ctx) => {
300
319
htmlHooks . delete ( hook ) ;
301
320
}
302
321
} ;
322
+ } ,
323
+ experimental_registerJavaScriptRenderingHook : ( hook : JavaScriptRenderingHook ) : IDisposable => {
324
+ jsHooks . add ( hook ) ;
325
+ return {
326
+ dispose : ( ) => {
327
+ jsHooks . delete ( hook ) ;
328
+ }
329
+ } ;
303
330
}
304
331
} ;
305
332
} ;
0 commit comments