Skip to content
This repository has been archived by the owner on Sep 27, 2024. It is now read-only.

Commit

Permalink
Handle rust model crash (#575)
Browse files Browse the repository at this point in the history
Handle rust model crash
  • Loading branch information
florianduros authored Feb 9, 2023
1 parent 3d51ebd commit 27b8f04
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 59 deletions.
49 changes: 28 additions & 21 deletions platforms/web/lib/useComposerModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import { RefObject, useEffect, useState } from 'react';
import { RefObject, useCallback, useEffect, useState } from 'react';

// rust generated bindings
import init, {
Expand Down Expand Up @@ -62,36 +62,43 @@ export function useComposerModel(
null,
);

useEffect(() => {
const initModel = async () => {
const initModel = useCallback(
async (initialContent?: string) => {
await initOnce();

if (initialContent) {
setComposerModel(
new_composer_model_from_html(
initialContent,
0,
initialContent.length,
),
);

if (editorRef.current) {
replaceEditor(
editorRef.current,
initialContent,
0,
initialContent.length,
try {
setComposerModel(
new_composer_model_from_html(
initialContent,
0,
initialContent.length,
),
);

if (editorRef.current) {
replaceEditor(
editorRef.current,
initialContent,
0,
initialContent.length,
);
}
} catch (e) {
setComposerModel(new_composer_model());
}
} else {
setComposerModel(new_composer_model());
}
};
},
[setComposerModel, editorRef],
);

useEffect(() => {
if (editorRef.current) {
initModel();
initModel(initialContent);
}
}, [editorRef, initialContent]);
}, [editorRef, initModel, initialContent]);

return composerModel;
return { composerModel, initModel };
}
88 changes: 53 additions & 35 deletions platforms/web/lib/useListeners/useListeners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import { RefObject, useEffect, useState } from 'react';
import { RefObject, useEffect, useRef, useState } from 'react';

import { ComposerModel } from '../../generated/wysiwyg';
import { isClipboardEvent, isInputEvent } from './assert';
Expand All @@ -40,6 +40,7 @@ export function useListeners(
composerModel: ComposerModel | null,
testUtilities: TestUtilities,
formattingFunctions: FormattingFunctions,
onError: (content?: string) => void,
initialContent?: string,
inputEventProcessor?: InputEventProcessor,
) {
Expand All @@ -48,6 +49,8 @@ export function useListeners(
actionStates: createDefaultActionStates(),
});

const plainTextContentRef = useRef<string>();

const [areListenersReady, setAreListenersReady] = useState(false);

useEffect(() => {
Expand All @@ -58,6 +61,8 @@ export function useListeners(
composerModel.action_states(),
),
});
plainTextContentRef.current =
composerModel.get_content_as_plain_text();
}
}, [composerModel]);

Expand All @@ -68,28 +73,34 @@ export function useListeners(
}

const _handleInput = (e: WysiwygInputEvent) => {
const res = handleInput(
e,
editorNode,
composerModel,
modelRef.current,
testUtilities,
formattingFunctions,
inputEventProcessor,
);

if (res) {
setState(({ content, actionStates }) => {
const newState: State = {
content,
actionStates: res.actionStates || actionStates,
};
if (res.content !== undefined) {
newState.content = res.content;
}

return newState;
});
try {
const res = handleInput(
e,
editorNode,
composerModel,
modelRef.current,
testUtilities,
formattingFunctions,
inputEventProcessor,
);

if (res) {
setState(({ content, actionStates }) => {
const newState: State = {
content,
actionStates: res.actionStates || actionStates,
};
if (res.content !== undefined) {
newState.content = res.content;
}

return newState;
});
plainTextContentRef.current =
composerModel.get_content_as_plain_text();
}
} catch (e) {
onError(plainTextContentRef.current);
}
};

Expand Down Expand Up @@ -130,17 +141,23 @@ export function useListeners(
editorNode.addEventListener('keydown', onKeyDown);

const onSelectionChange = () => {
const actionStates = handleSelectionChange(
editorNode,
composerModel,
testUtilities,
);

if (actionStates) {
setState(({ content }) => ({
content,
actionStates,
}));
try {
const actionStates = handleSelectionChange(
editorNode,
composerModel,
testUtilities,
);

if (actionStates) {
setState(({ content }) => ({
content,
actionStates,
}));
}
plainTextContentRef.current =
composerModel.get_content_as_plain_text();
} catch (e) {
onError(plainTextContentRef.current);
}
};
document.addEventListener('selectionchange', onSelectionChange);
Expand All @@ -162,7 +179,8 @@ export function useListeners(
modelRef,
testUtilities,
inputEventProcessor,
setState,
onError,
plainTextContentRef,
]);

return { areListenersReady, ...state };
Expand Down
13 changes: 12 additions & 1 deletion platforms/web/lib/useWysiwyg.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ describe('useWysiwyg', () => {
});

test('Create wysiwyg with initial content', async () => {
// When
// when
const content = 'fo<strong>o</strong><br />b<em>ar</em>';
render(<Editor initialContent={content} />);

Expand All @@ -115,4 +115,15 @@ describe('useWysiwyg', () => {
expect(screen.getByRole('textbox')).toContainHTML(content),
);
});

test('Handle panic', async () => {
// When
const content = 'fo<strng>o</strng><br />b<em><kar</em>';
render(<Editor initialContent={content} />);

// Then
await waitFor(() =>
expect(screen.getByRole('textbox')).not.toContainHTML(content),
);
});
});
13 changes: 11 additions & 2 deletions platforms/web/lib/useWysiwyg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import { RefObject, useEffect, useRef } from 'react';
import { RefObject, useCallback, useEffect, useRef } from 'react';

import { InputEventProcessor } from './types.js';
import { useFormattingFunctions } from './useFormattingFunctions';
Expand Down Expand Up @@ -59,20 +59,29 @@ export function useWysiwyg(wysiwygProps?: WysiwygProps) {
const ref = useEditor();
const modelRef = useRef<HTMLDivElement>(null);

const composerModel = useComposerModel(ref, wysiwygProps?.initialContent);
const { composerModel, initModel } = useComposerModel(
ref,
wysiwygProps?.initialContent,
);
const { testRef, utilities: testUtilities } = useTestCases(
ref,
composerModel,
);

const formattingFunctions = useFormattingFunctions(ref, composerModel);

const onError = useCallback(
(content?: string) => initModel(content),
[initModel],
);

const { content, actionStates, areListenersReady } = useListeners(
ref,
modelRef,
composerModel,
testUtilities,
formattingFunctions,
onError,
wysiwygProps?.initialContent,
wysiwygProps?.inputEventProcessor,
);
Expand Down

0 comments on commit 27b8f04

Please sign in to comment.