Skip to content
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
64 changes: 64 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,67 @@ yarn playwright test --retries <retries>
[vscode-messages]: https://code.visualstudio.com/api/extension-guides/webview#passing-messages-from-an-extension-to-a-webview
[vscode-webview]: https://github.com/rebornix/vscode-webview-react
[vscode-webview-react]: https://github.com/rebornix/vscode-webview-react

## Using the External API

VSCode Trace Extension provides an external API that adopter extensions can rely on for communication. Currently the API is limited to the following:

```javascript
getActiveExperiment(): Experiment | undefined
getActiveWebviewPanels(): { [key: string]: TraceViewerPanel | undefined; }
getActiveWebviews(): vscode.WebviewView[]
onWebviewCreated(listener: (data: vscode.WebviewView) => void): void
onWebviewPanelCreated(listener: (data: vscode.WebviewPanel) => void): void
```

### Using the API from Adopter Extensions

```javascript
//The following retrieves the API object from the vscode-trace-extension
const ext = vscode.extensions.getExtension("tracecompass-community.vscode-trace-extension");
const importedApi = ext.exports;
```

Once you have the API object, you can proceed to make API calls. For example, if you wish to retrieve the active experiment in the Trace Viewer, the following API call can be used:

```javascript
const experiment = importedApi.getActiveExperiment();
```

The API provides getters to retrieve the active webviews and panels. This can be useful for scenarios when webviews/panels were created before the adopter extension was activated but the adopter extension still wants to handle messages from them.

```javascript
for (const webview of importedApi.getActiveWebviews()) {
webview.webview.onDidReceiveMessage((message) => {
switch (message.command) {
case "webviewReady":
console.log("From adopter extension - webviewReady signal received");
break;
default:
break;
}
});
}
```

The API also provides a way to attach a listener for when webview or webview panel is created. Note that this listener will not be called for webviews and panels created before the registration of the listener. It is recommended to register the listeners during the activation of the adopter extensions.

```javascript
importedApi.onWebviewPanelCreated(_panel => {
// For newly created panel, handle messages from webviews
_panel.webview.onDidReceiveMessage((message) => {
switch (message.command) {
case "webviewReady":
console.log("From adopter extension - webviewReady signal received");
break;
default:
break;
}
});
_panel.onDidDispose(() => {
console.log("panel disposed");
});
});
```

