diff --git a/extensions/notebook-renderers/package.json b/extensions/notebook-renderers/package.json index b1f507d7adb..876a53953b7 100644 --- a/extensions/notebook-renderers/package.json +++ b/extensions/notebook-renderers/package.json @@ -24,7 +24,9 @@ "mimeTypes": [ "image/gif", "image/png", - "image/jpeg" + "image/jpeg", + "image/svg+xml", + "text/html" ] } ] diff --git a/extensions/notebook-renderers/src/index.ts b/extensions/notebook-renderers/src/index.ts index c1419dca780..4aa0d6b444a 100644 --- a/extensions/notebook-renderers/src/index.ts +++ b/extensions/notebook-renderers/src/index.ts @@ -3,33 +3,97 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type { ActivationFunction } from 'vscode-notebook-renderer'; +import type { ActivationFunction, OutputItem } from 'vscode-notebook-renderer'; interface IDisposable { dispose(): void; } -export const activate: ActivationFunction = (_ctx) => { +function renderImage(outputInfo: OutputItem, element: HTMLElement): IDisposable { + const blob = new Blob([outputInfo.data()], { type: outputInfo.mime }); + const src = URL.createObjectURL(blob); + const disposable = { + dispose: () => { + URL.revokeObjectURL(src); + } + }; + + const image = document.createElement('img'); + image.src = src; + const display = document.createElement('div'); + display.classList.add('display'); + display.appendChild(image); + element.appendChild(display); + + return disposable; +} + +const ttPolicy = window.trustedTypes?.createPolicy('notebookRenderer', { + createHTML: value => value, + createScript: value => value, +}); + +const preservedScriptAttributes: (keyof HTMLScriptElement)[] = [ + 'type', 'src', 'nonce', 'noModule', 'async', +]; + +const domEval = (container: Element) => { + const arr = Array.from(container.getElementsByTagName('script')); + for (let n = 0; n < arr.length; n++) { + const node = arr[n]; + const scriptTag = document.createElement('script'); + const trustedScript = ttPolicy?.createScript(node.innerText) ?? node.innerText; + scriptTag.text = trustedScript as string; + for (const key of preservedScriptAttributes) { + const val = node[key] || node.getAttribute && node.getAttribute(key); + if (val) { + scriptTag.setAttribute(key, val as any); + } + } + + // TODO@connor4312: should script with src not be removed? + container.appendChild(scriptTag).parentNode!.removeChild(scriptTag); + } +}; + +function renderHTML(outputInfo: OutputItem, container: HTMLElement): void { + const htmlContent = outputInfo.text(); + const element = document.createElement('div'); + const trustedHtml = ttPolicy?.createHTML(htmlContent) ?? htmlContent; + element.innerHTML = trustedHtml as string; + container.appendChild(element); + domEval(element); +} + +export const activate: ActivationFunction = (ctx) => { const disposables = new Map(); return { renderOutputItem: (outputInfo, element) => { - const blob = new Blob([outputInfo.data()], { type: outputInfo.mime }); - const src = URL.createObjectURL(blob); - const disposable = { - dispose: () => { - URL.revokeObjectURL(src); - } - }; + switch (outputInfo.mime) { + case 'text/html': + case 'image/svg+xml': + { + if (!ctx.workspace.isTrusted) { + return; + } + + renderHTML(outputInfo, element); + } + break; + case 'image/gif': + case 'image/png': + case 'image/jpeg': + { + const disposable = renderImage(outputInfo, element); + disposables.set(outputInfo.id, disposable); + } + break; + default: + break; + } - const image = document.createElement('img'); - image.src = src; - const display = document.createElement('div'); - display.classList.add('display'); - display.appendChild(image); - element.appendChild(display); - disposables.set(outputInfo.id, disposable); }, disposeOutputItem: (id: string | undefined) => { if (id) { diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts index 3958db4e7d8..92880cc9420 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts @@ -185,33 +185,7 @@ class PlainTextRendererContrib extends Disposable implements IOutputTransformCon } } -class HTMLRendererContrib extends Disposable implements IOutputTransformContribution { - getType() { - return RenderOutputType.Html; - } - - getMimetypes() { - return ['text/html', 'image/svg+xml']; - } - - constructor( - public notebookEditor: INotebookDelegateForOutput, - ) { - super(); - } - - render(output: ICellOutputViewModel, item: IOutputItemDto, container: HTMLElement, notebookUri: URI): IRenderOutput { - const str = getStringValue(item); - return { - type: RenderOutputType.Html, - source: output, - htmlContent: str - }; - } -} - OutputRendererRegistry.registerOutputTransform(JavaScriptRendererContrib); -OutputRendererRegistry.registerOutputTransform(HTMLRendererContrib); OutputRendererRegistry.registerOutputTransform(PlainTextRendererContrib); OutputRendererRegistry.registerOutputTransform(JSErrorRendererContrib); OutputRendererRegistry.registerOutputTransform(StreamRendererContrib); diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 2e9943d49af..ef32b4470d3 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -602,7 +602,6 @@ const _mimeTypeInfo = new Map([ ['image/svg+xml', { supportedByCore: true }], ['application/json', { alwaysSecure: true, supportedByCore: true }], [Mimes.text, { alwaysSecure: true, supportedByCore: true }], - ['text/html', { supportedByCore: true }], ['text/x-javascript', { alwaysSecure: true, supportedByCore: true }], // secure because rendered as text, not executed ['application/vnd.code.notebook.error', { alwaysSecure: true, supportedByCore: true }], ['application/vnd.code.notebook.stdout', { alwaysSecure: true, supportedByCore: true, mergeable: true }],