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
+}