@@ -402,6 +402,51 @@ const notebookToolsWidget: JupyterFrontEndPlugin<void> = {
402402} ,
403403} ;
404404
405+ /**
406+ * A plugin to update the tab icon based on the kernel status.
407+ */
408+ const tabIcon :JupyterFrontEndPlugin < void > = {
409+ id :'@jupyter-notebook/notebook-extension:tab-icon' ,
410+ autoStart :true ,
411+ requires :[ INotebookTracker ] ,
412+ activate :( app :JupyterFrontEnd , tracker :INotebookTracker ) => {
413+ // the favicons are provided by Jupyter Server
414+ const notebookIcon = ' /static/favicons/favicon-notebook.ico' ;
415+ const busyIcon = ' /static/favicons/favicon-busy-1.ico' ;
416+
417+ const updateBrowserFavicon = (
418+ status :ISessionContext . KernelDisplayStatus
419+ ) => {
420+ const link = document . querySelector (
421+ "link[rel*='icon']"
422+ ) as HTMLLinkElement ;
423+ switch ( status ) {
424+ case 'busy' :
425+ link . href = busyIcon ;
426+ break ;
427+ case 'idle' :
428+ link . href = notebookIcon ;
429+ break ;
430+ }
431+ } ;
432+
433+ const onChange = async ( ) => {
434+ const current = tracker . currentWidget ;
435+ const sessionContext = current ?. sessionContext ;
436+ if ( ! sessionContext ) {
437+ return ;
438+ }
439+
440+ sessionContext . statusChanged . connect ( ( ) => {
441+ const status = sessionContext . kernelDisplayStatus ;
442+ updateBrowserFavicon ( status ) ;
443+ } ) ;
444+ } ;
445+
446+ tracker . currentChanged . connect ( onChange ) ;
447+ } ,
448+ } ;
449+
405450/**
406451 * A plugin that adds a Trusted indicator to the menu area
407452 */
@@ -441,8 +486,9 @@ const plugins: JupyterFrontEndPlugin<any>[] = [
441486closeTab ,
442487kernelLogo ,
443488kernelStatus ,
444- scrollOutput ,
445489notebookToolsWidget ,
490+ scrollOutput ,
491+ tabIcon ,
446492trusted ,
447493] ;
448494