Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
525e3ef
Per-kernel widget manager.
May 27, 2024
02b37f6
Add attachToRendermime function.
May 29, 2024
245c9b2
Update lock file.
May 29, 2024
bc5a447
WidgetModel: set comm_live to false when comm closed and remove redun…
May 31, 2024
d735e58
Improve ManagerBase_loadFromKernel
May 31, 2024
4f21fb7
Update jupyterlab_widgets package.json
May 31, 2024
55e89c3
Improved restoring widgets when opening and closing of notebooks.
May 31, 2024
cbf6c40
manager and render code refactoring.
Jun 1, 2024
390b25b
Avoid generating a warning "Failed to fetch ipywidgets through the "j…
Jun 1, 2024
2b83783
WidgetModel.close - catch any error when closing comm to ensure closi…
Jun 2, 2024
3b5f4d7
Improved manager logic and re-rendering when the kernel is re-connected.
Jun 2, 2024
23667a9
Added delays for getWidgetManager and get_model if the model isn't im…
Jun 2, 2024
09a1020
Manager, WidgetManager and plugin refactoring (simplification) . Warn…
Jun 3, 2024
fdf6994
Change packaging workflow to jupyterlab~4.0.
Jun 4, 2024
0b3340f
WidgetManager - do nothing if rendermime is the global rendermime ins…
Jun 9, 2024
dded2e3
Fix not awaiting delay promise in get_model.
Jun 9, 2024
4ef40e4
Update yarn.lock
Aug 31, 2024
0b1a7f1
Add kernel monitoring.
Sep 8, 2024
70c3125
Changed getWidgetManager to be a static method of KernelWidgetManage…
Sep 25, 2024
1040d27
Tweak restoration
Nov 2, 2024
1e8256a
Improved KernelWidgetManager creation and kernel restoration.
Nov 3, 2024
af38eca
Create new comm for existing models when restoring from kernel.
Nov 8, 2024
dfedfa6
Remove istanbul-instrumenter-loaderjl
Oct 15, 2025
1af0cff
Use jupyterlab/mathjax-extension for typesetting.
Oct 17, 2025
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
2 changes: 1 addition & 1 deletion .github/workflows/packaging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ jobs:
- name: Check the JupyterLab extension is installed
if: matrix.dist != 'widgetsnbextension*.tar.gz'
run: |
python -m pip install jupyterlab~=3.0
python -m pip install jupyterlab~=4.0

