Skip to content

Commit cdd72e2

Browse files
committed
[jupyter] use display.Javascript to embed custom JS code
In previous implementation only display.HTML with embed <script></script> was used. But dependeing from security settings jupyter or web browser blocks loading of such <script> tag directly. Therefore split cell output to `display.HTML()` and `display.Javascript()`. This clearly indicates that JS code wants to be embed and therefore security settings should not block output. Can help to resolve issue in https://root-forum.cern.ch/t/64882/
1 parent 91c191b commit cdd72e2

1 file changed

Lines changed: 100 additions & 101 deletions

File tree

  • bindings/pyroot/pythonizations/python/ROOT/_jupyroot/helpers

bindings/pyroot/pythonizations/python/ROOT/_jupyroot/helpers/utils.py

Lines changed: 100 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -45,80 +45,64 @@
4545

4646
_visualObjects = []
4747

48-
_jsMagicHighlight = """
49-
Jupyter.CodeCell.options_default.highlight_modes['magic_{cppMIME}'] = {{'reg':[/^%%cpp/]}};
50-
console.log("JupyROOT - %%cpp magic configured");
51-
"""
5248

5349
_jsNotDrawableClassesPatterns = ["TEve*"]
5450

5551
_jsCanvasWidth = 800
5652
_jsCanvasHeight = 600
5753

58-
_jsFixedSizeDiv = """
59-
<div id="{jsDivId}" style="width: {jsCanvasWidth}px; height: {jsCanvasHeight}px; position: relative">
60-
</div>
61-
"""
6254

63-
_jsFullWidthDiv = """
64-
<div style="width: 100%; height: {jsCanvasHeight}px; position: relative">
55+
_jsFixedSizeDiv = """<div id="{jsDivId}" style="width: {jsCanvasWidth}px; height: {jsCanvasHeight}px; position: relative">
56+
</div>"""
57+
58+
59+
_jsFullWidthDiv = """<div style="width: 100%; height: {jsCanvasHeight}px; position: relative">
6560
<div id="{jsDivId}">
6661
</div>
67-
</div>
68-
"""
62+
</div>"""
6963

70-
_jsDrawJsonCode = """
71-
Core.unzipJSON({jsonLength},'{jsonZip}').then(json => {{
64+
65+
_jsDrawJsonCode = """Core.unzipJSON({jsonLength},'{jsonZip}').then(json => {{
7266
const obj = Core.parse(json);
7367
Core.draw('{jsDivId}', obj, '{jsDrawOptions}');
74-
}});
75-
"""
68+
}});"""
69+
7670

77-
_jsBrowseUrlCode = """
78-
Core.buildGUI('{jsDivId}','notebook').then(h => h.openRootFile('{fileUrl}'));
79-
"""
71+
_jsBrowseUrlCode = """Core.buildGUI('{jsDivId}','notebook').then(h => h.openRootFile('{fileUrl}'));"""
8072

8173

