Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Dispose webviewhost on panel load failure #13129

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/2 Fixes/13106.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
If a webpanel fails to load, dispose our webviewhost so that it can try again.
4 changes: 4 additions & 0 deletions src/client/common/application/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1060,6 +1060,10 @@ export type WebPanelMessage = {
// Wraps the VS Code webview panel
export const IWebPanel = Symbol('IWebPanel');
export interface IWebPanel {
/**
* Event is fired when the load for a web panel fails
*/
readonly loadFailed: Event<void>;
/**
* Convert a uri for the local file system to one that can be used inside webviews.
*
Expand Down
84 changes: 48 additions & 36 deletions src/client/common/application/webPanels/webPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
import '../../extensions';

import * as path from 'path';
import { Uri, Webview, WebviewOptions, WebviewPanel, window } from 'vscode';
import { Event, EventEmitter, Uri, Webview, WebviewOptions, WebviewPanel, window } from 'vscode';
import { Identifiers } from '../../../datascience/constants';
import { traceError } from '../../logger';
import { IFileSystem } from '../../platform/types';
import { IDisposableRegistry } from '../../types';
import * as localize from '../../utils/localize';
Expand All @@ -14,6 +15,7 @@ import { IWebPanel, IWebPanelOptions, WebPanelMessage } from '../types';
export class WebPanel implements IWebPanel {
private panel: WebviewPanel | undefined;
private loadPromise: Promise<void>;
private loadFailedEmitter = new EventEmitter<void>();

constructor(
private fs: IFileSystem,
Expand Down Expand Up @@ -43,6 +45,9 @@ export class WebPanel implements IWebPanel {
this.loadPromise = this.load();
}

public get loadFailed(): Event<void> {
return this.loadFailedEmitter.event;
}
public async show(preserveFocus: boolean) {
await this.loadPromise;
if (this.panel) {
Expand Down Expand Up @@ -89,42 +94,49 @@ export class WebPanel implements IWebPanel {

// tslint:disable-next-line:no-any
private async load() {
if (this.panel) {
const localFilesExist = await Promise.all(this.options.scripts.map((s) => this.fs.fileExists(s)));
if (localFilesExist.every((exists) => exists === true)) {
// Call our special function that sticks this script inside of an html page
// and translates all of the paths to vscode-resource URIs
this.panel.webview.html = await this.generateLocalReactHtml(this.panel.webview);

// Reset when the current panel is closed
this.disposableRegistry.push(
this.panel.onDidDispose(() => {
this.panel = undefined;
this.options.listener.dispose().ignoreErrors();
})
);

this.disposableRegistry.push(
this.panel.webview.onDidReceiveMessage((message) => {
// Pass the message onto our listener
this.options.listener.onMessage(message.type, message.payload);
})
);

this.disposableRegistry.push(
this.panel.onDidChangeViewState((_e) => {
// Pass the state change onto our listener
this.options.listener.onChangeViewState(this);
})
);

// Set initial state
this.options.listener.onChangeViewState(this);
} else {
// Indicate that we can't load the file path
const badPanelString = localize.DataScience.badWebPanelFormatString();
this.panel.webview.html = badPanelString.format(this.options.scripts.join(', '));
try {
if (this.panel) {
const localFilesExist = await Promise.all(this.options.scripts.map((s) => this.fs.fileExists(s)));
if (localFilesExist.every((exists) => exists === true)) {
// Call our special function that sticks this script inside of an html page
// and translates all of the paths to vscode-resource URIs
this.panel.webview.html = await this.generateLocalReactHtml(this.panel.webview);

// Reset when the current panel is closed
this.disposableRegistry.push(
this.panel.onDidDispose(() => {
this.panel = undefined;
this.options.listener.dispose().ignoreErrors();
})
);

this.disposableRegistry.push(
this.panel.webview.onDidReceiveMessage((message) => {
// Pass the message onto our listener
this.options.listener.onMessage(message.type, message.payload);
})
);

this.disposableRegistry.push(
this.panel.onDidChangeViewState((_e) => {
// Pass the state change onto our listener
this.options.listener.onChangeViewState(this);
})
);

// Set initial state
this.options.listener.onChangeViewState(this);
} else {
// Indicate that we can't load the file path
const badPanelString = localize.DataScience.badWebPanelFormatString();
this.panel.webview.html = badPanelString.format(this.options.scripts.join(', '));
}
}
} catch (error) {
// If our web panel failes to load, report that out so whatever
// is hosting the panel can clean up
traceError(`Error Loading WebPanel: ${error}`);
this.loadFailedEmitter.fire();
}
}

Expand Down
30 changes: 16 additions & 14 deletions src/client/datascience/webViewHost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,9 @@ export abstract class WebViewHost<IMapping> implements IDisposable {
private webPanel: IWebPanel | undefined;
private webPanelInit: Deferred<void> | undefined = createDeferred<void>();
private messageListener: IWebPanelMessageListener;
private themeChangeHandler: IDisposable | undefined;
private settingsChangeHandler: IDisposable | undefined;
private themeIsDarkPromise: Deferred<boolean> | undefined = createDeferred<boolean>();
private startupStopwatch = new StopWatch();
private readonly _disposables: IDisposable[] = [];

constructor(
@unmanaged() protected configService: IConfigurationService,
Expand Down Expand Up @@ -62,12 +61,12 @@ export abstract class WebViewHost<IMapping> implements IDisposable {
);

// Listen for settings changes from vscode.
this.themeChangeHandler = this.workspaceService.onDidChangeConfiguration(this.onPossibleSettingsChange, this);
this._disposables.push(this.workspaceService.onDidChangeConfiguration(this.onPossibleSettingsChange, this));

// Listen for settings changes
this.settingsChangeHandler = this.configService
.getSettings(undefined)
.onDidChange(this.onDataScienceSettingsChanged.bind(this));
this._disposables.push(
this.configService.getSettings(undefined).onDidChange(this.onDataScienceSettingsChanged.bind(this))
);
}

public async show(preserveFocus: boolean): Promise<void> {
Expand All @@ -91,14 +90,9 @@ export abstract class WebViewHost<IMapping> implements IDisposable {
this.webPanel.close();
this.webPanel = undefined;
}
if (this.themeChangeHandler) {
this.themeChangeHandler.dispose();
this.themeChangeHandler = undefined;
}
if (this.settingsChangeHandler) {
this.settingsChangeHandler.dispose();
this.settingsChangeHandler = undefined;
}

this._disposables.forEach((item) => item.dispose());

this.webPanelInit = undefined;
this.themeIsDarkPromise = undefined;
}
Expand Down Expand Up @@ -272,6 +266,9 @@ export abstract class WebViewHost<IMapping> implements IDisposable {
additionalPaths: workspaceFolder ? [workspaceFolder.fsPath] : []
});

// Track to seee if our web panel fails to load
this._disposables.push(this.webPanel.loadFailed(this.onWebPanelLoadFailed, this));

traceInfo('Web view created.');
}

Expand All @@ -294,6 +291,11 @@ export abstract class WebViewHost<IMapping> implements IDisposable {
this.postMessageInternal(SharedMessages.UpdateSettings, dsSettings).ignoreErrors();
};

// If our webpanel fails to load then just dispose ourselves
private onWebPanelLoadFailed = async () => {
this.dispose();
};

private getValue<T>(workspaceConfig: WorkspaceConfiguration, section: string, defaultValue: T): T {
if (workspaceConfig) {
return workspaceConfig.get(section, defaultValue);
Expand Down
4 changes: 4 additions & 0 deletions src/test/datascience/mountedWebView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export class MountedWebView implements IMountedWebView, IDisposable {
private active = true;
private visible = true;
private disposedEvent = new EventEmitter<void>();
private loadFailedEmitter = new EventEmitter<void>();

constructor(mount: () => ReactWrapper<any, Readonly<{}>, React.Component>, public readonly id: string) {
// Setup the acquireVsCodeApi. The react control will cache this value when it's mounted.
Expand Down Expand Up @@ -97,6 +98,9 @@ export class MountedWebView implements IMountedWebView, IDisposable {
public get onDisposed() {
return this.disposedEvent.event;
}
public get loadFailed(): Event<void> {
return this.loadFailedEmitter.event;
}
public attach(options: IWebPanelOptions) {
this.webPanelListener = options.listener;

Expand Down
6 changes: 6 additions & 0 deletions src/test/datascience/uiTests/webBrowserPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ export class WebBrowserPanel implements IWebPanel, IDisposable {
private panel?: WebviewPanel;
private server?: IWebServer;
private serverUrl: string | undefined;
private loadFailedEmitter = new EventEmitter<void>();
constructor(private readonly disposableRegistry: IDisposableRegistry, private readonly options: IWebPanelOptions) {
this.disposableRegistry.push(this);
const webViewOptions: WebviewOptions = {
Expand Down Expand Up @@ -174,6 +175,11 @@ export class WebBrowserPanel implements IWebPanel, IDisposable {
console.error('Failed to start Web Browser Panel', ex)
);
}

public get loadFailed(): Event<void> {
return this.loadFailedEmitter.event;
}

public asWebviewUri(localResource: Uri): Uri {
const filePath = localResource.fsPath;
const name = path.basename(path.dirname(filePath));
Expand Down