diff --git a/packages/roosterjs-content-model-core/lib/command/createModelFromHtml/createModelFromHtml.ts b/packages/roosterjs-content-model-core/lib/command/createModelFromHtml/createModelFromHtml.ts
index 68b34cbc3a9..714cad17fb6 100644
--- a/packages/roosterjs-content-model-core/lib/command/createModelFromHtml/createModelFromHtml.ts
+++ b/packages/roosterjs-content-model-core/lib/command/createModelFromHtml/createModelFromHtml.ts
@@ -1,4 +1,5 @@
import { convertInlineCss, retrieveCssRules } from './convertInlineCss';
+import { createDOMCreator } from '../../utils/domCreator';
import { createDomToModelContextForSanitizing } from './createDomToModelContextForSanitizing';
import { createEmptyModel, domToContentModel, parseFormat } from 'roosterjs-content-model-dom';
import type {
@@ -21,9 +22,7 @@ export function createModelFromHtml(
trustedHTMLHandler?: TrustedHTMLHandler,
defaultSegmentFormat?: ContentModelSegmentFormat
): ContentModelDocument {
- const doc = html
- ? new DOMParser().parseFromString(trustedHTMLHandler?.(html) ?? html, 'text/html')
- : null;
+ const doc = html ? createDOMCreator(trustedHTMLHandler).htmlToDOM(html) : null;
if (doc?.body) {
const context = createDomToModelContextForSanitizing(
diff --git a/packages/roosterjs-content-model-core/lib/command/paste/paste.ts b/packages/roosterjs-content-model-core/lib/command/paste/paste.ts
index 69cd08340d5..1028bc20765 100644
--- a/packages/roosterjs-content-model-core/lib/command/paste/paste.ts
+++ b/packages/roosterjs-content-model-core/lib/command/paste/paste.ts
@@ -6,8 +6,8 @@ import { retrieveHtmlInfo } from './retrieveHtmlInfo';
import type {
PasteTypeOrGetter,
ClipboardData,
- TrustedHTMLHandler,
IEditor,
+ DOMCreator,
} from 'roosterjs-content-model-types';
/**
@@ -22,9 +22,6 @@ export function paste(
pasteTypeOrGetter: PasteTypeOrGetter = 'normal'
) {
editor.focus();
-
- const trustedHTMLHandler = editor.getTrustedHTMLHandler();
-
if (!clipboardData.modelBeforePaste) {
editor.formatContentModel(model => {
clipboardData.modelBeforePaste = cloneModelForPaste(model);
@@ -34,7 +31,7 @@ export function paste(
}
// 1. Prepare variables
- const doc = createDOMFromHtml(clipboardData.rawHtml, trustedHTMLHandler);
+ const doc = createDOMFromHtml(clipboardData.rawHtml, editor.getDOMCreator());
const pasteType =
typeof pasteTypeOrGetter == 'function'
? pasteTypeOrGetter(doc, clipboardData)
@@ -50,7 +47,7 @@ export function paste(
pasteType,
(clipboardData.rawHtml == clipboardData.html
? doc
- : createDOMFromHtml(clipboardData.html, trustedHTMLHandler)
+ : createDOMFromHtml(clipboardData.html, editor.getDOMCreator())
)?.body
);
@@ -72,7 +69,7 @@ export function paste(
function createDOMFromHtml(
html: string | null | undefined,
- trustedHTMLHandler: TrustedHTMLHandler
+ domCreator: DOMCreator
): Document | null {
- return html ? new DOMParser().parseFromString(trustedHTMLHandler(html), 'text/html') : null;
+ return html ? domCreator.htmlToDOM(html) : null;
}
diff --git a/packages/roosterjs-content-model-core/lib/coreApi/restoreUndoSnapshot/restoreSnapshotHTML.ts b/packages/roosterjs-content-model-core/lib/coreApi/restoreUndoSnapshot/restoreSnapshotHTML.ts
index 96556c0e296..b8f8a76b34d 100644
--- a/packages/roosterjs-content-model-core/lib/coreApi/restoreUndoSnapshot/restoreSnapshotHTML.ts
+++ b/packages/roosterjs-content-model-core/lib/coreApi/restoreUndoSnapshot/restoreSnapshotHTML.ts
@@ -18,10 +18,7 @@ export function restoreSnapshotHTML(core: EditorCore, snapshot: Snapshot) {
} = core;
let refNode: Node | null = physicalRoot.firstChild;
- const body = new DOMParser().parseFromString(
- core.trustedHTMLHandler?.(snapshot.html) ?? snapshot.html,
- 'text/html'
- ).body;
+ const body = core.domCreator.htmlToDOM(snapshot.html).body;
for (let currentNode = body.firstChild; currentNode; ) {
const next = currentNode.nextSibling;
diff --git a/packages/roosterjs-content-model-core/lib/editor/Editor.ts b/packages/roosterjs-content-model-core/lib/editor/Editor.ts
index 6977040e9fa..84d4d653e8e 100644
--- a/packages/roosterjs-content-model-core/lib/editor/Editor.ts
+++ b/packages/roosterjs-content-model-core/lib/editor/Editor.ts
@@ -25,13 +25,14 @@ import type {
SnapshotsManager,
EditorCore,
EditorOptions,
- TrustedHTMLHandler,
Rect,
EntityState,
CachedElementHandler,
DomToModelOptionForCreateModel,
AnnounceData,
ExperimentalFeature,
+ LegacyTrustedHTMLHandler,
+ DOMCreator,
} from 'roosterjs-content-model-types';
/**
@@ -359,15 +360,26 @@ export class Editor implements IEditor {
}
/**
+ * @deprecated
* Get a function to convert HTML string to trusted HTML string.
* By default it will just return the input HTML directly. To override this behavior,
* pass your own trusted HTML handler to EditorOptions.trustedHTMLHandler
* See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/trusted-types
*/
- getTrustedHTMLHandler(): TrustedHTMLHandler {
+ getTrustedHTMLHandler(): LegacyTrustedHTMLHandler {
return this.getCore().trustedHTMLHandler;
}
+ /**
+ * Get a function to convert HTML string to a trust Document.
+ * By default it will just convert the original HTML string into a Document object directly.
+ * To override, pass your own trusted HTML handler to EditorOptions.trustedHTMLHandler
+ * See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/trusted-types
+ */
+ getDOMCreator(): DOMCreator {
+ return this.getCore().domCreator;
+ }
+
/**
* Get the scroll container of the editor
*/
diff --git a/packages/roosterjs-content-model-core/lib/editor/core/createEditorCore.ts b/packages/roosterjs-content-model-core/lib/editor/core/createEditorCore.ts
index 886b03dc7ee..1a6351a4513 100644
--- a/packages/roosterjs-content-model-core/lib/editor/core/createEditorCore.ts
+++ b/packages/roosterjs-content-model-core/lib/editor/core/createEditorCore.ts
@@ -1,5 +1,6 @@
import { coreApiMap } from '../../coreApi/coreApiMap';
import { createDarkColorHandler } from './DarkColorHandlerImpl';
+import { createDOMCreator, createTrustedHTMLHandler, isDOMCreator } from '../../utils/domCreator';
import { createDOMHelper } from './DOMHelperImpl';
import { createDomToModelSettings, createModelToDomSettings } from './createEditorDefaultSettings';
import { createEditorCorePlugins } from '../../corePlugin/createEditorCorePlugins';
@@ -18,6 +19,7 @@ import type {
*/
export function createEditorCore(contentDiv: HTMLDivElement, options: EditorOptions): EditorCore {
const corePlugins = createEditorCorePlugins(options, contentDiv);
+ const domCreator = createDOMCreator(options.trustedHTMLHandler);
return {
physicalRoot: contentDiv,
@@ -43,7 +45,11 @@ export function createEditorCore(contentDiv: HTMLDivElement, options: EditorOpti
options.knownColors,
options.generateColorKey
),
- trustedHTMLHandler: options.trustedHTMLHandler || defaultTrustHtmlHandler,
+ trustedHTMLHandler:
+ options.trustedHTMLHandler && !isDOMCreator(options.trustedHTMLHandler)
+ ? options.trustedHTMLHandler
+ : createTrustedHTMLHandler(domCreator),
+ domCreator: domCreator,
domHelper: createDOMHelper(contentDiv),
...getPluginState(corePlugins),
disposeErrorHandler: options.disposeErrorHandler,
@@ -90,13 +96,6 @@ function getIsMobileOrTablet(userAgent: string) {
return false;
}
-/**
- * @internal export for test only
- */
-export function defaultTrustHtmlHandler(html: string) {
- return html;
-}
-
function getPluginState(corePlugins: EditorCorePlugins): PluginState {
return {
domEvent: corePlugins.domEvent.getState(),
diff --git a/packages/roosterjs-content-model-core/lib/utils/domCreator.ts b/packages/roosterjs-content-model-core/lib/utils/domCreator.ts
new file mode 100644
index 00000000000..d9432630d52
--- /dev/null
+++ b/packages/roosterjs-content-model-core/lib/utils/domCreator.ts
@@ -0,0 +1,44 @@
+import type {
+ DOMCreator,
+ LegacyTrustedHTMLHandler,
+ TrustedHTMLHandler,
+} from 'roosterjs-content-model-types';
+
+/**
+ * @internal
+ */
+export const createTrustedHTMLHandler = (domCreator: DOMCreator): LegacyTrustedHTMLHandler => {
+ return (html: string) => domCreator.htmlToDOM(html).body.innerHTML;
+};
+
+/**
+ * @internal
+ */
+export function createDOMCreator(trustedHTMLHandler?: TrustedHTMLHandler): DOMCreator {
+ return trustedHTMLHandler && isDOMCreator(trustedHTMLHandler)
+ ? trustedHTMLHandler
+ : trustedHTMLHandlerToDOMCreator(trustedHTMLHandler as LegacyTrustedHTMLHandler);
+}
+
+/**
+ * @internal
+ */
+export function isDOMCreator(
+ trustedHTMLHandler: TrustedHTMLHandler
+): trustedHTMLHandler is DOMCreator {
+ return typeof (trustedHTMLHandler as DOMCreator).htmlToDOM === 'function';
+}
+
+/**
+ * @internal
+ */
+export const defaultTrustHtmlHandler: LegacyTrustedHTMLHandler = (html: string) => {
+ return html;
+};
+
+function trustedHTMLHandlerToDOMCreator(trustedHTMLHandler?: LegacyTrustedHTMLHandler): DOMCreator {
+ const handler = trustedHTMLHandler || defaultTrustHtmlHandler;
+ return {
+ htmlToDOM: (html: string) => new DOMParser().parseFromString(handler(html), 'text/html'),
+ };
+}
diff --git a/packages/roosterjs-content-model-core/test/coreApi/restoreUndoSnapshot/restoreSnapshotHTMLTest.ts b/packages/roosterjs-content-model-core/test/coreApi/restoreUndoSnapshot/restoreSnapshotHTMLTest.ts
index a238d818a6b..6bccf1c0b66 100644
--- a/packages/roosterjs-content-model-core/test/coreApi/restoreUndoSnapshot/restoreSnapshotHTMLTest.ts
+++ b/packages/roosterjs-content-model-core/test/coreApi/restoreUndoSnapshot/restoreSnapshotHTMLTest.ts
@@ -1,7 +1,11 @@
-import { EditorCore, Snapshot } from 'roosterjs-content-model-types';
+import { DOMCreator, EditorCore, Snapshot } from 'roosterjs-content-model-types';
import { restoreSnapshotHTML } from '../../../lib/coreApi/restoreUndoSnapshot/restoreSnapshotHTML';
import { wrap } from 'roosterjs-content-model-dom';
+const domCreator: DOMCreator = {
+ htmlToDOM: (html: string) => new DOMParser().parseFromString(html, 'text/html'),
+};
+
describe('restoreSnapshotHTML', () => {
let core: EditorCore;
let div: HTMLDivElement;
@@ -15,6 +19,7 @@ describe('restoreSnapshotHTML', () => {
entity: {
entityMap: {},
},
+ domCreator: domCreator,
} as any;
});
@@ -39,18 +44,17 @@ describe('restoreSnapshotHTML', () => {
});
it('Simple HTML, no entity, with trustHTMLHandler', () => {
- const trustedHTMLHandler = jasmine
- .createSpy('trustedHTMLHandler')
- .and.callFake((html: string) => html + html);
const snapshot: Snapshot = {
html: '
test1
',
} as any;
- (core).trustedHTMLHandler = trustedHTMLHandler;
+ const htmlToDOMSpy = spyOn(core.domCreator, 'htmlToDOM').and.callFake((html: string) =>
+ new DOMParser().parseFromString(html + html, 'text/html')
+ );
restoreSnapshotHTML(core, snapshot);
- expect(trustedHTMLHandler).toHaveBeenCalledWith('test1
');
+ expect(htmlToDOMSpy).toHaveBeenCalledWith('test1
');
expect(div.innerHTML).toBe('test1
test1
');
});
diff --git a/packages/roosterjs-content-model-core/test/editor/core/createEditorCoreTest.ts b/packages/roosterjs-content-model-core/test/editor/core/createEditorCoreTest.ts
index 197c55c1306..810cfc4ba1b 100644
--- a/packages/roosterjs-content-model-core/test/editor/core/createEditorCoreTest.ts
+++ b/packages/roosterjs-content-model-core/test/editor/core/createEditorCoreTest.ts
@@ -1,14 +1,11 @@
import * as createDefaultSettings from '../../../lib/editor/core/createEditorDefaultSettings';
import * as createEditorCorePlugins from '../../../lib/corePlugin/createEditorCorePlugins';
import * as DarkColorHandlerImpl from '../../../lib/editor/core/DarkColorHandlerImpl';
+import * as domCreator from '../../../lib/utils/domCreator';
import * as DOMHelperImpl from '../../../lib/editor/core/DOMHelperImpl';
import { coreApiMap } from '../../../lib/coreApi/coreApiMap';
-import { EditorCore, EditorOptions } from 'roosterjs-content-model-types';
-import {
- createEditorCore,
- defaultTrustHtmlHandler,
- getDarkColorFallback,
-} from '../../../lib/editor/core/createEditorCore';
+import { createEditorCore, getDarkColorFallback } from '../../../lib/editor/core/createEditorCore';
+import { DOMCreator, EditorCore, EditorOptions } from 'roosterjs-content-model-types';
describe('createEditorCore', () => {
function createMockedPlugin(stateName: string): any {
@@ -41,6 +38,10 @@ describe('createEditorCore', () => {
const mockedDomToModelSettings = 'DOMTOMODEL' as any;
const mockedModelToDomSettings = 'MODELTODOM' as any;
const mockedDOMHelper = 'DOMHELPER' as any;
+ const mockedDOMCreator: DOMCreator = {
+ htmlToDOM: mockedDOMHelper,
+ };
+ const mockedTrustHtmlHandler = 'TRUSTED' as any;
beforeEach(() => {
spyOn(createEditorCorePlugins, 'createEditorCorePlugins').and.returnValue(mockedPlugins);
@@ -54,6 +55,8 @@ describe('createEditorCore', () => {
mockedModelToDomSettings
);
spyOn(DOMHelperImpl, 'createDOMHelper').and.returnValue(mockedDOMHelper);
+ spyOn(domCreator, 'createDOMCreator').and.returnValue(mockedDOMCreator);
+ spyOn(domCreator, 'createTrustedHTMLHandler').and.returnValue(mockedTrustHtmlHandler);
});
function runTest(
@@ -88,7 +91,8 @@ describe('createEditorCore', () => {
modelToDomSettings: mockedModelToDomSettings,
},
darkColorHandler: mockedDarkColorHandler,
- trustedHTMLHandler: defaultTrustHtmlHandler,
+ trustedHTMLHandler: mockedTrustHtmlHandler,
+ domCreator: mockedDOMCreator,
cache: 'cache' as any,
format: 'format' as any,
copyPaste: 'copyPaste' as any,
@@ -146,7 +150,7 @@ describe('createEditorCore', () => {
const mockedPlugin1 = 'P1' as any;
const mockedPlugin2 = 'P2' as any;
const mockedGetDarkColor = 'DARK' as any;
- const mockedTrustHtmlHandler = 'TRUST' as any;
+ const mockedTrustHtmlHandler = 'OPTIONS TRUSTED' as any;
const mockedDisposeErrorHandler = 'DISPOSE' as any;
const mockedGenerateColorKey = 'KEY' as any;
const mockedKnownColors = 'COLORS' as any;
diff --git a/packages/roosterjs-content-model-core/test/utils/domCreatorTest.ts b/packages/roosterjs-content-model-core/test/utils/domCreatorTest.ts
new file mode 100644
index 00000000000..48d35e60e84
--- /dev/null
+++ b/packages/roosterjs-content-model-core/test/utils/domCreatorTest.ts
@@ -0,0 +1,38 @@
+import { createDOMCreator, isDOMCreator } from '../../lib/utils/domCreator';
+
+describe('domCreator', () => {
+ it('isDOMCreator - True', () => {
+ const trustedHTMLHandler = {
+ htmlToDOM: (html: string) => new DOMParser().parseFromString(html, 'text/html'),
+ };
+ expect(isDOMCreator(trustedHTMLHandler)).toBe(true);
+ });
+
+ it('isDOMCreator - False', () => {
+ const trustedHTMLHandler = (html: string) => html;
+ expect(isDOMCreator(trustedHTMLHandler)).toBe(false);
+ });
+
+ it('createDOMCreator - isDOMCreator', () => {
+ const trustedHTMLHandler = {
+ htmlToDOM: (html: string) => new DOMParser().parseFromString(html, 'text/html'),
+ };
+ const result = createDOMCreator(trustedHTMLHandler);
+ expect(result).toEqual(trustedHTMLHandler);
+ });
+
+ it('createDOMCreator - undefined', () => {
+ const doc = document.implementation.createHTMLDocument();
+ doc.body.appendChild(document.createTextNode('test'));
+ const result = createDOMCreator(undefined).htmlToDOM('test');
+ expect(result.lastChild).toEqual(doc.lastChild);
+ });
+
+ it('createDOMCreator - trustedHTML', () => {
+ const doc = document.implementation.createHTMLDocument();
+ doc.body.appendChild(document.createTextNode('test trusted'));
+ const trustedHTMLHandler = (html: string) => html + ' trusted';
+ const result = createDOMCreator(trustedHTMLHandler).htmlToDOM('test');
+ expect(result.lastChild).toEqual(doc.lastChild);
+ });
+});
diff --git a/packages/roosterjs-content-model-plugins/lib/paste/Excel/processPastedContentFromExcel.ts b/packages/roosterjs-content-model-plugins/lib/paste/Excel/processPastedContentFromExcel.ts
index f13491b0556..5bbc167fe76 100644
--- a/packages/roosterjs-content-model-plugins/lib/paste/Excel/processPastedContentFromExcel.ts
+++ b/packages/roosterjs-content-model-plugins/lib/paste/Excel/processPastedContentFromExcel.ts
@@ -1,11 +1,7 @@
import { addParser } from '../utils/addParser';
import { isNodeOfType, moveChildNodes } from 'roosterjs-content-model-dom';
import { setProcessor } from '../utils/setProcessor';
-import type {
- BeforePasteEvent,
- ElementProcessor,
- TrustedHTMLHandler,
-} from 'roosterjs-content-model-types';
+import type { BeforePasteEvent, DOMCreator, ElementProcessor } from 'roosterjs-content-model-types';
const LAST_TD_END_REGEX = /<\/\s*td\s*>((?!<\/\s*tr\s*>)[\s\S])*$/i;
const LAST_TR_END_REGEX = /<\/\s*tr\s*>((?!<\/\s*table\s*>)[\s\S])*$/i;
@@ -21,14 +17,14 @@ const DEFAULT_BORDER_STYLE = 'solid 1px #d4d4d4';
export function processPastedContentFromExcel(
event: BeforePasteEvent,
- trustedHTMLHandler: TrustedHTMLHandler,
+ domCreator: DOMCreator,
allowExcelNoBorderTable?: boolean
) {
const { fragment, htmlBefore, clipboardData } = event;
const html = clipboardData.html ? excelHandler(clipboardData.html, htmlBefore) : undefined;
if (html && clipboardData.html != html) {
- const doc = new DOMParser().parseFromString(trustedHTMLHandler(html), 'text/html');
+ const doc = domCreator.htmlToDOM(html);
moveChildNodes(fragment, doc?.body);
}
diff --git a/packages/roosterjs-content-model-plugins/lib/paste/PastePlugin.ts b/packages/roosterjs-content-model-plugins/lib/paste/PastePlugin.ts
index d3a3d5fa7cb..5fea7005843 100644
--- a/packages/roosterjs-content-model-plugins/lib/paste/PastePlugin.ts
+++ b/packages/roosterjs-content-model-plugins/lib/paste/PastePlugin.ts
@@ -99,7 +99,7 @@ export class PastePlugin implements EditorPlugin {
switch (pasteSource) {
case 'wordDesktop':
- processPastedContentFromWordDesktop(event, this.editor.getTrustedHTMLHandler());
+ processPastedContentFromWordDesktop(event, this.editor.getDOMCreator());
break;
case 'wacComponents':
processPastedContentWacComponents(event);
@@ -110,7 +110,7 @@ export class PastePlugin implements EditorPlugin {
// Handle HTML copied from Excel
processPastedContentFromExcel(
event,
- this.editor.getTrustedHTMLHandler(),
+ this.editor.getDOMCreator(),
this.allowExcelNoBorderTable
);
}
@@ -121,7 +121,7 @@ export class PastePlugin implements EditorPlugin {
);
break;
case 'powerPointDesktop':
- processPastedContentFromPowerPoint(event, this.editor.getTrustedHTMLHandler());
+ processPastedContentFromPowerPoint(event, this.editor.getDOMCreator());
break;
}
diff --git a/packages/roosterjs-content-model-plugins/lib/paste/PowerPoint/processPastedContentFromPowerPoint.ts b/packages/roosterjs-content-model-plugins/lib/paste/PowerPoint/processPastedContentFromPowerPoint.ts
index dad6ea6d2f5..2482c87758e 100644
--- a/packages/roosterjs-content-model-plugins/lib/paste/PowerPoint/processPastedContentFromPowerPoint.ts
+++ b/packages/roosterjs-content-model-plugins/lib/paste/PowerPoint/processPastedContentFromPowerPoint.ts
@@ -1,5 +1,5 @@
import { moveChildNodes } from 'roosterjs-content-model-dom';
-import type { BeforePasteEvent, TrustedHTMLHandler } from 'roosterjs-content-model-types';
+import type { BeforePasteEvent, DOMCreator } from 'roosterjs-content-model-types';
/**
* @internal
@@ -9,17 +9,14 @@ import type { BeforePasteEvent, TrustedHTMLHandler } from 'roosterjs-content-mod
export function processPastedContentFromPowerPoint(
event: BeforePasteEvent,
- trustedHTMLHandler: TrustedHTMLHandler
+ domCreator: DOMCreator
) {
const { fragment, clipboardData } = event;
if (clipboardData.html && !clipboardData.text && clipboardData.image) {
// It is possible that PowerPoint copied both image and HTML but not plain text.
// We always prefer HTML if any.
- const doc = new DOMParser().parseFromString(
- trustedHTMLHandler(clipboardData.html),
- 'text/html'
- );
+ const doc = domCreator.htmlToDOM(clipboardData.html);
moveChildNodes(fragment, doc?.body);
}
diff --git a/packages/roosterjs-content-model-plugins/lib/paste/WordDesktop/getStyleMetadata.ts b/packages/roosterjs-content-model-plugins/lib/paste/WordDesktop/getStyleMetadata.ts
index 72709b51b40..114914a902d 100644
--- a/packages/roosterjs-content-model-plugins/lib/paste/WordDesktop/getStyleMetadata.ts
+++ b/packages/roosterjs-content-model-plugins/lib/paste/WordDesktop/getStyleMetadata.ts
@@ -1,6 +1,6 @@
import { getObjectKeys } from 'roosterjs-content-model-dom';
import type { WordMetadata } from './WordMetadata';
-import type { BeforePasteEvent } from 'roosterjs-content-model-types';
+import type { BeforePasteEvent, DOMCreator } from 'roosterjs-content-model-types';
const FORMATING_REGEX = /[\n\t'{}"]+/g;
@@ -24,12 +24,9 @@ const FORMATING_REGEX = /[\n\t'{}"]+/g;
* 5. Save data in record and only use the required information.
*
*/
-export function getStyleMetadata(
- ev: BeforePasteEvent,
- trustedHTMLHandler: (val: string) => string
-) {
+export function getStyleMetadata(ev: BeforePasteEvent, domCreator: DOMCreator) {
const metadataMap: Map = new Map();
- const doc = new DOMParser().parseFromString(trustedHTMLHandler(ev.htmlBefore), 'text/html');
+ const doc = domCreator.htmlToDOM(ev.htmlBefore);
const styles = doc.querySelectorAll('style');
styles.forEach(style => {
diff --git a/packages/roosterjs-content-model-plugins/lib/paste/WordDesktop/processPastedContentFromWordDesktop.ts b/packages/roosterjs-content-model-plugins/lib/paste/WordDesktop/processPastedContentFromWordDesktop.ts
index 403127175cf..33f904b9935 100644
--- a/packages/roosterjs-content-model-plugins/lib/paste/WordDesktop/processPastedContentFromWordDesktop.ts
+++ b/packages/roosterjs-content-model-plugins/lib/paste/WordDesktop/processPastedContentFromWordDesktop.ts
@@ -11,6 +11,7 @@ import type {
ContentModelBlockFormat,
ContentModelListItemLevelFormat,
ContentModelTableFormat,
+ DOMCreator,
DomToModelContext,
ElementProcessor,
FormatParser,
@@ -25,11 +26,8 @@ const DEFAULT_BROWSER_LINE_HEIGHT_PERCENTAGE = 1.2;
* Handles Pasted content when source is Word Desktop
* @param ev BeforePasteEvent
*/
-export function processPastedContentFromWordDesktop(
- ev: BeforePasteEvent,
- trustedHTMLHandler: (text: string) => string
-) {
- const metadataMap: Map = getStyleMetadata(ev, trustedHTMLHandler);
+export function processPastedContentFromWordDesktop(ev: BeforePasteEvent, domCreator: DOMCreator) {
+ const metadataMap: Map = getStyleMetadata(ev, domCreator);
setProcessor(ev.domToModelOption, 'element', wordDesktopElementProcessor(metadataMap));
addParser(ev.domToModelOption, 'block', adjustPercentileLineHeight);
diff --git a/packages/roosterjs-content-model-plugins/test/paste/ContentModelPastePluginTest.ts b/packages/roosterjs-content-model-plugins/test/paste/ContentModelPastePluginTest.ts
index e78f0beb1eb..56f955ebafb 100644
--- a/packages/roosterjs-content-model-plugins/test/paste/ContentModelPastePluginTest.ts
+++ b/packages/roosterjs-content-model-plugins/test/paste/ContentModelPastePluginTest.ts
@@ -4,11 +4,14 @@ import * as getPasteSource from '../../lib/paste/pasteSourceValidations/getPaste
import * as PowerPointFile from '../../lib/paste/PowerPoint/processPastedContentFromPowerPoint';
import * as setProcessor from '../../lib/paste/utils/setProcessor';
import * as WacFile from '../../lib/paste/WacComponents/processPastedContentWacComponents';
-import { BeforePasteEvent, IEditor } from 'roosterjs-content-model-types';
+import { BeforePasteEvent, DOMCreator, IEditor } from 'roosterjs-content-model-types';
import { PastePlugin } from '../../lib/paste/PastePlugin';
import { PastePropertyNames } from '../../lib/paste/pasteSourceValidations/constants';
const trustedHTMLHandler = (val: string) => val;
+const domCreator: DOMCreator = {
+ htmlToDOM: (html: string) => new DOMParser().parseFromString(html, 'text/html'),
+};
const DEFAULT_TIMES_ADD_PARSER_CALLED = 4;
describe('Content Model Paste Plugin Test', () => {
@@ -17,6 +20,7 @@ describe('Content Model Paste Plugin Test', () => {
beforeEach(() => {
editor = ({
getTrustedHTMLHandler: () => trustedHTMLHandler,
+ getDOMCreator: () => domCreator,
} as any) as IEditor;
spyOn(addParser, 'addParser').and.callThrough();
spyOn(setProcessor, 'setProcessor').and.callThrough();
@@ -72,7 +76,7 @@ describe('Content Model Paste Plugin Test', () => {
expect(ExcelFile.processPastedContentFromExcel).toHaveBeenCalledWith(
event,
- trustedHTMLHandler,
+ domCreator,
undefined /*allowExcelNoBorderTable*/
);
expect(addParser.addParser).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED + 3);
@@ -89,7 +93,7 @@ describe('Content Model Paste Plugin Test', () => {
expect(ExcelFile.processPastedContentFromExcel).not.toHaveBeenCalledWith(
event,
- trustedHTMLHandler,
+ domCreator,
undefined /*allowExcelNoBorderTable*/
);
expect(addParser.addParser).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED);
@@ -105,7 +109,7 @@ describe('Content Model Paste Plugin Test', () => {
expect(ExcelFile.processPastedContentFromExcel).toHaveBeenCalledWith(
event,
- trustedHTMLHandler,
+ domCreator,
undefined /*allowExcelNoBorderTable*/
);
expect(addParser.addParser).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED + 1);
@@ -121,7 +125,7 @@ describe('Content Model Paste Plugin Test', () => {
expect(ExcelFile.processPastedContentFromExcel).toHaveBeenCalledWith(
event,
- trustedHTMLHandler,
+ domCreator,
undefined /*allowExcelNoBorderTable*/
);
expect(addParser.addParser).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED + 1);
@@ -137,7 +141,7 @@ describe('Content Model Paste Plugin Test', () => {
expect(PowerPointFile.processPastedContentFromPowerPoint).toHaveBeenCalledWith(
event,
- trustedHTMLHandler
+ domCreator
);
expect(addParser.addParser).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED);
expect(setProcessor.setProcessor).toHaveBeenCalledTimes(0);
diff --git a/packages/roosterjs-content-model-plugins/test/paste/getStyleMetadataTest.ts b/packages/roosterjs-content-model-plugins/test/paste/getStyleMetadataTest.ts
index 21e7ac1e9ae..0aae54bd19c 100644
--- a/packages/roosterjs-content-model-plugins/test/paste/getStyleMetadataTest.ts
+++ b/packages/roosterjs-content-model-plugins/test/paste/getStyleMetadataTest.ts
@@ -1,13 +1,17 @@
import { BeforePasteEvent } from 'roosterjs-content-model-types';
import { getStyleMetadata } from '../../lib/paste/WordDesktop/getStyleMetadata';
+const domCreator = {
+ htmlToDOM: (html: string) => new DOMParser().parseFromString(html, 'text/html'),
+};
+
describe('getStyleMetadata', () => {
it('Extract metadata from style element', () => {
const event = ({
htmlBefore:
'',
});
- const result = getStyleMetadata(event, (val: string) => val);
+ const result = getStyleMetadata(event, domCreator);
expect(result.get('l0:level1')).toEqual({
'mso-level-number-format': 'roman-upper',
diff --git a/packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromExcelTest.ts b/packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromExcelTest.ts
index 449cb65591b..42cd593c605 100644
--- a/packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromExcelTest.ts
+++ b/packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromExcelTest.ts
@@ -1,5 +1,5 @@
import * as PastePluginFile from '../../lib/paste/Excel/processPastedContentFromExcel';
-import { ContentModelDocument } from 'roosterjs-content-model-types';
+import { ContentModelDocument, DOMCreator } from 'roosterjs-content-model-types';
import { createBeforePasteEventMock } from './processPastedContentFromWordDesktopTest';
import { processPastedContentFromExcel } from '../../lib/paste/Excel/processPastedContentFromExcel';
import {
@@ -13,6 +13,9 @@ import {
let div: HTMLElement;
let fragment: DocumentFragment;
+const domCreator: DOMCreator = {
+ htmlToDOM: (html: string) => new DOMParser().parseFromString(html, 'text/html'),
+};
describe('processPastedContentFromExcelTest', () => {
function runTest(source?: string, expected?: string, expectedModel?: ContentModelDocument) {
@@ -26,7 +29,7 @@ describe('processPastedContentFromExcelTest', () => {
const event = createBeforePasteEventMock(fragment);
event.clipboardData.html = source;
- processPastedContentFromExcel(event, (s: string) => s);
+ processPastedContentFromExcel(event, domCreator);
const model = domToContentModel(
fragment,
@@ -349,7 +352,7 @@ describe('Do not run scenarios', () => {
if (excelHandler) {
spyOn(PastePluginFile, 'excelHandler').and.returnValue(excelHandler);
}
- processPastedContentFromExcel(event, (s: string) => s);
+ processPastedContentFromExcel(event, domCreator);
// Assert
while (div.firstChild) {
diff --git a/packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromPowerPointTest.ts b/packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromPowerPointTest.ts
index ed7bbef3432..73fc764fb6e 100644
--- a/packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromPowerPointTest.ts
+++ b/packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromPowerPointTest.ts
@@ -1,10 +1,6 @@
import * as moveChildNodes from 'roosterjs-content-model-dom/lib/domUtils/moveChildNodes';
import { processPastedContentFromPowerPoint } from '../../lib/paste/PowerPoint/processPastedContentFromPowerPoint';
-import type {
- BeforePasteEvent,
- ClipboardData,
- TrustedHTMLHandler,
-} from 'roosterjs-content-model-types';
+import type { BeforePasteEvent, ClipboardData, DOMCreator } from 'roosterjs-content-model-types';
const getPasteEvent = (): BeforePasteEvent => {
return {
@@ -29,7 +25,9 @@ const getPasteEvent = (): BeforePasteEvent => {
describe('processPastedContentFromPowerPointTest |', () => {
let ev: BeforePasteEvent;
- let trustedHTMLHandlerMock: TrustedHTMLHandler = (html: string) => html;
+ let trustedHTMLHandlerMock: DOMCreator = {
+ htmlToDOM: (html: string) => new DOMParser().parseFromString(html, 'text/html'),
+ };
let image: HTMLImageElement;
let doc: Document;
diff --git a/packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromWordDesktopTest.ts b/packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromWordDesktopTest.ts
index 07f17b4039d..7b301c86fe8 100644
--- a/packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromWordDesktopTest.ts
+++ b/packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromWordDesktopTest.ts
@@ -9,6 +9,10 @@ import {
moveChildNodes,
} from 'roosterjs-content-model-dom';
+const domCreator = {
+ htmlToDOM: (html: string) => new DOMParser().parseFromString(html, 'text/html'),
+};
+
describe('processPastedContentFromWordDesktopTest', () => {
let div: HTMLElement;
let fragment: DocumentFragment;
@@ -27,7 +31,7 @@ describe('processPastedContentFromWordDesktopTest', () => {
moveChildNodes(fragment, div);
}
const event = createBeforePasteEventMock(fragment, htmlBefore);
- processPastedContentFromWordDesktop(event, (val: string) => val);
+ processPastedContentFromWordDesktop(event, domCreator);
const model = domToContentModel(
fragment,
diff --git a/packages/roosterjs-content-model-types/lib/editor/EditorCore.ts b/packages/roosterjs-content-model-types/lib/editor/EditorCore.ts
index 42144b97f61..965ebb00722 100644
--- a/packages/roosterjs-content-model-types/lib/editor/EditorCore.ts
+++ b/packages/roosterjs-content-model-types/lib/editor/EditorCore.ts
@@ -17,7 +17,7 @@ import type { EditorContext } from '../context/EditorContext';
import type { EditorEnvironment } from '../parameter/EditorEnvironment';
import type { ModelToDomOption } from '../context/ModelToDomOption';
import type { OnNodeCreated } from '../context/ModelToDomSettings';
-import type { TrustedHTMLHandler } from '../parameter/TrustedHTMLHandler';
+import type { DOMCreator, LegacyTrustedHTMLHandler } from '../parameter/TrustedHTMLHandler';
import type { Rect } from '../parameter/Rect';
import type {
ContentModelFormatter,
@@ -361,11 +361,20 @@ export interface EditorCore extends PluginState {
readonly darkColorHandler: DarkColorHandler;
/**
- * A handler to convert HTML string to a trust HTML string.
- * By default it will just return the original HTML string directly.
+ * @deprecated
+ * @see DOMCreator
+ * A handler to convert HTML string to a trust string.
+ * By default it will just convert the original HTML string into a string directly.
* To override, pass your own trusted HTML handler to EditorOptions.trustedHTMLHandler
*/
- readonly trustedHTMLHandler: TrustedHTMLHandler;
+ readonly trustedHTMLHandler: LegacyTrustedHTMLHandler;
+
+ /**
+ * A handler to convert HTML string to a trust Document.
+ * By default it will just convert the original HTML string into a Document object directly.
+ * To override, pass your own trusted HTML handler to EditorOptions.trustedHTMLHandler
+ */
+ readonly domCreator: DOMCreator;
/**
* A helper class to provide DOM access APIs
diff --git a/packages/roosterjs-content-model-types/lib/editor/IEditor.ts b/packages/roosterjs-content-model-types/lib/editor/IEditor.ts
index 39c5e08e7ae..b34d2f24fec 100644
--- a/packages/roosterjs-content-model-types/lib/editor/IEditor.ts
+++ b/packages/roosterjs-content-model-types/lib/editor/IEditor.ts
@@ -15,7 +15,7 @@ import type {
FormatContentModelOptions,
} from '../parameter/FormatContentModelOptions';
import type { DarkColorHandler } from '../context/DarkColorHandler';
-import type { TrustedHTMLHandler } from '../parameter/TrustedHTMLHandler';
+import type { DOMCreator, LegacyTrustedHTMLHandler } from '../parameter/TrustedHTMLHandler';
import type { Rect } from '../parameter/Rect';
import type { EntityState } from '../parameter/FormatContentModelContext';
import type { ExperimentalFeature } from './ExperimentalFeature';
@@ -193,12 +193,21 @@ export interface IEditor {
hasFocus(): boolean;
/**
+ * @deprecated use getDOMCreator instead
* Get a function to convert HTML string to trusted HTML string.
* By default it will just return the input HTML directly. To override this behavior,
* pass your own trusted HTML handler to EditorOptions.trustedHTMLHandler
* See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/trusted-types
*/
- getTrustedHTMLHandler(): TrustedHTMLHandler;
+ getTrustedHTMLHandler(): LegacyTrustedHTMLHandler;
+
+ /**
+ * Get a function to convert HTML string to a trust Document.
+ * By default it will just convert the original HTML string into a Document object directly.
+ * To override, pass your own trusted HTML handler to EditorOptions.trustedHTMLHandler
+ * See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/trusted-types
+ */
+ getDOMCreator(): DOMCreator;
/**
* Get the scroll container of the editor
diff --git a/packages/roosterjs-content-model-types/lib/index.ts b/packages/roosterjs-content-model-types/lib/index.ts
index 215f43d77df..241660c5d01 100644
--- a/packages/roosterjs-content-model-types/lib/index.ts
+++ b/packages/roosterjs-content-model-types/lib/index.ts
@@ -424,7 +424,11 @@ export { DOMEventHandlerFunction, DOMEventRecord } from './parameter/DOMEventRec
export { EdgeLinkPreview } from './parameter/EdgeLinkPreview';
export { ClipboardData } from './parameter/ClipboardData';
export { AnnounceData, KnownAnnounceStrings } from './parameter/AnnounceData';
-export { TrustedHTMLHandler } from './parameter/TrustedHTMLHandler';
+export {
+ TrustedHTMLHandler,
+ DOMCreator,
+ LegacyTrustedHTMLHandler,
+} from './parameter/TrustedHTMLHandler';
export { Rect } from './parameter/Rect';
export { ValueSanitizer } from './parameter/ValueSanitizer';
export { DOMHelper } from './parameter/DOMHelper';
diff --git a/packages/roosterjs-content-model-types/lib/parameter/TrustedHTMLHandler.ts b/packages/roosterjs-content-model-types/lib/parameter/TrustedHTMLHandler.ts
index 05785843669..16c678ee01e 100644
--- a/packages/roosterjs-content-model-types/lib/parameter/TrustedHTMLHandler.ts
+++ b/packages/roosterjs-content-model-types/lib/parameter/TrustedHTMLHandler.ts
@@ -1,4 +1,17 @@
/**
+ * @deprecated Use DOMCreator instead
* A handler type to convert HTML string to a trust HTML string
*/
-export type TrustedHTMLHandler = (html: string) => string;
+export type LegacyTrustedHTMLHandler = (html: string) => string;
+
+/**
+ * A handler type to convert HTML string to a DOM object
+ */
+export interface DOMCreator {
+ htmlToDOM: (html: string) => Document;
+}
+
+/**
+ * A handler type to convert HTML string to a trust HTML string or a DOM object
+ */
+export type TrustedHTMLHandler = DOMCreator | LegacyTrustedHTMLHandler;
diff --git a/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts b/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts
index b80589167e9..082368f5489 100644
--- a/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts
+++ b/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts
@@ -421,10 +421,8 @@ export class EditorAdapter extends Editor implements ILegacyEditor {
insertContent(content: string, option?: InsertOption) {
if (content) {
const doc = this.getDocument();
- const body = new DOMParser().parseFromString(
- this.getCore().trustedHTMLHandler(content),
- 'text/html'
- )?.body;
+ const body = this.getCore().domCreator.htmlToDOM(content)?.body;
+
let allNodes = body?.childNodes ? toArray(body.childNodes) : [];
// If it is to insert on new line, and there are more than one node in the collection, wrap all nodes with