@@ -72,14 +72,13 @@ class WebSocketComm {
7272 callback : ( ) => void ;
7373 }
7474 > = { } ;
75- // True when the iframe is loading, so that an `Update` should be postponed
76- // until the page load is finished. Otherwise, the page is fully loaded, so
77- // the `Update` may be applied immediately.
78- onloading = false ;
7975 // The current filename of the file being edited. This is provided by the
8076 // IDE and passed back to it, but not otherwise used by the Framework.
8177 current_filename : string | undefined = undefined ;
8278
79+ // A promise to serialize calls to `set_content`.
80+ promise = Promise . resolve ( ) ;
81+
8382 constructor ( ws_url : string ) {
8483 // The `ReconnectingWebSocket` doesn't provide ALL the `WebSocket`
8584 // methods. Ignore this, since we can't use `ReconnectingWebSocket` as a
@@ -141,28 +140,44 @@ class WebSocketComm {
141140 const contents = current_update . contents ;
142141 const cursor_position = current_update . cursor_position ;
143142 if ( contents !== undefined ) {
144- // If the page is still loading, wait until the load
145- // completed before updating the editable contents.
146- if ( this . onloading ) {
147- root_iframe ! . onload = ( ) => {
148- set_content (
149- contents ,
150- current_update . cursor_position
151- ) ;
152- this . onloading = false ;
153- } ;
143+ if (
144+ root_iframe ! . contentDocument ?. readyState !==
145+ "loading"
146+ ) {
147+ // The page is ready; load in the provided content.
148+ this . promise = this . promise . finally (
149+ async ( ) =>
150+ await set_content (
151+ contents ,
152+ current_update . cursor_position ,
153+ ) ,
154+ ) ;
154155 } else {
155- set_content (
156- contents ,
157- current_update . cursor_position
156+ // If the page is still loading, wait until the load
157+ // completes before updating the editable contents.
158+ //
159+ // Construct the promise to use; this causes the
160+ // `onload` callback to be set immediately.
161+ const p = new Promise < void > (
162+ ( resolve ) =>
163+ ( root_iframe ! . onload = async ( ) => {
164+ await set_content (
165+ contents ,
166+ current_update . cursor_position ,
167+ ) ;
168+ resolve ( ) ;
169+ } ) ,
158170 ) ;
171+ this . promise = this . promise . finally ( ( ) => p ) ;
159172 }
160173 } else if ( cursor_position !== undefined ) {
161174 // We might receive a message while the Client is
162175 // reloading; during this period, `scroll_to_line` isn't
163176 // defined.
164- root_iframe ! . contentWindow ! . CodeChatEditor . scroll_to_line ?.(
165- cursor_position
177+ this . promise = this . promise . finally ( ( ) =>
178+ root_iframe ! . contentWindow ?. CodeChatEditor . scroll_to_line ?.(
179+ cursor_position ,
180+ ) ,
166181 ) ;
167182 }
168183
@@ -173,36 +188,34 @@ class WebSocketComm {
173188 // Note that we can ignore `value[1]` (if the file is text
174189 // or binary); the server only sends text files here.
175190 const current_file = value [ 0 ] as string ;
191+ const testSuffix = testMode
192+ ? // Append the test parameter correctly, depending if
193+ // there are already parameters or not.
194+ current_file . indexOf ( "?" ) === - 1
195+ ? "?test"
196+ : "&test"
197+ : "" ;
176198 // If the page is still loading, then don't save. Otherwise,
177199 // save the editor contents if necessary.
178- let cce = get_client ( ) ;
179- let promise =
180- cce !== undefined
181- ? cce . on_save ( true )
182- : Promise . resolve ( ) ;
183- promise . then ( ( _ ) => {
184- // Now, it's safe to load a new file.
185- const testSuffix = testMode
186- ? // Append the test parameter correctly, depending if
187- // there are already parameters or not.
188- current_file . indexOf ( "?" ) === - 1
189- ? "?test"
190- : "&test"
191- : "" ;
192- // Tell the client to allow this navigation -- the
193- // document it contains has already been saved.
194- if ( cce !== undefined ) {
195- cce . allow_navigation = true ;
196- }
197- this . set_root_iframe_src ( current_file + testSuffix ) ;
198- // The `current_file` is a URL-encoded path, not a
199- // filesystem path. So, we can't use it for
200- // `current_filename`. Instead, signal that the
201- // `current_filename` should be set on the next `Update`
202- // message.
203- this . current_filename = undefined ;
204- this . send_result ( id , null ) ;
205- } ) ;
200+ const cce = get_client ( ) ;
201+ this . promise = this . promise
202+ . finally ( ( ) => cce ?. on_save ( true ) )
203+ . finally ( ( ) => {
204+ // Now, it's safe to load a new file.
205+ // Tell the client to allow this navigation -- the
206+ // document it contains has already been saved.
207+ if ( cce !== undefined ) {
208+ cce . allow_navigation = true ;
209+ }
210+ this . set_root_iframe_src ( current_file + testSuffix ) ;
211+ // The `current_file` is a URL-encoded path, not a
212+ // filesystem path. So, we can't use it for
213+ // `current_filename`. Instead, signal that the
214+ // `current_filename` should be set on the next `Update`
215+ // message.
216+ this . current_filename = undefined ;
217+ this . send_result ( id , null ) ;
218+ } ) ;
206219 break ;
207220
208221 case "Result" :
@@ -243,10 +256,6 @@ class WebSocketComm {
243256 // assign the `src` attribute.
244257 root_iframe ! . removeAttribute ( "srcdoc" ) ;
245258 root_iframe ! . src = url ;
246- // There's no easy way to determine when the iframe's DOM is ready. This
247- // is a kludgy workaround -- set a flag.
248- this . onloading = true ;
249- root_iframe ! . onload = ( ) => ( this . onloading = false ) ;
250259 } ;
251260
252261 send = ( data : any ) => this . ws . send ( data ) ;
@@ -331,7 +340,10 @@ const get_client = () => root_iframe?.contentWindow?.CodeChatEditor;
331340
332341// Assign content to either the Client (if it's loaded) or the webpage (if not)
333342// in the `root_iframe`.
334- const set_content = ( contents : CodeChatForWeb , cursor_position ?: number ) => {
343+ const set_content = async (
344+ contents : CodeChatForWeb ,
345+ cursor_position ?: number ,
346+ ) => {
335347 let client = get_client ( ) ;
336348 if ( client === undefined ) {
337349 // See if this is the [simple viewer](#Client-simple-viewer). Otherwise,
@@ -347,7 +359,7 @@ const set_content = (contents: CodeChatForWeb, cursor_position?: number) => {
347359 cw . document . write ( contents . source . Plain . doc ) ;
348360 cw . document . close ( ) ;
349361 } else {
350- root_iframe ! . contentWindow ! . CodeChatEditor . open_lp (
362+ await root_iframe ! . contentWindow ! . CodeChatEditor . open_lp (
351363 contents ,
352364 cursor_position ,
353365 ) ;
@@ -371,7 +383,7 @@ export const page_init = (
371383 testMode_ : boolean ,
372384) => {
373385 testMode = testMode_ ;
374- on_dom_content_loaded ( async ( ) => {
386+ on_dom_content_loaded ( ( ) => {
375387 // If the hosting page uses HTTPS, then use a secure websocket (WSS
376388 // protocol); otherwise, use an insecure websocket (WS).
377389 const protocol = window . location . protocol === "http:" ? "ws:" : "wss:" ;
0 commit comments