mirror of
https://github.com/Microsoft/vscode
synced 2024-07-17 11:07:22 +00:00
use tt policy when rendering html from markdown, https://github.com/microsoft/vscode/issues/106396
This commit is contained in:
parent
93a1dab31f
commit
a8cf19d34a
117
.vscode/searches/TrustedTypes.code-search
vendored
117
.vscode/searches/TrustedTypes.code-search
vendored
|
@ -4,24 +4,27 @@
|
|||
# Excluding: *.test.ts
|
||||
# ContextLines: 3
|
||||
|
||||
22 results - 14 files
|
||||
22 results - 15 files
|
||||
|
||||
src/vs/base/browser/dom.ts:
|
||||
26 // this is a workaround for innerHTML not allowing for "asymetric" accessors
|
||||
27 // see https://github.com/microsoft/vscode/issues/106396#issuecomment-692625393
|
||||
28 // and https://github.com/microsoft/TypeScript/issues/30024
|
||||
29: node.innerHTML = value as unknown as string;
|
||||
30 }
|
||||
31
|
||||
32 export function isInDOM(node: Node | null): boolean {
|
||||
|
||||
src/vs/base/browser/markdownRenderer.ts:
|
||||
161 const strValue = values[0];
|
||||
162 const span = element.querySelector(`div[data-code="${id}"]`);
|
||||
163 if (span) {
|
||||
164: span.innerHTML = strValue;
|
||||
165 }
|
||||
166 }).catch(err => {
|
||||
167 // ignore
|
||||
|
||||
243 return true;
|
||||
244 }
|
||||
245
|
||||
246: element.innerHTML = insane(renderedMarkdown, {
|
||||
247 allowedSchemes,
|
||||
248 // allowedTags should included everything that markdown renders to.
|
||||
249 // Since we have our own sanitize function for marked, it's possible we missed some tag so let insane make sure.
|
||||
273 };
|
||||
274
|
||||
275 if (_ttpInsane) {
|
||||
276: element.innerHTML = _ttpInsane.createHTML(renderedMarkdown, insaneOptions) as unknown as string;
|
||||
277 } else {
|
||||
278: element.innerHTML = insane(renderedMarkdown, insaneOptions);
|
||||
279 }
|
||||
280
|
||||
281 // signal that async code blocks can be now be inserted
|
||||
|
||||
src/vs/base/browser/ui/contextview/contextview.ts:
|
||||
157 this.shadowRootHostElement = DOM.$('.shadow-root-host');
|
||||
|
@ -50,6 +53,15 @@ src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts:
|
|||
326 document.head.appendChild(styleTag);
|
||||
327 }
|
||||
|
||||
src/vs/editor/browser/core/markdownRenderer.ts:
|
||||
88
|
||||
89 const element = document.createElement('span');
|
||||
90
|
||||
91: element.innerHTML = MarkdownRenderer._ttpTokenizer
|
||||
92 ? MarkdownRenderer._ttpTokenizer.createHTML(value, tokenization) as unknown as string
|
||||
93 : tokenizeToString(value, tokenization);
|
||||
94
|
||||
|
||||
src/vs/editor/browser/view/domLineBreaksComputer.ts:
|
||||
107 allCharOffsets[i] = tmp[0];
|
||||
108 allVisibleColumns[i] = tmp[1];
|
||||
|
@ -60,21 +72,21 @@ src/vs/editor/browser/view/domLineBreaksComputer.ts:
|
|||
113 containerDomNode.style.top = '10000';
|
||||
|
||||
src/vs/editor/browser/view/viewLayer.ts:
|
||||
507 private _finishRenderingNewLines(ctx: IRendererContext<T>, domNodeIsEmpty: boolean, newLinesHTML: string, wasNew: boolean[]): void {
|
||||
508 const lastChild = <HTMLElement>this.domNode.lastChild;
|
||||
509 if (domNodeIsEmpty || !lastChild) {
|
||||
510: this.domNode.innerHTML = newLinesHTML;
|
||||
511 } else {
|
||||
512 lastChild.insertAdjacentHTML('afterend', newLinesHTML);
|
||||
513 }
|
||||
512 }
|
||||
513 const lastChild = <HTMLElement>this.domNode.lastChild;
|
||||
514 if (domNodeIsEmpty || !lastChild) {
|
||||
515: this.domNode.innerHTML = newLinesHTML;
|
||||
516 } else {
|
||||
517 lastChild.insertAdjacentHTML('afterend', newLinesHTML);
|
||||
518 }
|
||||
|
||||
525 private _finishRenderingInvalidLines(ctx: IRendererContext<T>, invalidLinesHTML: string, wasInvalid: boolean[]): void {
|
||||
526 const hugeDomNode = document.createElement('div');
|
||||
527
|
||||
528: hugeDomNode.innerHTML = invalidLinesHTML;
|
||||
529
|
||||
530 for (let i = 0; i < ctx.linesLength; i++) {
|
||||
531 const line = ctx.lines[i];
|
||||
530 private _finishRenderingInvalidLines(ctx: IRendererContext<T>, invalidLinesHTML: string, wasInvalid: boolean[]): void {
|
||||
531 const hugeDomNode = document.createElement('div');
|
||||
532
|
||||
533: hugeDomNode.innerHTML = invalidLinesHTML;
|
||||
534
|
||||
535 for (let i = 0; i < ctx.linesLength; i++) {
|
||||
536 const line = ctx.lines[i];
|
||||
|
||||
src/vs/editor/browser/widget/diffEditorWidget.ts:
|
||||
2157
|
||||
|
@ -141,23 +153,6 @@ src/vs/editor/test/browser/controller/imeTester.ts:
|
|||
74
|
||||
75 let startBtn = document.createElement('button');
|
||||
|
||||
src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts:
|
||||
455
|
||||
456 private getMarkdownDragImage(templateData: MarkdownCellRenderTemplate): HTMLElement {
|
||||
457 const dragImageContainer = DOM.$('.cell-drag-image.monaco-list-row.focused.markdown-cell-row');
|
||||
458: dragImageContainer.innerHTML = templateData.container.outerHTML;
|
||||
459
|
||||
460 // Remove all rendered content nodes after the
|
||||
461 const markdownContent = dragImageContainer.querySelector('.cell.markdown')!;
|
||||
|
||||
641 return null;
|
||||
642 }
|
||||
643
|
||||
644: editorContainer.innerHTML = richEditorText;
|
||||
645
|
||||
646 return dragImageContainer;
|
||||
647 }
|
||||
|
||||
src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts:
|
||||
375 addMouseoverListeners(outputNode, outputId);
|
||||
376 const content = data.content;
|
||||
|
@ -177,18 +172,18 @@ src/vs/workbench/contrib/webview/browser/pre/main.js:
|
|||
399 applyStyles(newDocument, newDocument.body);
|
||||
|
||||
src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts:
|
||||
281
|
||||
282 const content = model.main.textEditorModel.getValue(EndOfLinePreference.LF);
|
||||
283 if (!input.resource.path.endsWith('.md')) {
|
||||
284: this.content.innerHTML = content;
|
||||
285 this.updateSizeClasses();
|
||||
286 this.decorateContent();
|
||||
287 this.contentDisposables.push(this.keybindingService.onDidUpdateKeybindings(() => this.decorateContent()));
|
||||
280
|
||||
281 const content = model.main;
|
||||
282 if (!input.resource.path.endsWith('.md')) {
|
||||
283: this.content.innerHTML = content;
|
||||
284 this.updateSizeClasses();
|
||||
285 this.decorateContent();
|
||||
286 this.contentDisposables.push(this.keybindingService.onDidUpdateKeybindings(() => this.decorateContent()));
|
||||
|
||||
303 const innerContent = document.createElement('div');
|
||||
304 innerContent.classList.add('walkThroughContent'); // only for markdown files
|
||||
305 const markdown = this.expandMacros(content);
|
||||
306: innerContent.innerHTML = marked(markdown, { renderer });
|
||||
307 this.content.appendChild(innerContent);
|
||||
308
|
||||
309 model.snippets.forEach((snippet, i) => {
|
||||
302 const innerContent = document.createElement('div');
|
||||
303 innerContent.classList.add('walkThroughContent'); // only for markdown files
|
||||
304 const markdown = this.expandMacros(content);
|
||||
305: innerContent.innerHTML = marked(markdown, { renderer });
|
||||
306 this.content.appendChild(innerContent);
|
||||
307
|
||||
308 model.snippets.forEach((snippet, i) => {
|
||||
|
|
|
@ -9,7 +9,7 @@ import { onUnexpectedError } from 'vs/base/common/errors';
|
|||
import { IMarkdownString, parseHrefAndDimensions, removeMarkdownEscapes } from 'vs/base/common/htmlContent';
|
||||
import { defaultGenerator } from 'vs/base/common/idGenerator';
|
||||
import * as marked from 'vs/base/common/marked/marked';
|
||||
import { insane } from 'vs/base/common/insane/insane';
|
||||
import { insane, InsaneOptions } from 'vs/base/common/insane/insane';
|
||||
import { parse } from 'vs/base/common/marshalling';
|
||||
import { cloneAndChange } from 'vs/base/common/objects';
|
||||
import { escape } from 'vs/base/common/strings';
|
||||
|
@ -30,6 +30,12 @@ export interface MarkdownRenderOptions extends FormattedTextRenderOptions {
|
|||
baseUrl?: URI;
|
||||
}
|
||||
|
||||
const _ttpInsane = window.trustedTypes?.createPolicy('insane', {
|
||||
createHTML(value, options: InsaneOptions): string {
|
||||
return insane(value, options);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Low-level way create a html element from a markdown string.
|
||||
*
|
||||
|
@ -227,25 +233,16 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
|||
if (value.length > 100_000) {
|
||||
value = `${value.substr(0, 100_000)}…`;
|
||||
}
|
||||
const renderedMarkdown = marked.parse(
|
||||
markdown.supportThemeIcons ? markdownEscapeEscapedCodicons(value) : value,
|
||||
markedOptions
|
||||
);
|
||||
|
||||
function filter(token: { tag: string, attrs: { readonly [key: string]: string } }): boolean {
|
||||
if (token.tag === 'span' && markdown.isTrusted && (Object.keys(token.attrs).length === 1)) {
|
||||
if (token.attrs['style']) {
|
||||
return !!token.attrs['style'].match(/^(color\:#[0-9a-fA-F]+;)?(background-color\:#[0-9a-fA-F]+;)?$/);
|
||||
} else if (token.attrs['class']) {
|
||||
// The class should match codicon rendering in src\vs\base\common\codicons.ts
|
||||
return !!token.attrs['class'].match(/^codicon codicon-[a-z\-]+( codicon-animation-[a-z\-]+)?$/);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
// escape theme icons
|
||||
if (markdown.supportThemeIcons) {
|
||||
value = markdownEscapeEscapedCodicons(value);
|
||||
}
|
||||
|
||||
element.innerHTML = insane(renderedMarkdown, {
|
||||
const renderedMarkdown = marked.parse(value, markedOptions);
|
||||
|
||||
|
||||
// sanitize with insane
|
||||
const insaneOptions = {
|
||||
allowedSchemes,
|
||||
// allowedTags should included everything that markdown renders to.
|
||||
// Since we have our own sanitize function for marked, it's possible we missed some tag so let insane make sure.
|
||||
|
@ -261,9 +258,27 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
|||
'th': ['align'],
|
||||
'td': ['align']
|
||||
},
|
||||
filter
|
||||
});
|
||||
filter(token: { tag: string, attrs: { readonly [key: string]: string } }): boolean {
|
||||
if (token.tag === 'span' && markdown.isTrusted && (Object.keys(token.attrs).length === 1)) {
|
||||
if (token.attrs['style']) {
|
||||
return !!token.attrs['style'].match(/^(color\:#[0-9a-fA-F]+;)?(background-color\:#[0-9a-fA-F]+;)?$/);
|
||||
} else if (token.attrs['class']) {
|
||||
// The class should match codicon rendering in src\vs\base\common\codicons.ts
|
||||
return !!token.attrs['class'].match(/^codicon codicon-[a-z\-]+( codicon-animation-[a-z\-]+)?$/);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
if (_ttpInsane) {
|
||||
element.innerHTML = _ttpInsane.createHTML(renderedMarkdown, insaneOptions) as unknown as string;
|
||||
} else {
|
||||
element.innerHTML = insane(renderedMarkdown, insaneOptions);
|
||||
}
|
||||
|
||||
// signal that async code blocks can be now be inserted
|
||||
signalInnerHTML!();
|
||||
|
||||
return element;
|
||||
|
|
14
src/vs/base/common/insane/insane.d.ts
vendored
14
src/vs/base/common/insane/insane.d.ts
vendored
|
@ -3,13 +3,15 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export interface InsaneOptions {
|
||||
readonly allowedSchemes?: readonly string[],
|
||||
readonly allowedTags?: readonly string[],
|
||||
readonly allowedAttributes?: { readonly [key: string]: string[] },
|
||||
readonly filter?: (token: { tag: string, attrs: { readonly [key: string]: string } }) => boolean,
|
||||
}
|
||||
|
||||
export function insane(
|
||||
html: string,
|
||||
options?: {
|
||||
readonly allowedSchemes?: readonly string[],
|
||||
readonly allowedTags?: readonly string[],
|
||||
readonly allowedAttributes?: { readonly [key: string]: string[] },
|
||||
readonly filter?: (token: { tag: string, attrs: { readonly [key: string]: string } }) => boolean,
|
||||
},
|
||||
options?: InsaneOptions,
|
||||
strict?: boolean,
|
||||
): string;
|
||||
|
|
Loading…
Reference in a new issue