Skip to content

Commit

Permalink
Merge branch 'master' into u/jisong/2859
Browse files Browse the repository at this point in the history
  • Loading branch information
JiuqingSong authored Dec 9, 2024
2 parents 7918a8c + 65b880d commit a0357ea
Show file tree
Hide file tree
Showing 24 changed files with 223 additions and 95 deletions.
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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(
Expand Down
13 changes: 5 additions & 8 deletions packages/roosterjs-content-model-core/lib/command/paste/paste.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { retrieveHtmlInfo } from './retrieveHtmlInfo';
import type {
PasteTypeOrGetter,
ClipboardData,
TrustedHTMLHandler,
IEditor,
DOMCreator,
} from 'roosterjs-content-model-types';

/**
Expand All @@ -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);
Expand All @@ -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)
Expand All @@ -50,7 +47,7 @@ export function paste(
pasteType,
(clipboardData.rawHtml == clipboardData.html
? doc
: createDOMFromHtml(clipboardData.html, trustedHTMLHandler)
: createDOMFromHtml(clipboardData.html, editor.getDOMCreator())
)?.body
);

Expand All @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
16 changes: 14 additions & 2 deletions packages/roosterjs-content-model-core/lib/editor/Editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@ import type {
SnapshotsManager,
EditorCore,
EditorOptions,
TrustedHTMLHandler,
Rect,
EntityState,
CachedElementHandler,
DomToModelOptionForCreateModel,
AnnounceData,
ExperimentalFeature,
LegacyTrustedHTMLHandler,
DOMCreator,
} from 'roosterjs-content-model-types';

/**
Expand Down Expand Up @@ -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
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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(),
Expand Down
44 changes: 44 additions & 0 deletions packages/roosterjs-content-model-core/lib/utils/domCreator.ts
Original file line number Diff line number Diff line change
@@ -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'),
};
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -15,6 +19,7 @@ describe('restoreSnapshotHTML', () => {
entity: {
entityMap: {},
},
domCreator: domCreator,
} as any;
});

Expand All @@ -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: '<div>test1</div>',
} as any;

(<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('<div>test1</div>');
expect(htmlToDOMSpy).toHaveBeenCalledWith('<div>test1</div>');
expect(div.innerHTML).toBe('<div>test1</div><div>test1</div>');
});

Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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);
Expand All @@ -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(
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down
38 changes: 38 additions & 0 deletions packages/roosterjs-content-model-core/test/utils/domCreatorTest.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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);
}

Expand Down
Loading

0 comments on commit a0357ea

Please sign in to comment.