82-
_jsBrowseFileCode = """
83-
const binaryString = atob('{fileBase64}');
74+
_jsBrowseFileCode = """const binaryString = atob('{fileBase64}');
8475
const bytes = new Uint8Array(binaryString.length);
8576
for (let i = 0; i < binaryString.length; i++)
8677
bytes[i] = binaryString.charCodeAt(i);
87-
Core.buildGUI('{jsDivId}','notebook').then(h => h.openRootFile(bytes.buffer));
88-
"""
89-
90-
_jsCode = """
91-
{jsDivHtml}
92-
<script>
93-
function process_{jsDivId}() {{
94-
function execCode(Core) {{
95-
Core.settings.HandleKeys = false;
96-
{jsDrawCode}
97-
}}
98-
const servers = ['/static/', 'https://root.cern/js/7.11.0/', 'https://jsroot.gsi.de/7.11.0/'],
99-
path = 'build/jsroot';
100-
if (typeof JSROOT !== 'undefined')
101-
execCode(JSROOT);
102-
else if (typeof requirejs !== 'undefined') {{
103-
servers.forEach((s,i) => {{ servers[i] = s + path; }});
104-
requirejs.config({{ paths: {{ 'jsroot' : servers }} }})(['jsroot'], execCode);
105-
}} else {{
106-
const config = document.getElementById('jupyter-config-data');
107-
if (config)
108-
servers[0] = (JSON.parse(config.innerHTML || '{{}}')?.baseUrl || '/') + 'static/';
109-
else
110-
servers.shift();
111-
function loadJsroot() {{
112-
return !servers.length ? 0 : import(servers.shift() + path + '.js').catch(loadJsroot).then(() => execCode(JSROOT));
113-
}}
114-
loadJsroot();
115-
}}
78+
Core.buildGUI('{jsDivId}','notebook').then(h => h.openRootFile(bytes.buffer));"""
79+
80+
81+
_jsCode = """function execCode_{jsDivId}(Core) {{
82+
Core.settings.HandleKeys = false;
83+
{jsDrawCode}
84+
}}
85+
const servers = ['/static/', 'https://root.cern/js/7.11.0/', 'https://jsroot.gsi.de/7.11.0/'],
86+
path = 'build/jsroot';
87+
if (typeof JSROOT !== 'undefined')
88+
execCode_{jsDivId}(JSROOT);
89+
else if (typeof requirejs !== 'undefined') {{
90+
servers.forEach((s,i) => {{ servers[i] = s + path; }});
91+
requirejs.config({{ paths: {{ 'jsroot' : servers }} }})(['jsroot'], execCode_{jsDivId});
92+
}} else {{
93+
const config = document.getElementById('jupyter-config-data');
94+
if (config)
95+
servers[0] = (JSON.parse(config.innerHTML || '{{}}')?.baseUrl || '/') + 'static/';
96+
else
97+
servers.shift();
98+
function loadJsroot_{jsDivId}() {{
99+
return !servers.length ? 0 : import(servers.shift() + path + '.js').catch(loadJsroot_{jsDivId}).then(() => execCode_{jsDivId}(JSROOT));
116100
}}
117-
process_{jsDivId}();
118-
</script>
119-
"""
101+
loadJsroot_{jsDivId}();
102+
}}"""
120103

121-
TBufferJSONErrorMessage = "The TBufferJSON class is necessary for JS visualisation to work and cannot be found. Did you enable the http module (-D http=ON for CMake)?"
104+
105+
TBufferJSONErrorMessage = "The TBufferJSON class is necessary for JS visualisation to work and cannot be found. Check your ROOT installation and libRIO library"
122106

123107

124108
def TBufferJSONAvailable():
@@ -508,9 +492,7 @@ class NotebookDrawerUrl:
508492
def __init__(self, theUrl):
509493
self.drawUrl = theUrl
510494

511-
def _getUrlJsCode(self):
512-
513-
id = _getUniqueDivId()
495+
def _getUrlJsCode(self, id):
514496

515497
drawHtml = _jsFullWidthDiv.format(
516498
jsDivId=id,
@@ -531,7 +513,8 @@ def _getUrlJsCode(self):
531513
return thisJsCode
532514

533515
def Draw(self, displayFunction):
534-
code = self._getUrlJsCode()
516+
id = _getUniqueDivId()
517+
code = self._getUrlJsCode(id)
535518
displayFunction(display.HTML(code))
536519

537520

@@ -545,7 +528,14 @@ def __init__(self, theFileName, theForce=False):
545528
self.drawFileName = theFileName
546529
self.drawForce = theForce
547530

548-
def _getFileJsCode(self):
531+
def _getFileHtmlCode(self, id):
532+
drawHtml = _jsFullWidthDiv.format(
533+
jsDivId=id,
534+
jsCanvasHeight=_jsCanvasHeight
535+
)
536+
return drawHtml
537+
538+
def _getFileJsCode(self, id):
549539
base64 = ""
550540

551541
with ROOT.TDirectory.TContext(), ROOT.TFile.Open(self.drawFileName) as f:
@@ -570,30 +560,24 @@ def _getFileJsCode(self):
570560

571561
base64 = ROOT.TBase64.Encode(addrc, sz)
572562

573-
id = _getUniqueDivId()
574-
575-
drawHtml = _jsFullWidthDiv.format(
576-
jsDivId=id,
577-
jsCanvasHeight=_jsCanvasHeight
578-
)
579-
580563
browseFileCode = _jsBrowseFileCode.format(
581564
jsDivId=id,
582565
fileBase64=base64
583566
)
584567

585568
thisJsCode = _jsCode.format(
586569
jsDivId=id,
587-
jsDivHtml=drawHtml,
588570
jsDrawCode=browseFileCode
589571
)
590572

591573
return thisJsCode
592574

593575
def Draw(self, displayFunction):
594-
code = self._getFileJsCode()
595-
displayFunction(display.HTML(code))
596-
576+
id = _getUniqueDivId()
577+
html = self._getFileHtmlCode(id)
578+
code = self._getFileJsCode(id)
579+
displayFunction(display.HTML(html))
580+
displayFunction(display.Javascript(code))
597581

598582

599583
class NotebookDrawerJson:
@@ -622,9 +606,17 @@ def _getJson(self):
622606
def _getJsOptions(self):
623607
return ""
624608

625-
def _getJsCode(self):
609+
def _getHtmlCode(self, id):
626610
width = self._getWidth()
627611
height = self._getHeight()
612+
drawHtml = _jsFixedSizeDiv.format(
613+
jsDivId=id,
614+
jsCanvasWidth=width,
615+
jsCanvasHeight=height
616+
)
617+
return drawHtml
618+
619+
def _getJsCode(self, id):
628620
json = self._getJson()
629621
options = self._getJsOptions()
630622

@@ -633,14 +625,6 @@ def _getJsCode(self):
633625

634626
zip = ROOT.TBufferJSON.zipJSON(json)
635627

636-
id = _getUniqueDivId()
637-
638-
drawHtml = _jsFixedSizeDiv.format(
639-
jsDivId=id,
640-
jsCanvasWidth=width,
641-
jsCanvasHeight=height
642-
)
643-
644628
drawJsonCode = _jsDrawJsonCode.format(
645629
jsDivId=id,
646630
jsonLength=len(json),
@@ -650,9 +634,9 @@ def _getJsCode(self):
650634

651635
thisJsCode = _jsCode.format(
652636
jsDivId=id,
653-
jsDivHtml=drawHtml,
654637
jsDrawCode=drawJsonCode
655638
)
639+
656640
return thisJsCode
657641

658642
def _getCanvas(self):
@@ -675,9 +659,12 @@ def _getPngImage(self):
675659

676660
def Draw(self, displayFunction):
677661
global _enableJSVis
662+
id = _getUniqueDivId()
678663
if _enableJSVis and self._canJsDisplay():
679-
code = self._getJsCode()
680-
displayFunction(display.HTML(code))
664+
html = self._getHtmlCode(id)
665+
code = self._getJsCode(id)
666+
displayFunction(display.HTML(html))
667+
displayFunction(display.Javascript(code))
681668
elif self._canPngDisplay():
682669
displayFunction(self._getPngImage())
683670
else:
@@ -718,23 +705,42 @@ def _getCanvas(self):
718705

719706
def Draw(self, displayFunction):
720707
global _enableJSVis, _canvasHandles
721-
code = ""
722-
if _enableJSVis and self._canJsDisplay():
723-
code = display.HTML(self._getJsCode())
724-
elif self._canPngDisplay():
725-
code = self._getPngImage()
726-
else:
727-
code = display.HTML(f"Neither JSROOT nor plain drawing of {self.drawObject.ClassName()} is implemented")
728708

709+
html = ""
710+
code = ""
711+
handle = None
729712
name = self._getCanvasId()
730713
updated = self._getUpdated()
714+
id = _getUniqueDivId()
715+
731716
if updated and name and (name in _canvasHandles):
732-
_canvasHandles[name].update(code)
733-
elif name:
734-
_canvasHandles[name] = displayFunction(code, display_id=True)
735-
else:
736-
displayFunction(code)
717+
handle = _canvasHandles[name]
718+
id = handle["divId"]
737719

720+
if _enableJSVis and self._canJsDisplay():
721+
html = display.HTML(self._getHtmlCode(id))
722+
code = display.Javascript(self._getJsCode(id))
723+
elif self._canPngDisplay():
724+
html = self._getPngImage()
725+
else:
726+
html = display.HTML(f"Neither JSROOT nor plain drawing of {self.drawObject.ClassName()} is implemented")
727+
728+
if handle:
729+
handle["htmlOutput"].update(html)
730+
if handle["codeOutput"]:
731+
handle["codeOutput"].update(code)
732+
elif code:
733+
handle["codeOutput"] = displayFunction(code)
734+
else:
735+
html = displayFunction(html, display_id=True)
736+
if code:
737+
code = displayFunction(code)
738+
if name:
739+
_canvasHandles[name] = {
740+
"divId": id,
741+
"htmlOutput": html,
742+
"codeOutput": code,
743+
}
738744

739745

740746
class NotebookDrawerTCanvas(NotebookDrawerCanvBase):
@@ -892,15 +898,8 @@ def enhanceROOTModule():
892898
ROOT.enableJSVis = enableJSVis
893899

894900

895-
def enableCppHighlighting():
896-
ipDispJs = display.display_javascript
897-
# Define highlight mode for %%cpp magic
898-
ipDispJs(_jsMagicHighlight.format(cppMIME=cppMIME), raw=True)
899-
900-
901901
def iPythonize():
902902
setStyle()
903903
initializeJSVis()
904904
loadMagicsAndCapturers()
905-
# enableCppHighlighting()
906905
enhanceROOTModule()

0 commit comments

Comments
 (0)