As a general rule, adopter extensions should retrieve and handle the webviews and webview panels once during their activation by calling `getActiveWebviews` and `getActiveWebviewPanels`. This ensures that the webviews and panels created before the activation of the adopter extension are handled. To handle any new webviews and panels created afterwards, listeners can be registered by calling `onWebviewCreated` and `onWebviewPanelCreated`.
7 changes: 6 additions & 1 deletion vscode-trace-extension/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@ import { fileHandler, openOverviewHandler, resetZoomHandler, undoRedoHandler, zo
import { TraceServerConnectionStatusService } from './utils/trace-server-status';
import { getTspClientUrl, updateTspClient } from './utils/tspClient';
import { TraceExtensionLogger } from './utils/trace-extension-logger';
import { ExternalAPI, traceExtensionAPI} from './external-api/external-api';
import { TraceExtensionWebviewManager } from './utils/trace-extension-webview-manager';
import { VSCODE_MESSAGES } from 'vscode-trace-common/lib/messages/vscode-message-manager';
import { TraceViewerPanel } from './trace-viewer-panel/trace-viewer-webview-panel';
import { TspClientProvider } from 'vscode-trace-common/lib/client/tsp-client-provider-impl';

export let traceLogger: TraceExtensionLogger;
export const traceExtensionWebviewManager: TraceExtensionWebviewManager = new TraceExtensionWebviewManager();
const tspClientProvider = new TspClientProvider(getTspClientUrl(), undefined);

export function activate(context: vscode.ExtensionContext): void {
export function activate(context: vscode.ExtensionContext): ExternalAPI {
traceLogger = new TraceExtensionLogger('Trace Extension');

const serverStatusBarItemPriority = 1;
Expand Down Expand Up @@ -117,10 +120,12 @@ export function activate(context: vscode.ExtensionContext): void {

vscode.commands.executeCommand('setContext', 'traceViewer.markerSetsPresent', false);
vscode.commands.executeCommand('setContext', 'traceViewer.markerCategoriesPresent', false);
return traceExtensionAPI;
}

export function deactivate(): void {
traceLogger.disposeChannel();
traceExtensionWebviewManager.dispose();
}

async function startTraceServerIfAvailable(): Promise<void> {
Expand Down
64 changes: 64 additions & 0 deletions vscode-trace-extension/src/external-api/external-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/***************************************************************************************
* Copyright (c) 2023 BlackBerry Limited and others.
*
* Licensed under the MIT license. See LICENSE file in the project root for details.
***************************************************************************************/
import { Experiment } from 'tsp-typescript-client/lib/models/experiment';
import { TraceViewerPanel } from '../trace-viewer-panel/trace-viewer-webview-panel';
import * as vscode from 'vscode';
import { traceExtensionWebviewManager } from '../extension';

export interface ExternalAPI {
getActiveExperiment(): Experiment | undefined,
getActiveWebviewPanels(): { [key: string]: TraceViewerPanel | undefined; },
getActiveWebviews(): vscode.WebviewView[],
onWebviewCreated(listener: (data: vscode.WebviewView) => void): void,
onWebviewPanelCreated(listener: (data: vscode.WebviewPanel) => void): void,
}

export const traceExtensionAPI: ExternalAPI = {
/**
* Retrieves the currently active experiment
*
* @returns Experiment if one is currently active, otherwise undefined
*/
getActiveExperiment(): Experiment | undefined {
return TraceViewerPanel.getCurrentExperiment();
},

/**
* Retrieves active trace panels
*
* @returns Key value pairs where the value is of TraceViewerPanel type if panel with that key is active, otherwise undefined
*/
getActiveWebviewPanels(): { [key: string]: TraceViewerPanel | undefined; } {
return TraceViewerPanel.activePanels;
},

/**
* Retrieves active webviews
*
* @returns List of active webviews
*/
getActiveWebviews(): vscode.WebviewView[] {
return traceExtensionWebviewManager.getAllActiveWebviews();
},

/**
* Registers an event listener for onWebviewCreated event
*
* @param listener event listener
*/
onWebviewCreated(listener: (data: vscode.WebviewView) => void): void {
traceExtensionWebviewManager.onWebviewCreated(listener);
},

/**
* Registers an event listener for onWebviewPanelCreated event
*
* @param listener event listener
*/
onWebviewPanelCreated(listener: (data: vscode.WebviewPanel) => void): void {
traceExtensionWebviewManager.onWebviewPanelCreated(listener);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { TraceServerConnectionStatusService } from '../../utils/trace-server-sta
import { getTraceServerUrl, getTspClientUrl } from '../../utils/tspClient';
import { convertSignalExperiment } from 'vscode-trace-common/lib/signals/vscode-signal-converter';
import { VSCODE_MESSAGES } from 'vscode-trace-common/lib/messages/vscode-message-manager';
import { traceExtensionWebviewManager } from 'vscode-trace-extension/src/extension';

const JSONBig = JSONBigConfig({
useNativeBigInt: true,
Expand Down Expand Up @@ -47,6 +48,7 @@ export class TraceExplorerAvailableViewsProvider implements vscode.WebviewViewPr
};

webviewView.webview.html = this._getHtmlForWebview(webviewView.webview);
traceExtensionWebviewManager.fireWebviewCreated(webviewView);

// Handle messages from the webview
webviewView.webview.onDidReceiveMessage(message => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { TraceViewerPanel } from '../../trace-viewer-panel/trace-viewer-webview-
import { TraceServerConnectionStatusService } from '../../utils/trace-server-status';
import { getTraceServerUrl, getTspClientUrl } from '../../utils/tspClient';
import { VSCODE_MESSAGES } from 'vscode-trace-common/lib/messages/vscode-message-manager';
import { traceExtensionWebviewManager } from 'vscode-trace-extension/src/extension';

const JSONBig = JSONBigConfig({
useNativeBigInt: true,
Expand All @@ -20,7 +21,7 @@ export class TraceExplorerOpenedTracesViewProvider implements vscode.WebviewView
private _disposables: vscode.Disposable[] = [];
private _selectedExperiment: Experiment | undefined;

constructor(
constructor(
private readonly _extensionUri: vscode.Uri,
private readonly _statusService: TraceServerConnectionStatusService,
) {}
Expand Down Expand Up @@ -69,6 +70,7 @@ export class TraceExplorerOpenedTracesViewProvider implements vscode.WebviewView
};

webviewView.webview.html = this._getHtmlForWebview(webviewView.webview);
traceExtensionWebviewManager.fireWebviewCreated(webviewView);

// Handle messages from the webview
webviewView.webview.onDidReceiveMessage(message => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* Licensed under the MIT license. See LICENSE file in the project root for details.
***************************************************************************************/
import * as vscode from 'vscode';
import { traceExtensionWebviewManager } from 'vscode-trace-extension/src/extension';
import { getTraceServerUrl } from 'vscode-trace-extension/src/utils/tspClient';

export class TraceExplorerItemPropertiesProvider implements vscode.WebviewViewProvider {
Expand All @@ -26,6 +27,7 @@ export class TraceExplorerItemPropertiesProvider implements vscode.WebviewViewPr
]
};
webviewView.webview.html = this._getHtmlForWebview(webviewView.webview);
traceExtensionWebviewManager.fireWebviewCreated(webviewView);
}

postMessagetoWebview(_command: string, _data: unknown): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { VSCODE_MESSAGES } from 'vscode-trace-common/lib/messages/vscode-message
import { MarkerSet } from 'tsp-typescript-client/lib/models/markerset';
import JSONBigConfig from 'json-bigint';
import * as fs from 'fs';
import { traceExtensionWebviewManager } from '../extension';

const JSONBig = JSONBigConfig({
useNativeBigInt: true,
Expand Down Expand Up @@ -99,6 +100,10 @@ export class TraceViewerPanel {
TraceViewerPanel.currentPanel?.showMarkersFilter();
}

public static getCurrentExperiment(): Experiment | undefined {
return TraceViewerPanel.currentPanel?._experiment;
}

private static async saveTraceCsv(csvData: string, defaultFileName: string) {
const saveDialogOptions = {
defaultUri: vscode.workspace.workspaceFolders
Expand Down Expand Up @@ -143,6 +148,7 @@ export class TraceViewerPanel {

// Set the webview's initial html content
this._panel.webview.html = this._getHtmlForWebview();
traceExtensionWebviewManager.fireWebviewPanelCreated(this._panel);

// Listen for when the panel is disposed
// This happens when the user closes the panel or when the panel is closed programmatically
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/***************************************************************************************
* Copyright (c) 2023 BlackBerry Limited and others.
*
* Licensed under the MIT license. See LICENSE file in the project root for details.
***************************************************************************************/
import * as vscode from 'vscode';

/**
* Manages webview panels and webviews creation events
*/
export class TraceExtensionWebviewManager {
private webviews: vscode.WebviewView[] = [];
private webviewCreated: vscode.EventEmitter<vscode.WebviewView> = new vscode.EventEmitter();
private webviewPanelCreated: vscode.EventEmitter<vscode.WebviewPanel> = new vscode.EventEmitter();
private isManagerDisposed = false;

getAllActiveWebviews(): vscode.WebviewView[] {
if (!this.isDisposed()) {
return this.webviews;
}
return [];
}

fireWebviewCreated(_webview: vscode.WebviewView): void {
if (!this.isDisposed()) {
this.addWebview(_webview);
this.webviewCreated.fire(_webview);
}
}

fireWebviewPanelCreated(_webviewPanel: vscode.WebviewPanel): void {
if (!this.isDisposed()) {
this.webviewPanelCreated.fire(_webviewPanel);
}
}

onWebviewCreated(listener: (data: vscode.WebviewView) => unknown): void {
if (!this.isDisposed()) {
this.webviewCreated.event(listener);
}
}

onWebviewPanelCreated(listener: (data: vscode.WebviewPanel) => unknown): void {
if (!this.isDisposed()) {
this.webviewPanelCreated.event(listener);
}
}

dispose(): void {
if (!this.isDisposed()) {
this.webviews = [];
this.webviewCreated.dispose();
this.webviewPanelCreated.dispose();
this.isManagerDisposed = true;
}
}

isDisposed(): boolean {
return this.isManagerDisposed;
}

private addWebview(webview: vscode.WebviewView): void {
// Remove it from the array when the webview disposes
webview.onDidDispose(() => {
this.removeWebview(webview);
});
this.webviews.push(webview);
}

private removeWebview(_webview: vscode.WebviewView): void {
this.webviews.filter(webview => webview === _webview).forEach(webview => {
const index = this.webviews.indexOf(webview);
if (index !== -1) {
this.webviews.splice(index, 1);
}
});
}
}