diff --git a/frontend/src/components/common/html-to-react/html-to-react.tsx b/frontend/src/components/common/html-to-react/html-to-react.tsx index bdc1be0f5..44e85edf3 100644 --- a/frontend/src/components/common/html-to-react/html-to-react.tsx +++ b/frontend/src/components/common/html-to-react/html-to-react.tsx @@ -3,6 +3,7 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ +import { measurePerformance } from '../../../utils/measure-performance' import type { ParserOptions } from '@hedgedoc/html-to-react' import { convertHtmlToReact } from '@hedgedoc/html-to-react' import type DOMPurify from 'dompurify' @@ -24,8 +25,12 @@ export interface HtmlToReactProps { */ export const HtmlToReact: React.FC = ({ htmlCode, domPurifyConfig, parserOptions }) => { const elements = useMemo(() => { - const sanitizedHtmlCode = sanitize(htmlCode, { ...domPurifyConfig, RETURN_DOM_FRAGMENT: false, RETURN_DOM: false }) - return convertHtmlToReact(sanitizedHtmlCode, parserOptions) + const sanitizedHtmlCode = measurePerformance('html-to-react: sanitize', () => { + return sanitize(htmlCode, { ...domPurifyConfig, RETURN_DOM_FRAGMENT: false, RETURN_DOM: false }) + }) + return measurePerformance('html-to-react: convertHtmlToReact', () => { + return convertHtmlToReact(sanitizedHtmlCode, parserOptions) + }) }, [domPurifyConfig, htmlCode, parserOptions]) return {elements} diff --git a/frontend/src/components/markdown-renderer/markdown-to-react/markdown-to-react.tsx b/frontend/src/components/markdown-renderer/markdown-to-react/markdown-to-react.tsx index 0dc523247..06a5ea88f 100644 --- a/frontend/src/components/markdown-renderer/markdown-to-react/markdown-to-react.tsx +++ b/frontend/src/components/markdown-renderer/markdown-to-react/markdown-to-react.tsx @@ -3,6 +3,7 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ +import { measurePerformance } from '../../../utils/measure-performance' import { HtmlToReact } from '../../common/html-to-react/html-to-react' import type { MarkdownRendererExtension } from '../extensions/_base-classes/markdown-renderer-extension' import { useCombinedNodePreprocessor } from './hooks/use-combined-node-preprocessor' @@ -43,7 +44,9 @@ export const MarkdownToReact: React.FC = ({ }, [nodeToReactTransformer, markdownRenderExtensions]) useMemo(() => { - nodeToReactTransformer.setLineIds(lineNumberMapper.updateLineMapping(markdownContentLines)) + measurePerformance('markdown-to-react: update-line-mapping', () => { + nodeToReactTransformer.setLineIds(lineNumberMapper.updateLineMapping(markdownContentLines)) + }) }, [nodeToReactTransformer, lineNumberMapper, markdownContentLines]) const nodePreProcessor = useCombinedNodePreprocessor(markdownRenderExtensions) @@ -60,7 +63,11 @@ export const MarkdownToReact: React.FC = ({ [nodeToReactTransformer, nodePreProcessor] ) - const html = useMemo(() => markdownIt.render(markdownContentLines.join('\n')), [markdownContentLines, markdownIt]) + const html = useMemo(() => { + return measurePerformance('markdown-to-react: markdown-it', () => + markdownIt.render(markdownContentLines.join('\n')) + ) + }, [markdownContentLines, markdownIt]) const domPurifyConfig: DOMPurify.Config = useMemo( () => ({ ADD_TAGS: markdownRenderExtensions.flatMap((extension) => extension.buildTagNameAllowList()) diff --git a/frontend/src/utils/measure-performance.spec.ts b/frontend/src/utils/measure-performance.spec.ts new file mode 100644 index 000000000..c3f954c76 --- /dev/null +++ b/frontend/src/utils/measure-performance.spec.ts @@ -0,0 +1,42 @@ +/* + * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { measurePerformance } from './measure-performance' +import { Mock } from 'ts-mockery' + +describe('measure performance', () => { + it('uses the global performance functions', () => { + const measurementName = 'marker-name' + const markMock = jest.fn() + const measureMock = jest.fn() + const startMaker = `${measurementName} - start` + const endMarker = `${measurementName} - end` + + Object.defineProperty(window, 'performance', { + get: () => + Mock.of({ + mark: markMock, + measure: measureMock + }) + }) + const result = measurePerformance(measurementName, () => { + return 'value' + }) + expect(result).toBe('value') + expect(markMock).toHaveBeenNthCalledWith(1, startMaker) + expect(markMock).toHaveBeenNthCalledWith(2, endMarker) + expect(measureMock).toBeCalledWith(measurementName, startMaker, endMarker) + }) + + it('runs the task without global performance functions', () => { + Object.defineProperty(window, 'performance', { + get: () => undefined + }) + const result = measurePerformance('measurementName', () => { + return 'value' + }) + expect(result).toBe('value') + }) +}) diff --git a/frontend/src/utils/measure-performance.ts b/frontend/src/utils/measure-performance.ts new file mode 100644 index 000000000..703efb29c --- /dev/null +++ b/frontend/src/utils/measure-performance.ts @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +/** + * Uses the browser's performance API to measure how long a given task is running. + * + * @param measurementName The name of the measurement. Is also used to derive the name of the markers + * @param task The task to run + * @return the result of the task + */ +export const measurePerformance = (measurementName: string, task: () => T): T => { + if (!window.performance || !window.performance.mark || !window.performance.measure) { + return task() + } + const startMarkName = `${measurementName} - start` + const endMarkName = `${measurementName} - end` + window.performance.mark(startMarkName) + const result = task() + window.performance.mark(endMarkName) + window.performance.measure(measurementName, startMarkName, endMarkName) + return result +}