Skip to content

Commit d8568bb

Browse files
authored
Let notebook renderers throw error to fallback to next mime type (#171567)
For #171562
1 parent dcb358a commit d8568bb

File tree

2 files changed

+103
-28
lines changed

2 files changed

+103
-28
lines changed

src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,11 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Themable {
260260
'notebook-cell-renderer-not-found-error': nls.localize({
261261
key: 'notebook.error.rendererNotFound',
262262
comment: ['$0 is a placeholder for the mime type']
263-
}, "No renderer found for '$0' a"),
263+
}, "No renderer found for '$0'"),
264+
'notebook-cell-renderer-fallbacks-exhausted': nls.localize({
265+
key: 'notebook.error.rendererFallbacksExhausted',
266+
comment: ['$0 is a placeholder for the mime type']
267+
}, "Could not render content for '$0'"),
264268
};
265269
}
266270

src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts

Lines changed: 98 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -714,8 +714,13 @@ async function webviewPreloads(ctx: PreloadContext) {
714714
}
715715
};
716716

717-
interface ExtendedOutputItem {
718-
readonly _allOutputItems: ReadonlyArray<{ readonly mime: string; getItem(): Promise<rendererApi.OutputItem | undefined> }>;
717+
interface AdditionalOutputItemInfo {
718+
readonly mime: string;
719+
getItem(): Promise<rendererApi.OutputItem | undefined>;
720+
}
721+
722+
interface ExtendedOutputItem extends rendererApi.OutputItem {
723+
readonly _allOutputItems: ReadonlyArray<AdditionalOutputItemInfo>;
719724
}
720725

721726
let hasWarnedAboutAllOutputItemsProposal = false;
@@ -726,15 +731,15 @@ async function webviewPreloads(ctx: PreloadContext) {
726731
metadata: unknown,
727732
valueBytes: Uint8Array,
728733
allOutputItemData: ReadonlyArray<{ readonly mime: string }>
729-
): rendererApi.OutputItem & ExtendedOutputItem {
734+
): ExtendedOutputItem {
730735

731736
function create(
732737
id: string,
733738
mime: string,
734739
metadata: unknown,
735740
valueBytes: Uint8Array,
736-
): rendererApi.OutputItem & ExtendedOutputItem {
737-
return Object.freeze<rendererApi.OutputItem & ExtendedOutputItem>({
741+
): ExtendedOutputItem {
742+
return Object.freeze<ExtendedOutputItem>({
738743
id,
739744
mime,
740745
metadata,
@@ -1268,6 +1273,8 @@ async function webviewPreloads(ctx: PreloadContext) {
12681273
}
12691274
});
12701275

1276+
const renderFallbackErrorName = 'vscode.fallbackToNextRenderer';
1277+
12711278
class Renderer {
12721279

12731280
private _onMessageEvent = createEmitter();
@@ -1305,11 +1312,16 @@ async function webviewPreloads(ctx: PreloadContext) {
13051312
this.postDebugMessage('Rendered output item', { id: item.id, duration: `${performance.now() - renderStart}ms` });
13061313

13071314
} catch (e) {
1308-
if (!signal.aborted) {
1309-
showRenderError(`Error rendering output item using '${this.data.id}'`, element, e instanceof Error ? [e] : []);
1315+
if (signal.aborted) {
1316+
return;
1317+
}
13101318

1311-
this.postDebugMessage('Rendering output item failed', { id: item.id, error: e + '' });
1319+
if (e instanceof Error && e.name === renderFallbackErrorName) {
1320+
throw e;
13121321
}
1322+
1323+
showRenderError(`Error rendering output item using '${this.data.id}'`, element, e instanceof Error ? [e] : []);
1324+
this.postDebugMessage('Rendering output item failed', { id: item.id, error: e + '' });
13131325
}
13141326
}
13151327

@@ -1560,7 +1572,63 @@ async function webviewPreloads(ctx: PreloadContext) {
15601572
this._renderers.get(rendererId)?.disposeOutputItem(outputId);
15611573
}
15621574

1563-
public async render(info: rendererApi.OutputItem, preferredRendererId: string | undefined, element: HTMLElement, signal: AbortSignal): Promise<void> {
1575+
public async render(item: ExtendedOutputItem, preferredRendererId: string | undefined, element: HTMLElement, signal: AbortSignal): Promise<void> {
1576+
const primaryRenderer = this.findRenderer(preferredRendererId, item);
1577+
if (!primaryRenderer) {
1578+
const errorMessage = (document.documentElement.style.getPropertyValue('--notebook-cell-renderer-not-found-error') || '').replace('$0', () => item.mime);
1579+
this.showRenderError(item, element, errorMessage);
1580+
return;
1581+
}
1582+
1583+
// Try primary renderer first
1584+
if (!(await this._doRender(item, element, primaryRenderer, signal)).continue) {
1585+
return;
1586+
}
1587+
1588+
// Primary renderer failed in an expected way. Fallback to render the next mime types
1589+
for (const additionalItemData of item._allOutputItems) {
1590+
if (additionalItemData.mime === item.mime) {
1591+
continue;
1592+
}
1593+
1594+
const additionalItem = await additionalItemData.getItem();
1595+
if (signal.aborted) {
1596+
return;
1597+
}
1598+
1599+
if (additionalItem) {
1600+
const renderer = this.findRenderer(undefined, additionalItem);
1601+
if (renderer) {
1602+
if (!(await this._doRender(additionalItem, element, renderer, signal)).continue) {
1603+
return; // We rendered successfully
1604+
}
1605+
}
1606+
}
1607+
}
1608+
1609+
// All renderers have failed and there is nothing left to fallback to
1610+
const errorMessage = (document.documentElement.style.getPropertyValue('--notebook-cell-renderer-fallbacks-exhausted') || '').replace('$0', () => item.mime);
1611+
this.showRenderError(item, element, errorMessage);
1612+
}
1613+
1614+
private async _doRender(item: rendererApi.OutputItem, element: HTMLElement, renderer: Renderer, signal: AbortSignal): Promise<{ continue: boolean }> {
1615+
try {
1616+
await renderer.renderOutputItem(item, element, signal);
1617+
return { continue: false }; // We rendered successfully
1618+
} catch (e) {
1619+
if (signal.aborted) {
1620+
return { continue: false };
1621+
}
1622+
1623+
if (e instanceof Error && e.name === renderFallbackErrorName) {
1624+
return { continue: true };
1625+
} else {
1626+
throw e; // Bail and let callers handle unknown errors
1627+
}
1628+
}
1629+
}
1630+
1631+
private findRenderer(preferredRendererId: string | undefined, info: rendererApi.OutputItem) {
15641632
let renderer: Renderer | undefined;
15651633

15661634
if (typeof preferredRendererId === 'string') {
@@ -1578,26 +1646,24 @@ async function webviewPreloads(ctx: PreloadContext) {
15781646
renderer = renderers[0];
15791647
}
15801648
}
1649+
return renderer;
1650+
}
15811651

1582-
if (renderer) {
1583-
await renderer.renderOutputItem(info, element, signal);
1584-
} else {
1585-
const errorContainer = document.createElement('div');
1652+
private showRenderError(info: rendererApi.OutputItem, element: HTMLElement, errorMessage: string) {
1653+
const errorContainer = document.createElement('div');
15861654

1587-
const error = document.createElement('div');
1588-
error.className = 'no-renderer-error';
1589-
const errorText = (document.documentElement.style.getPropertyValue('--notebook-cell-renderer-not-found-error') || '').replace('$0', () => info.mime);
1590-
error.innerText = errorText;
1655+
const error = document.createElement('div');
1656+
error.className = 'no-renderer-error';
1657+
error.innerText = errorMessage;
15911658

1592-
const cellText = document.createElement('div');
1593-
cellText.innerText = info.text();
1659+
const cellText = document.createElement('div');
1660+
cellText.innerText = info.text();
15941661

1595-
errorContainer.appendChild(error);
1596-
errorContainer.appendChild(cellText);
1662+
errorContainer.appendChild(error);
1663+
errorContainer.appendChild(cellText);
15971664

1598-
element.innerText = '';
1599-
element.appendChild(errorContainer);
1600-
}
1665+
element.innerText = '';
1666+
element.appendChild(errorContainer);
16011667
}
16021668
}();
16031669

@@ -1819,7 +1885,7 @@ async function webviewPreloads(ctx: PreloadContext) {
18191885
public readonly id: string;
18201886
public readonly element: HTMLElement;
18211887

1822-
private readonly outputItem: rendererApi.OutputItem;
1888+
private readonly outputItem: ExtendedOutputItem;
18231889

18241890
/// Internal field that holds text content
18251891
private _content: { readonly value: string; readonly version: number; readonly metadata: NotebookCellMetadata };
@@ -1840,7 +1906,7 @@ async function webviewPreloads(ctx: PreloadContext) {
18401906
});
18411907

18421908
let cachedData: { readonly version: number; readonly value: Uint8Array } | undefined;
1843-
this.outputItem = Object.freeze<rendererApi.OutputItem>({
1909+
this.outputItem = Object.freeze<ExtendedOutputItem>({
18441910
id,
18451911
mime,
18461912

@@ -1868,7 +1934,12 @@ async function webviewPreloads(ctx: PreloadContext) {
18681934

18691935
blob(): Blob {
18701936
return new Blob([this.data()], { type: this.mime });
1871-
}
1937+
},
1938+
1939+
_allOutputItems: [{
1940+
mime,
1941+
getItem: async () => this.outputItem,
1942+
}]
18721943
});
18731944

18741945
const root = document.getElementById('container')!;

0 commit comments

Comments
 (0)