jupyter labextension list
jupyter labextension list 2>&1 | grep -ie "@jupyter-widgets/jupyterlab-manager.*enabled.*ok" -
Expand Down
5 changes: 4 additions & 1 deletion examples/web1/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ module.exports = {
path: path.resolve(__dirname, 'built'),
},
module: {
rules: [{ test: /\.css$/i, use: ['style-loader', 'css-loader'] }],
rules: [
{ test: /\.css$/i, use: ['style-loader', 'css-loader'] },
{ test: /\.svg$/, type: 'asset/resource' },
],
},
};
1 change: 0 additions & 1 deletion packages/base-manager/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@
"chai": "^4.0.0",
"chai-as-promised": "^7.0.0",
"expect.js": "^0.3.1",
"istanbul-instrumenter-loader": "^3.0.1",
"karma": "^6.3.3",
"karma-chrome-launcher": "^3.1.0",
"karma-coverage": "^2.0.3",
Expand Down
38 changes: 29 additions & 9 deletions packages/base-manager/src/manager-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,10 +214,14 @@ export abstract class ManagerBase implements IWidgetManager {
*
* If you would like to synchronously test if a model exists, use .has_model().
*/
async get_model(model_id: string): Promise<WidgetModel> {
async get_model(model_id: string, delays = [500]): Promise<WidgetModel> {
let i = 0;
while (!this._models[model_id] && i < delays.length) {
await new Promise((resolve) => setTimeout(resolve, delays[i++]));
}
const modelPromise = this._models[model_id];
if (modelPromise === undefined) {
throw new Error('widget model not found');
throw new Error(`widget model '${model_id}' not found`);
}
return modelPromise;
}
Expand Down Expand Up @@ -383,15 +387,21 @@ export abstract class ManagerBase implements IWidgetManager {
// Try fetching all widget states through the control comm
let data: any;
let buffers: any;
let timeoutID: number | undefined;
const comm_ids = await this._get_comm_info();
if (Object.keys(comm_ids).length === 0) {
// There is nothing to load, either a new kernel or no widgets in the kernel.
return Promise.resolve();
}
try {
const initComm = await this._create_comm(
CONTROL_COMM_TARGET,
uuid(),
{},
{ version: CONTROL_COMM_PROTOCOL_VERSION }
);

await new Promise((resolve, reject) => {
let succeeded = false;
initComm.on_msg((msg: any) => {
data = msg['content']['data'];

Expand All @@ -409,24 +419,31 @@ export abstract class ManagerBase implements IWidgetManager {
return new DataView(b instanceof ArrayBuffer ? b : b.buffer);
}
});

succeeded = true;
clearTimeout(timeoutID);
resolve(null);
});

initComm.on_close(() => reject('Control comm was closed too early'));
initComm.on_close(() => {
if (!succeeded) reject('Control comm was closed too early');
});

// Send a states request msg
initComm.send({ method: 'request_states' }, {});

// Reject if we didn't get a response in time
setTimeout(
timeoutID = window.setTimeout(
() => reject('Control comm did not respond in time'),
CONTROL_COMM_TIMEOUT
);
});

initComm.close();
} catch (error) {
console.warn(
'Failed to fetch ipywidgets through the "jupyter.widget.control" comm channel, fallback to fetching individual model state. Reason:',
error
);
clearTimeout(timeoutID);
// Fall back to the old implementation for old ipywidgets backend versions (ipywidgets<=7.6)
return this._loadFromKernelModels();
}
Expand Down Expand Up @@ -487,6 +504,9 @@ export abstract class ManagerBase implements IWidgetManager {
model.constructor as typeof WidgetModel
)._deserialize_state(state.state, this);
model!.set_state(deserializedState);
if (!model.comm_live) {
model.comm = await this._create_comm('jupyter.widget', widget_id);
}
}
} catch (error) {
// Failed to create a widget model, we continue creating other models so that
Expand Down Expand Up @@ -738,12 +758,12 @@ export abstract class ManagerBase implements IWidgetManager {

/**
* Disconnect the widget manager from the kernel, setting each model's comm
* as dead.
* as undefined.
*/
disconnect(): void {
Object.keys(this._models).forEach((i) => {
this._models[i].then((model) => {
model.comm_live = false;
model.comm = undefined;
});
});
}
Expand Down
1 change: 0 additions & 1 deletion packages/base/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@
"chai": "^4.0.0",
"chai-as-promised": "^7.0.0",
"expect.js": "^0.3.1",
"istanbul-instrumenter-loader": "^3.0.1",
"karma": "^6.3.3",
"karma-chrome-launcher": "^3.1.0",
"karma-coverage": "^2.0.3",
Expand Down
4 changes: 3 additions & 1 deletion packages/base/src/errorwidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ export function createErrorWidgetModel(
error: error,
};
super(attributes, options);
this.comm_live = true;
}
get comm_live(): boolean {
return true;
}
}
return ErrorWidget;
Expand Down
63 changes: 32 additions & 31 deletions packages/base/src/widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ export class WidgetModel extends Backbone.Model {
// Attributes should be initialized here, since user initialization may depend on it
this.widget_manager = options.widget_manager;
this.model_id = options.model_id;
const comm = options.comm;
this.comm = options.comm;

this.views = Object.create(null);
this.state_change = Promise.resolve();
Expand All @@ -174,27 +174,23 @@ export class WidgetModel extends Backbone.Model {
// _buffered_state_diff must be created *after* the super.initialize
// call above. See the note in the set() method below.
this._buffered_state_diff = {};
}

if (comm) {
// Remember comm associated with the model.
this.comm = comm;
get comm() {
return this._comm;
}

// Hook comm messages up to model.
set comm(comm: IClassicComm | undefined) {
this._comm = comm;
if (comm) {
comm.on_close(this._handle_comm_closed.bind(this));
comm.on_msg(this._handle_comm_msg.bind(this));

this.comm_live = true;
} else {
this.comm_live = false;
}
this.trigger('comm_live_update');
}

get comm_live(): boolean {
return this._comm_live;
}
set comm_live(x) {
this._comm_live = x;
this.trigger('comm_live_update');
get comm_live() {
return Boolean(this.comm);
}

/**
Expand All @@ -218,39 +214,46 @@ export class WidgetModel extends Backbone.Model {
*
* @returns - a promise that is fulfilled when all the associated views have been removed.
*/
close(comm_closed = false): Promise<void> {
async close(comm_closed = false): Promise<void> {
// can only be closed once.
if (this._closed) {
return Promise.resolve();
return;
}
this._closed = true;
if (this.comm && !comm_closed) {
this.comm.close();
if (this._comm && !comm_closed) {
try {
this._comm.close();
} catch (err) {
// Do Nothing
}
}
this.stopListening();
this.trigger('destroy', this);
if (this.comm) {
delete this.comm;
}
delete this._comm;

// Delete all views of this model
if (this.views) {
const views = Object.keys(this.views).map((id: string) => {
return this.views![id].then((view) => view.remove());
});
delete this.views;
return Promise.all(views).then(() => {
return;
});
await Promise.all(views);
return;
}
return Promise.resolve();
}

/**
* Handle when a widget comm is closed.
*/
_handle_comm_closed(msg: KernelMessage.ICommCloseMsg): void {
if (!this.comm) {
return;
}
this.comm = undefined;
this.trigger('comm:close');
this.close(true);
if (!this._closed) {
this.close(true);
}
}

/**
Expand Down Expand Up @@ -635,7 +638,7 @@ export class WidgetModel extends Backbone.Model {
* This invokes a Backbone.Sync.
*/
save_changes(callbacks?: {}): void {
if (this.comm_live) {
if (this.comm) {
const options: any = { patch: true };
if (callbacks) {
options.callbacks = callbacks;
Expand Down Expand Up @@ -721,11 +724,9 @@ export class WidgetModel extends Backbone.Model {
model_id: string;
views?: { [key: string]: Promise<WidgetView> };
state_change: Promise<any>;
comm?: IClassicComm;
name: string;
module: string;

private _comm_live: boolean;
private _comm?: IClassicComm;
private _closed: boolean;
private _state_lock: any;
private _buffered_state_diff: any;
Expand Down
14 changes: 7 additions & 7 deletions packages/base/test/src/widget_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,15 +194,15 @@ describe('WidgetModel', function () {
});
});

it('sets the widget_manager, id, comm, and comm_live properties', function () {
const widgetDead = new WidgetModel({}, {
model_id: 'widgetDead',
it('sets the widget_manager, id, comm, properties', function () {
const widgetNoComm = new WidgetModel({}, {
model_id: 'noComm',
widget_manager: this.manager,
} as IBackboneModelOptions);
expect(widgetDead.model_id).to.equal('widgetDead');
expect(widgetDead.widget_manager).to.equal(this.manager);
expect(widgetDead.comm).to.be.undefined;
expect(widgetDead.comm_live).to.be.false;
expect(widgetNoComm.model_id).to.equal('noComm');
expect(widgetNoComm.widget_manager).to.equal(this.manager);
expect(widgetNoComm.comm).to.be.undefined;
expect(widgetNoComm.comm_live).to.be.false;

const comm = new MockComm();
const widgetLive = new WidgetModel({}, {
Expand Down
3 changes: 1 addition & 2 deletions packages/controls/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
},
"dependencies": {
"@jupyter-widgets/base": "^6.0.11",
"@jupyterlab/mathjax-extension": "^4.4.9",
"@lumino/algorithm": "^1 || ^2",
"@lumino/domutils": "^1 || ^2",
"@lumino/messaging": "^1 || ^2",
Expand All @@ -53,13 +54,11 @@
"@types/d3-format": "^3.0.1",
"@types/expect.js": "^0.3.29",
"@types/jquery": "^3.5.16",
"@types/mathjax": "^0.0.37",
"@types/mocha": "^9.0.0",
"@types/node": "^17.0.2",
"chai": "^4.0.0",
"css-loader": "^6.5.1",
"expect.js": "^0.3.1",
"istanbul-instrumenter-loader": "^3.0.1",
"karma": "^6.3.3",
"karma-chrome-launcher": "^3.1.0",
"karma-coverage": "^2.0.3",
Expand Down
11 changes: 4 additions & 7 deletions packages/controls/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

import { MathJaxTypesetter } from '@jupyterlab/mathjax-extension';
export { uuid, resolvePromisesDict } from '@jupyter-widgets/base';

const typesetter = new MathJaxTypesetter();
/**
* Apply MathJax rendering to an element, and optionally set its text.
*
* If MathJax is not available, make no changes.
* Apply MathJax rendering to an element, if `text` is provided it will replace element.textContent.
*
* Parameters
* ----------
Expand All @@ -17,9 +16,7 @@ export function typeset(element: HTMLElement, text?: string): void {
if (text !== void 0) {
element.textContent = text;
}
if ((window as any).MathJax !== void 0) {
MathJax!.Hub!.Queue(['Typeset', MathJax.Hub, element]);
}
typesetter.typeset(element);
}

/**
Expand Down
13 changes: 1 addition & 12 deletions packages/controls/src/widget_description.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,18 +66,7 @@ export class DescriptionView extends DOMWidgetView {
}

typeset(element: HTMLElement, text?: string): void {
this.displayed.then(() => {
const widget_manager: any = this.model.widget_manager;
const latexTypesetter = widget_manager._rendermime?.latexTypesetter;
if (latexTypesetter) {
if (text !== void 0) {
element.textContent = text;
}
latexTypesetter.typeset(element);
} else {
return typeset(element, text);
}
});
this.displayed.then(() => typeset(element, text));
}

updateDescription(): void {
Expand Down
1 change: 1 addition & 0 deletions packages/controls/test/webpack.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ module.exports = {
generator: { filename: '[name].[ext]' },
},
{ test: /\.ipynb$/, type: 'json' },
{ test: /\.svg$/, type: 'asset/resource' },
],
},
mode: 'development',
Expand Down
2 changes: 1 addition & 1 deletion packages/controls/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"compilerOptions": {
"outDir": "lib",
"rootDir": "src",
"types": ["mathjax", "node"]
"types": ["node"]
},
"include": ["src/**/*"],
"references": [
Expand Down
Loading