Skip to content
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
4348575
Disable loading scripts from unpkg for widgets
DonJayamanne Apr 2, 2020
cca2194
Oops
DonJayamanne Apr 2, 2020
f9cf82f
Updated
DonJayamanne Apr 2, 2020
dc69573
review fixes
DonJayamanne Apr 2, 2020
21e3191
Backup
DonJayamanne Apr 3, 2020
ebf5ea6
Merge branch 'master' into local2
DonJayamanne Apr 6, 2020
b399d2e
Merge branch 'master' into local2
DonJayamanne Apr 6, 2020
1675a7b
Merge branch 'master' into local2
DonJayamanne Apr 6, 2020
1ac4458
Ooops
DonJayamanne Apr 6, 2020
91f9d2d
Misc
DonJayamanne Apr 7, 2020
1dbf99d
Oops
DonJayamanne Apr 7, 2020
278341a
Merge branch 'master' into local2
DonJayamanne Apr 7, 2020
78564d6
Fixes
DonJayamanne Apr 7, 2020
544e740
misc
DonJayamanne Apr 8, 2020
19751e9
misc
DonJayamanne Apr 8, 2020
3b6861c
Merge branch 'master' into local2
DonJayamanne Apr 8, 2020
1df1e10
Remove
DonJayamanne Apr 8, 2020
11499bb
Fixes
DonJayamanne Apr 8, 2020
953b5c4
Changes
DonJayamanne Apr 8, 2020
4f71587
Merge branch 'master' into local2
DonJayamanne Apr 8, 2020
0f9a413
Fix casing
DonJayamanne Apr 9, 2020
2342dfc
Misc changes
DonJayamanne Apr 9, 2020
4afd7bf
Fixes
DonJayamanne Apr 9, 2020
1cf388e
Merge branch 'local3' into local2
DonJayamanne Apr 9, 2020
2ee0597
Merge branch 'master' into local2
DonJayamanne Apr 9, 2020
ddd1d9d
Oops
DonJayamanne Apr 9, 2020
f785d94
Remove azure ML
DonJayamanne Apr 9, 2020
aaf7407
Oops
DonJayamanne Apr 9, 2020
b0d436f
Rename test
DonJayamanne Apr 9, 2020
7a5f73c
Review fixes
DonJayamanne Apr 9, 2020
c4deb45
More fixes
DonJayamanne Apr 9, 2020
8e5ad7a
More changes
DonJayamanne Apr 9, 2020
3aba931
More changes
DonJayamanne Apr 9, 2020
f1a4c1b
Faster check
DonJayamanne Apr 9, 2020
798e8ee
More changes after discussion
DonJayamanne Apr 9, 2020
bfb7792
Fix tests
DonJayamanne Apr 9, 2020
dd87682
Fix tests
DonJayamanne Apr 9, 2020
ded7fbf
Fix
DonJayamanne Apr 9, 2020
075db59
Restore AML
DonJayamanne Apr 9, 2020
f8061fc
Final changes
DonJayamanne Apr 9, 2020
4a95e6c
Merge branch 'master' into local2
DonJayamanne Apr 9, 2020
3b91f08
Merged
DonJayamanne Apr 9, 2020
51b9803
Oops
DonJayamanne Apr 9, 2020
f948a09
OOps
DonJayamanne Apr 9, 2020
ce92590
Address sonar issues
DonJayamanne Apr 9, 2020
5f370c9
Fix linter
DonJayamanne Apr 9, 2020
92e056e
Fixes
DonJayamanne Apr 10, 2020
ede21dc
Better way to pas array buffer
DonJayamanne Apr 10, 2020
d9f0d16
Added comments
DonJayamanne Apr 10, 2020
b273642
Oops
DonJayamanne Apr 10, 2020
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
8 changes: 0 additions & 8 deletions build/webpack/webpack.datascience-ui.config.builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,6 @@ function buildConfiguration(isNotebook) {
{
from: path.join(constants.ExtensionRootDir, 'node_modules/font-awesome/**/*'),
to: path.join(constants.ExtensionRootDir, 'out', 'datascience-ui', 'common', 'node_modules')
},
{
from: path.join(constants.ExtensionRootDir, 'out/ipywidgets/azureml/azuremlindex.js'),
to: path.join(constants.ExtensionRootDir, 'out', 'datascience-ui', bundleFolder)
},
{
from: path.join(constants.ExtensionRootDir, 'out/ipywidgets/azureml/azuremlregistration.js'),
to: path.join(constants.ExtensionRootDir, 'out', 'datascience-ui', bundleFolder)
}
]
);
Expand Down
20 changes: 16 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1636,10 +1636,22 @@
"description": "Allows a user to import a jupyter notebook into a python file anytime one is opened.",
"scope": "resource"
},
"python.dataScience.loadWidgetScriptsFromThirdPartySource": {
"type": "boolean",
"default": false,
"description": "Enables loading of scripts files for Widgets (ipywidgest, bqplot, beakerx, ipyleaflet, etc) from https://unpkg.com.",
"python.dataScience.widgetScriptSources": {
"type": "array",
"default": [],
"items": {
"type": "string",
"enum": [
"jsdelivr.com",
"unpkg.com"
],
"enumDescriptions": [
"Loads widget (javascript) scripts from https://www.jsdelivr.com/",
"Loads widget (javascript) scripts from https://unpkg.com/"
]
},
"uniqueItems": true,
"markdownDescription": "Defines the location and order of the sources where scripts files for Widgets are downloaded from (e.g. ipywidgest, bqplot, beakerx, ipyleaflet, etc). Not selecting any of these could result in widgets not rendering or function correctly. See [here](http://www.google.com) for more information.",
"scope": "machine"
},
"python.dataScience.gatherRules": {
Expand Down
3 changes: 2 additions & 1 deletion package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -465,5 +465,6 @@
"DataScience.gatherQuality": "Did gather work as desired?",
"DataScience.loadClassFailedWithNoInternet": "Error loading {0}:{1}. Internet connection required for loading 3rd party widgets.",
"DataScience.loadThirdPartyWidgetScriptsDisabled": "Loading of Widgets is disabled by default. Click <a href='https://command:python.datascience.loadWidgetScriptsFromThirdPartySource'>here</a> to enable the setting 'python.dataScience.loadWidgetScriptsFromThirdPartySource'. Once enabled you will need to restart the Kernel",
"DataScience.loadThirdPartyWidgetScriptsPostEnabled": "Please restart the Kernel when changing the setting 'loadWidgetScriptsFromThirdPartySource'."
"DataScience.loadThirdPartyWidgetScriptsPostEnabled": "Please restart the Kernel when changing the setting 'loadWidgetScriptsFromThirdPartySource'.",
"DataScience.useCDNForWidgets": "Widgets require us to download supporting files from a 3rd party website. Click [here](https://aka.ms/widgets) for more information."
}
12 changes: 12 additions & 0 deletions src/client/common/application/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1045,6 +1045,18 @@ export type WebPanelMessage = {
// Wraps the VS Code webview panel
export const IWebPanel = Symbol('IWebPanel');
export interface IWebPanel {
/**
* Convert a uri for the local file system to one that can be used inside webviews.
*
* Webviews cannot directly load resources from the workspace or local file system using `file:` uris. The
* `asWebviewUri` function takes a local `file:` uri and converts it into a uri that can be used inside of
* a webview to load the same resource:
*
* ```ts
* webview.html = `<img src="${webview.asWebviewUri(vscode.Uri.file('/Users/codey/workspace/cat.gif'))}">`
* ```
*/
asWebviewUri(localResource: Uri): Uri;
setTitle(val: string): void;
/**
* Makes the webpanel show up.
Expand Down
8 changes: 7 additions & 1 deletion src/client/common/application/webPanels/webPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ export class WebPanel implements IWebPanel {
this.panel.dispose();
}
}
public asWebviewUri(localResource: Uri) {
if (!this.panel) {
throw new Error('WebView not initialized, too early to get a Uri');
}
return this.panel?.webview.asWebviewUri(localResource);
}

public isVisible(): boolean {
return this.panel ? this.panel.visible : false;
Expand Down Expand Up @@ -161,7 +167,7 @@ export class WebPanel implements IWebPanel {
<meta name="theme" content="${Identifiers.GeneratedThemeName}"/>
<title>VS Code Python React UI</title>
<base href="${uriBase}${uriBase.endsWith('/') ? '' : '/'}"/>
<link rel="stylesheet" href="${rootPath}/../common/node_modules/font-awesome/css/font-awesome.min.css">
<link rel="stylesheet" href="${rootPath}/../common/node_modules/font-awesome/css/font-awesome.min.css">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
Expand Down
4 changes: 2 additions & 2 deletions src/client/common/net/httpClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export class HttpClient implements IHttpClient {
}

public async getJSON<T>(uri: string, strict: boolean = true): Promise<T> {
const body = await this.getBody(uri);
const body = await this.getContents(uri);
return this.parseBodyToJSON(body, strict);
}

Expand All @@ -44,7 +44,7 @@ export class HttpClient implements IHttpClient {
}
}

public async getBody(uri: string): Promise<string> {
public async getContents(uri: string): Promise<string> {
// tslint:disable-next-line:no-require-imports
const request = require('request') as typeof requestTypes;
return new Promise<string>((resolve, reject) => {
Expand Down
7 changes: 7 additions & 0 deletions src/client/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,8 +394,11 @@ export interface IDataScienceSettings {
disableJupyterAutoStart?: boolean;
jupyterCommandLineArguments: string[];
loadWidgetScriptsFromThirdPartySource?: boolean;
widgetScriptSources: WidgetCDNs[];
}

export type WidgetCDNs = 'unpkg.com' | 'jsdelivr.com';

export const IConfigurationService = Symbol('IConfigurationService');
export interface IConfigurationService {
getSettings(resource?: Uri): IPythonSettings;
Expand Down Expand Up @@ -466,6 +469,10 @@ export interface IHttpClient {
* @param strict Set `false` to allow trailing comma and comments in the JSON, defaults to `true`
*/
getJSON<T>(uri: string, strict?: boolean): Promise<T>;
/**
* Returns the contents of a remote resource.
*/
getContents(uri: string): Promise<string>;
}

export const IExtensionContext = Symbol('ExtensionContext');
Expand Down
5 changes: 5 additions & 0 deletions src/client/common/utils/localize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export namespace Common {
export const bannerLabelNo = localize('Common.bannerLabelNo', 'No');
export const canceled = localize('Common.canceled', 'Canceled');
export const cancel = localize('Common.cancel', 'Cancel');
export const ok = localize('Common.ok', 'Ok');
export const gotIt = localize('Common.gotIt', 'Got it!');
export const install = localize('Common.install', 'Install');
export const loadingExtension = localize('Common.loadingPythonExtension', 'Python extension loading...');
Expand Down Expand Up @@ -840,6 +841,10 @@ export namespace DataScience {
'DataScience.loadThirdPartyWidgetScriptsPostEnabled',
"Once you have updated the setting 'loadWidgetScriptsFromThirdPartySource' you will need to restart the Kernel."
);
export const useCDNForWidgets = localize(
'DataScience.useCDNForWidgets',
'Widgets require us to download supporting files from a 3rd party website. Click [here](https://aka.ms/widgets) for more information.'
);
}

export namespace DebugConfigStrings {
Expand Down
10 changes: 9 additions & 1 deletion src/client/datascience/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,15 @@ export enum Telemetry {
GatherQualityReport = 'DS_INTERNAL.GATHER_QUALITY_REPORT',
ZMQNotSupported = 'DATASCIENCE.ZMQ_NATIVE_BINARIES_NOT_LOADING',
IPyWidgetLoadFailure = 'DS_INTERNAL.IPYWIDGET_LOAD_FAILURE',
IPyWidgetLoadDisabled = 'DS_INTERNAL.IPYWIDGET_LOAD_DISABLED'
IPyWidgetLoadDisabled = 'DS_INTERNAL.IPYWIDGET_LOAD_DISABLED',
HashedIPyWidgetNameUsed = 'DS_INTERNAL.IPYWIDGET_USED_BY_USER',
Comment thread
DonJayamanne marked this conversation as resolved.
Resolved
HashedIPyWidgetNameDiscovered = 'DS_INTERNAL.IPYWIDGET_DISCOVERED',
HashedIPyWidgetScriptDiscoveryError = 'DS_INTERNAL.IPYWIDGET_DISCOVERY_ERRORED',
DiscoverIPyWidgetNamesLocalPerf = 'DS_INTERNAL.IPYWIDGET_TEST_AVAILABILITY_ON_LOCAL',
// DiscoverIPyWidgetNamesRemotePerf = 'DS_INTERNAL.IPYWIDGET_TEST_AVAILABILITY_ON_REMOTE',
DiscoverIPyWidgetNamesCDNPerf = 'DS_INTERNAL.IPYWIDGET_TEST_AVAILABILITY_ON_CDN',
IPyWidgetPromptToUseCDN = 'DS_INTERNAL.IPYWIDGET_PROMPT_TO_USE_CDN',
IPyWidgetPromptToUseCDNSelection = 'DS_INTERNAL.IPYWIDGET_PROMPT_TO_USE_CDN_SELECTION'
}

export enum NativeKeyboardCommandTelemetry {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
LoadIPyWidgetClassDisabledAction
} from '../../../datascience-ui/interactive-common/redux/reducers/types';
import { PythonInterpreter } from '../../interpreter/contracts';
import { WidgetScriptSource } from '../ipywidgets/types';
import { LiveKernelModel } from '../jupyter/kernels/types';
import { CssMessages, IGetCssRequest, IGetCssResponse, IGetMonacoThemeRequest, SharedMessages } from '../messages';
import { IGetMonacoThemeResponse } from '../monacoMessages';
Expand Down Expand Up @@ -115,6 +116,18 @@ export enum InteractiveWindowMessages {
export enum IPyWidgetMessages {
IPyWidgets_Ready = 'IPyWidgets_Ready',
IPyWidgets_onRestartKernel = 'IPyWidgets_onRestartKernel',
IPyWidgets_onKernelChanged = 'IPyWidgets_onKernelChanged',
IPyWidgets_updateRequireConfig = 'IPyWidgets_updateRequireConfig',
/**
* UI sends a request to extension to determine whether we have the source for any of the widgets.
*/
IPyWidgets_WidgetScriptSourceRequest = 'IPyWidgets_WidgetScriptSourceRequest',
/**
* Extension sends response to the request with yes/no.
*/
IPyWidgets_WidgetScriptSourceResponse = 'IPyWidgets_WidgetScriptSourceResponse',
IPyWidgets_AllWidgetScriptSourcesRequest = 'IPyWidgets_AllWidgetScriptSourcesRequest',
IPyWidgets_AllWidgetScriptSourcesResponse = 'IPyWidgets_AllWidgetScriptSourcesResponse',
IPyWidgets_msg = 'IPyWidgets_msg',
IPyWidgets_binary_msg = 'IPyWidgets_binary_msg',
IPyWidgets_msg_handled = 'IPyWidgets_msg_handled',
Expand Down Expand Up @@ -474,8 +487,13 @@ export type NotebookModelChange =
// Map all messages to specific payloads
export class IInteractiveWindowMapping {
public [IPyWidgetMessages.IPyWidgets_kernelOptions]: KernelSocketOptions;
public [IPyWidgetMessages.IPyWidgets_AllWidgetScriptSourcesRequest]: undefined | never;
public [IPyWidgetMessages.IPyWidgets_AllWidgetScriptSourcesResponse]: WidgetScriptSource[];
public [IPyWidgetMessages.IPyWidgets_WidgetScriptSourceRequest]: { moduleName: string; moduleVersion: string };
public [IPyWidgetMessages.IPyWidgets_WidgetScriptSourceResponse]: WidgetScriptSource;
public [IPyWidgetMessages.IPyWidgets_Ready]: never | undefined;
public [IPyWidgetMessages.IPyWidgets_onRestartKernel]: never | undefined;
public [IPyWidgetMessages.IPyWidgets_onKernelChanged]: never | undefined;
public [IPyWidgetMessages.IPyWidgets_registerCommTarget]: string;
// tslint:disable-next-line: no-any
public [IPyWidgetMessages.IPyWidgets_binary_msg]: { id: string; data: any };
Expand Down
9 changes: 7 additions & 2 deletions src/client/datascience/interactive-common/synchronization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,14 @@ const messageWithMessageTypes: MessageMapping<IInteractiveWindowMapping> & Messa
[SharedMessages.Started]: MessageType.other,
[SharedMessages.UpdateSettings]: MessageType.other,
// IpyWidgets
[IPyWidgetMessages.IPyWidgets_kernelOptions]: MessageType.noIdea,
[IPyWidgetMessages.IPyWidgets_kernelOptions]: MessageType.syncAcrossSameNotebooks,
[IPyWidgetMessages.IPyWidgets_Ready]: MessageType.noIdea,
[IPyWidgetMessages.IPyWidgets_onRestartKernel]: MessageType.noIdea,
[IPyWidgetMessages.IPyWidgets_AllWidgetScriptSourcesRequest]: MessageType.noIdea,
[IPyWidgetMessages.IPyWidgets_AllWidgetScriptSourcesResponse]: MessageType.syncAcrossSameNotebooks,
[IPyWidgetMessages.IPyWidgets_WidgetScriptSourceRequest]: MessageType.noIdea,
[IPyWidgetMessages.IPyWidgets_WidgetScriptSourceResponse]: MessageType.syncAcrossSameNotebooks,
[IPyWidgetMessages.IPyWidgets_onKernelChanged]: MessageType.syncAcrossSameNotebooks,
[IPyWidgetMessages.IPyWidgets_onRestartKernel]: MessageType.syncAcrossSameNotebooks,
[IPyWidgetMessages.IPyWidgets_msg]: MessageType.noIdea,
[IPyWidgetMessages.IPyWidgets_binary_msg]: MessageType.noIdea,
[IPyWidgetMessages.IPyWidgets_msg_handled]: MessageType.noIdea,
Expand Down
1 change: 0 additions & 1 deletion src/client/datascience/interactive-ipynb/nativeEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,6 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor {
nativeEditorDir,
[
path.join(nativeEditorDir, 'require.js'),
path.join(nativeEditorDir, 'azuremlregistration.js'),
path.join(nativeEditorDir, 'ipywidgets.js'),
path.join(nativeEditorDir, 'monaco.bundle.js'),
path.join(nativeEditorDir, 'commons.initial.bundle.js'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,6 @@ export class InteractiveWindow extends InteractiveBase implements IInteractiveWi
historyReactDir,
[
path.join(historyReactDir, 'require.js'),
path.join(historyReactDir, 'azuremlregistration.js'),
path.join(historyReactDir, 'ipywidgets.js'),
path.join(historyReactDir, 'monaco.bundle.js'),
path.join(historyReactDir, 'commons.initial.bundle.js'),
Expand Down
96 changes: 96 additions & 0 deletions src/client/datascience/ipywidgets/cdnWidgetScriptSourceProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

'use strict';

import { IConfigurationService, IHttpClient, WidgetCDNs } from '../../common/types';
import { StopWatch } from '../../common/utils/stopWatch';
import { sendTelemetryEvent } from '../../telemetry';
import { Telemetry } from '../constants';
import { IWidgetScriptSourceProvider, WidgetScriptSource } from './types';

// Source borrowed from https://github.com/jupyter-widgets/ipywidgets/blob/54941b7a4b54036d089652d91b39f937bde6b6cd/packages/html-manager/src/libembed-amd.ts#L33
Comment thread
DonJayamanne marked this conversation as resolved.
Resolved
const unpgkUrl = 'https://unpkg.com/';
const jsdelivrUrl = 'https://cdn.jsdelivr.net/npm/';
function moduleNameToCDNUrl(cdn: string, moduleName: string, moduleVersion: string) {
let packageName = moduleName;
let fileName = 'index'; // default filename
// if a '/' is present, like 'foo/bar', packageName is changed to 'foo', and path to 'bar'
// We first find the first '/'
let index = moduleName.indexOf('/');
if (index !== -1 && moduleName[0] === '@') {
// if we have a namespace, it's a different story
// @foo/bar/baz should translate to @foo/bar and baz
// so we find the 2nd '/'
index = moduleName.indexOf('/', index + 1);
}
if (index !== -1) {
fileName = moduleName.substr(index + 1);
packageName = moduleName.substr(0, index);
}
return `${cdn}${packageName}@${moduleVersion}/dist/${fileName}`;
}

function getCDNPrefix(cdn?: WidgetCDNs): string | undefined {
switch (cdn) {
case 'unpkg.com':
return unpgkUrl;
case 'jsdelivr.com':
return jsdelivrUrl;
default:
break;
}
}
/**
* Widget scripts are found in CDN.
* Given an widget module name & version, this will attempt to find the Url on a CDN.
* We'll need to stick to the order of preference prescribed by the user.
*/
export class CDNWidgetScriptSourceProvider implements IWidgetScriptSourceProvider {
private get cdnProviders(): readonly WidgetCDNs[] {
const settings = this.configurationSettings.getSettings(undefined);
return settings.datascience.widgetScriptSources;
}
public static validUrls = new Map<string, boolean>();
constructor(
private readonly configurationSettings: IConfigurationService,
private readonly httpClient: IHttpClient
) {}
public dispose() {
// Noop.
}
public async getWidgetScriptSource(moduleName: string, moduleVersion: string): Promise<WidgetScriptSource> {
const cdns = [...this.cdnProviders];
while (cdns.length) {
const cdn = cdns.shift();
const cdnBaseUrl = getCDNPrefix(cdn);
if (!cdnBaseUrl || !cdn) {
continue;
}
const scriptUri = moduleNameToCDNUrl(cdnBaseUrl, moduleName, moduleVersion);
const exists = await this.getUrlForWidget(cdn, scriptUri);
if (exists) {
return { moduleName, scriptUri, source: 'cdn' };
}
}
return { moduleName };
}
public async getWidgetScriptSources(_ignoreCache?: boolean): Promise<Readonly<WidgetScriptSource[]>> {
return [];
Comment thread
DonJayamanne marked this conversation as resolved.
Resolved
}
private async getUrlForWidget(cdn: string, url: string): Promise<boolean> {
if (CDNWidgetScriptSourceProvider.validUrls.has(url)) {
return CDNWidgetScriptSourceProvider.validUrls.get(url)!;
}

const stopWatch = new StopWatch();
const exists = await this.httpClient
.getContents(url)
.then(() => true)
.catch(() => false);
sendTelemetryEvent(Telemetry.DiscoverIPyWidgetNamesCDNPerf, stopWatch.elapsedTime, { cdn, exists });

CDNWidgetScriptSourceProvider.validUrls.set(url, exists);
return exists;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ class IPyWidgetMessageDispatcherWithOldMessages implements IIPyWidgetMessageDisp
*/
@injectable()
export class IPyWidgetMessageDispatcherFactory implements IDisposable {
private readonly ipywidgetMulticasters = new Map<string, IPyWidgetMessageDispatcher>();
private readonly messageDispatchers = new Map<string, IPyWidgetMessageDispatcher>();
private readonly messages: IPyWidgetMessage[] = [];
private disposed = false;
private disposables: IDisposable[] = [];
Expand All @@ -98,17 +98,17 @@ export class IPyWidgetMessageDispatcherFactory implements IDisposable {
}
}
public create(identity: Uri): IIPyWidgetMessageDispatcher {
let baseDispatcher = this.ipywidgetMulticasters.get(identity.fsPath);
let baseDispatcher = this.messageDispatchers.get(identity.fsPath);
if (!baseDispatcher) {
baseDispatcher = new IPyWidgetMessageDispatcher(this.notebookProvider, identity);
this.ipywidgetMulticasters.set(identity.fsPath, baseDispatcher);
this.messageDispatchers.set(identity.fsPath, baseDispatcher);

// Capture all messages so we can re-play messages that others missed.
this.disposables.push(baseDispatcher.postMessage(this.onMessage, this));
}

// If we have messages upto this point, then capture those messages,
// & pass to the multicaster so it can re-broadcast those old messages.
// & pass to the dispatcher so it can re-broadcast those old messages.
// If there are no old messages, even then return a new instance of the class.
// This way, the reference to that will be controlled by calling code.
const dispatcher = new IPyWidgetMessageDispatcherWithOldMessages(
Expand All @@ -124,9 +124,9 @@ export class IPyWidgetMessageDispatcherFactory implements IDisposable {
}
notebook.onDisposed(
() => {
const item = this.ipywidgetMulticasters.get(notebook.identity.fsPath);
const item = this.messageDispatchers.get(notebook.identity.fsPath);
item?.dispose(); // NOSONAR
this.ipywidgetMulticasters.delete(notebook.identity.fsPath);
this.messageDispatchers.delete(notebook.identity.fsPath);
},
this,
this.disposables
Expand Down
Loading