@@ -53,6 +53,15 @@ function createHostFrame(toolEntry, sourceUrl) {
5353 return frame ;
5454}
5555
56+ function setFrameActive ( frame , isActive ) {
57+ if ( ! ( frame instanceof HTMLIFrameElement ) ) {
58+ return ;
59+ }
60+ frame . hidden = ! isActive ;
61+ frame . style . display = isActive ? "block" : "none" ;
62+ frame . setAttribute ( "aria-hidden" , isActive ? "false" : "true" ) ;
63+ }
64+
5665function readToolDestroyContract ( frameWindow , toolId ) {
5766 if ( ! frameWindow || typeof frameWindow !== "object" ) {
5867 return null ;
@@ -77,24 +86,21 @@ export function createToolHostRuntime(options = {}) {
7786
7887 let currentMount = null ;
7988 let mountSequence = 0 ;
89+ const mountedByToolId = new Map ( ) ;
8090
8191 function getCurrentMount ( ) {
8292 return currentMount ? { ...currentMount } : null ;
8393 }
8494
85- function unmountCurrentTool ( reason = "manual" ) {
86- if ( ! currentMount ) {
87- onStatus ( "Tool host is idle." ) ;
88- return false ;
95+ function destroyMountRecord ( record , reason = "manual" ) {
96+ if ( ! record ) {
97+ return "not-available" ;
8998 }
9099
91- const previous = currentMount ;
92- currentMount = null ;
93-
94100 let destroyStatus = "not-available" ;
95101 try {
96- const frameWindow = previous . frame ?. contentWindow ?? null ;
97- const destroyFn = readToolDestroyContract ( frameWindow , previous . tool . id ) ;
102+ const frameWindow = record . frame ?. contentWindow ?? null ;
103+ const destroyFn = readToolDestroyContract ( frameWindow , record . tool . id ) ;
98104 if ( destroyFn ) {
99105 const destroyResult = destroyFn ( {
100106 reason,
@@ -107,18 +113,59 @@ export function createToolHostRuntime(options = {}) {
107113 destroyStatus = "failed" ;
108114 }
109115
110- if ( previous . frame && previous . frame . parentElement === mountContainer ) {
111- previous . frame . removeAttribute ( "src" ) ;
112- mountContainer . removeChild ( previous . frame ) ;
116+ if ( record . frame && record . frame . parentElement === mountContainer ) {
117+ record . frame . removeAttribute ( "src" ) ;
118+ mountContainer . removeChild ( record . frame ) ;
113119 }
114- if ( previous . hostContextId ) {
115- removeToolHostSharedContextById ( previous . hostContextId ) ;
120+ if ( record . hostContextId ) {
121+ removeToolHostSharedContextById ( record . hostContextId ) ;
122+ }
123+ mountedByToolId . delete ( record . tool . id ) ;
124+ return destroyStatus ;
125+ }
126+
127+ function setActiveMount ( record ) {
128+ mountedByToolId . forEach ( ( entry ) => {
129+ setFrameActive ( entry . frame , entry . tool . id === record ?. tool ?. id ) ;
130+ } ) ;
131+ currentMount = record ? { ...record } : null ;
132+ }
133+
134+ function unmountCurrentTool ( reason = "manual" ) {
135+ if ( ! currentMount ) {
136+ onStatus ( "Tool host is idle." ) ;
137+ return false ;
116138 }
139+
140+ const previous = mountedByToolId . get ( currentMount . tool . id ) || currentMount ;
141+ currentMount = null ;
142+ const destroyStatus = destroyMountRecord ( previous , reason ) ;
117143 onStatus ( `Unmounted ${ previous . tool . displayName } (${ reason } , destroy=${ destroyStatus } ).` ) ;
118144 onUnmounted ( previous . tool , reason , destroyStatus ) ;
119145 return true ;
120146 }
121147
148+ function clearMountedTools ( reason = "manual" ) {
149+ let unmountedAny = false ;
150+ let lastTool = null ;
151+ let lastDestroyStatus = "not-available" ;
152+ const mountedEntries = [ ...mountedByToolId . values ( ) ] ;
153+ mountedEntries . forEach ( ( entry ) => {
154+ const destroyStatus = destroyMountRecord ( entry , reason ) ;
155+ unmountedAny = true ;
156+ lastTool = entry . tool ;
157+ lastDestroyStatus = destroyStatus ;
158+ } ) ;
159+ currentMount = null ;
160+ if ( unmountedAny ) {
161+ onUnmounted ( lastTool , reason , lastDestroyStatus ) ;
162+ onStatus ( `Unmounted all hosted tools (${ reason } ).` ) ;
163+ } else {
164+ onStatus ( "Tool host is idle." ) ;
165+ }
166+ return unmountedAny ;
167+ }
168+
122169 function mountTool ( toolId , config = { } ) {
123170 if ( ! mountContainer ) {
124171 onStatus ( "Tool host container is unavailable." ) ;
@@ -132,10 +179,12 @@ export function createToolHostRuntime(options = {}) {
132179 return null ;
133180 }
134181
135- if ( currentMount && currentMount . tool . id !== toolEntry . id ) {
136- unmountCurrentTool ( "switch" ) ;
137- } else if ( currentMount && currentMount . tool . id === toolEntry . id ) {
138- unmountCurrentTool ( "reload" ) ;
182+ const existingMount = mountedByToolId . get ( toolEntry . id ) || null ;
183+ if ( existingMount ) {
184+ setActiveMount ( existingMount ) ;
185+ onStatus ( `Mounted ${ toolEntry . displayName } (restored).` ) ;
186+ onMounted ( toolEntry , existingMount ) ;
187+ return getCurrentMount ( ) ;
139188 }
140189
141190 mountSequence += 1 ;
@@ -166,28 +215,35 @@ export function createToolHostRuntime(options = {}) {
166215 onStatus ( `Failed to load ${ toolEntry . displayName } .` ) ;
167216 } , { once : true } ) ;
168217
169- mountContainer . replaceChildren ( frame ) ;
170- currentMount = {
218+ if ( mountedByToolId . size === 0 ) {
219+ mountContainer . replaceChildren ( frame ) ;
220+ } else {
221+ mountContainer . appendChild ( frame ) ;
222+ }
223+ const nextMount = {
171224 tool : toolEntry ,
172225 frame,
173226 sourceUrl,
174227 mountedAt : new Date ( ) . toISOString ( ) ,
175228 mountSequence : sequenceId ,
176229 hostContextId
177230 } ;
231+ mountedByToolId . set ( toolEntry . id , nextMount ) ;
232+ setActiveMount ( nextMount ) ;
178233
179234 onStatus ( `Mounting ${ toolEntry . displayName } ...` ) ;
180- onMounted ( toolEntry , currentMount ) ;
235+ onMounted ( toolEntry , nextMount ) ;
181236 return getCurrentMount ( ) ;
182237 }
183238
184239 window . addEventListener ( "beforeunload" , ( ) => {
185- unmountCurrentTool ( "page-unload" ) ;
240+ clearMountedTools ( "page-unload" ) ;
186241 } ) ;
187242
188243 return {
189244 mountTool,
190245 unmountCurrentTool,
246+ clearMountedTools,
191247 getCurrentMount
192248 } ;
193249}
0 commit comments