re #142429. html/svg renderer

This commit is contained in:
rebornix 2022-02-07 13:58:30 -08:00
parent f2b7964599
commit ac308c3e03
No known key found for this signature in database
GPG key ID: 181FC90D15393C20
4 changed files with 83 additions and 44 deletions

View file

@ -24,7 +24,9 @@
"mimeTypes": [
"image/gif",
"image/png",
"image/jpeg"
"image/jpeg",
"image/svg+xml",
"text/html"
]
}
]

View file

@ -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<void> = (_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<void> = (ctx) => {
const disposables = new Map<string, IDisposable>();
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) {

View file

@ -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);

View file

@ -602,7 +602,6 @@ const _mimeTypeInfo = new Map<string, MimeTypeInfo>([
['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 }],