Skip to content

Commit 7db5ba3

Browse files
committed
Fix: correctly order Client calls from the Framework.
1 parent ab044aa commit 7db5ba3

File tree

3 files changed

+78
-58
lines changed

3 files changed

+78
-58
lines changed

client/src/CodeChatEditor.mts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,14 +221,20 @@ const on_dom_content_loaded = (on_load_func: () => void) => {
221221
const is_doc_only = () => {
222222
// This might be called by the framework before a document is loaded.
223223
// So, make sure `current_metadata` exists first.
224-
return current_metadata !== undefined && current_metadata["mode"] === "markdown";
224+
return current_metadata?.["mode"] === "markdown";
225225
};
226226

227227
// Wait for the DOM to load before opening the file.
228228
const open_lp = async (
229229
codechat_for_web: CodeChatForWeb,
230230
cursor_position?: number,
231-
) => on_dom_content_loaded(() => _open_lp(codechat_for_web, cursor_position));
231+
) =>
232+
await new Promise<void>((resolve) =>
233+
on_dom_content_loaded(async () => {
234+
await _open_lp(codechat_for_web, cursor_position);
235+
resolve();
236+
}),
237+
);
232238

233239
// Store the HTML sent for CodeChat Editor documents. We can't simply use
234240
// TinyMCE's

client/src/CodeChatEditorFramework.mts

Lines changed: 66 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -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:";

client/src/CodeMirror-integration.mts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ import { show_toast } from "./show_toast.mjs";
9595
// Globals
9696
// -------
9797
let current_view: EditorView;
98+
console.log("*************** Init");
9899
let tinymce_singleton: Editor | undefined;
99100
// When true, don't update on the next call to `on_dirty`. See that function for
100101
// more info.
@@ -994,6 +995,7 @@ export const CodeMirror_load = async (
994995
state,
995996
scrollTo: scrollSnapshot,
996997
});
998+
console.log("*************** Defined");
997999
tinymce_singleton = (
9981000
await init({
9991001
selector: "#TinyMCE-inst",
@@ -1080,8 +1082,8 @@ export const CodeMirror_load = async (
10801082

10811083
export const scroll_to_line = (line: number) => {
10821084
ignore_selection_change = true;
1083-
const line_range = current_view.state.doc.line(line);
1084-
current_view.dispatch({
1085+
const line_range = current_view?.state.doc.line(line);
1086+
current_view?.dispatch({
10851087
selection: EditorSelection.cursor(line_range.from),
10861088
scrollIntoView: true,
10871089
});

0 commit comments

Comments
 (0)