Repository: bokeh/bokeh

Issue:
Bokeh / JupyterLab integration 
Updated high level design principles:

* [ ] mime bundle for loading BokehJS

    The `output_notebook()` function generates a mime bundle looks like 

    ```
    {'application/javascript': js, 
      'application/vnd.bokeh.bootstrap_bokehjs': ''}
    ```
     In the classic notebook, it ignores the bokeh mimetype and runs the JS. In JLab, the Bokeh mimetype has a higher precedence, so it ignores the JS mimetype.

    * For the classic notebook  the JS will load the Bokeh mini-extension to register for the `show` mime type. Additionally BokehJS will be loaded from CDN or for `inline` resources, from the JS in the bundle itself. 

   * For JLab the Bokeh mini-extension will be previously installed via usual JLab mechanisms. The extension will load BokehJS from CDN or for `inline` resources, from the locally installed Bokeh package. (via a REST endpoint??)

* [ ] mime bundle for showing standalone docs and applications

    The `show(...)`  function generates a mime bundle that looks like

    ```
    'application/vnd.bokeh.show`: JSON
    ```

    Where JSON is the serialized Bokeh Document and any additional metadata (versions, comm ids etc)


Discussion:
> does JupyterLab expose anything that would reliably permit detection of when code is running in JupyterLab?

When python code is displaying in lab, or when javascript is running in lab? If the former, it's impossible for python code to tell if it's displaying to lab - it may be displaying to many different frontends at the same time, including lab and other frontends. If the latter - well, the javascript will be a lab plugin, so yes, it knows it is running in lab.

*Edit: it's not a matter of 'relenting' - it's a core architecture feature that python code can be simultaneously rendering to many different frontends, so it doesn't make sense for the python code to assume one particular frontend.*
@jasongrout what I take from  that is that it will be fundametally impossible to have a single (Python) function `show(plot)` what works in both the classic notebook and in JupyterLab, do you disagree? That seems like a pretty terrible situation. 
> @jasongrout what I take from that is that it will be fundametally impossible to have a single (Python) function show(plot) what works in both the classic notebook and in JupyterLab, do you disagree? That seems like a pretty terrible situation.

No, not at all. `display(plot)` should construct a display_data message that is agnostic to the frontend. The frontend (e.g., a plugin in the frontend) should take that message and do something that makes sense to display it.

*edit: I mean, "yes, I disagree". Sorry, it's difficult to phrase answers to questions asked in the negative.*
> display_data message

Where is this documented for the classic notebook and for jlab? How can a python function `output_notebook` install the JS handler for this function in the classic notebook? Right now in the notebook, users can do:
```

# imports

output_notebook()

plot = figure(...)

show(plot)
```
and that just works, with no additional steps, no extension building or work required outside the classic notebook. That can't change (for the classic notebook), because there's going on five years of accumulated info and demonstration of that usage. 
The ability to display custom mime types was added to Notebook 4.3+ here: https://github.com/jupyter/notebook/pull/1866.    Here is an example implementation of a notebook renderer: https://github.com/jupyterlab/jupyterlab_json/blob/master/nbextension/src/renderer.js.   The `output_notebook()` function could become a deprecated no-op, and `show()` would use the custom mime type handling.
This would require each frontend have a bokeh extension installed in order to render the mimetype. 
(For the classic notebook, though, you might be able to forego an extension and just register your mimetype with the notebook using javascript in your output_notebook() function)
> (For the classic notebook, though, you might be able to forego an extension and just register your mimetype with the notebook using javascript in your output_notebook() function)

if `ouput_notebook` unconditionally publishes JS, will it always just get silently ignored in JLab? (or is/will there be a warning?) If it will just be silently ignored in JLab, then yes that might work
Right now, Lab ignores any script tags in the text/html mimetype, but the notebook executes it. So that's one way to do that.
I'll be honest and say this prospect petrifies me. The current implementation for the classic notebook, which was created using the tools that existed at the time, before any of this mime API exsisted, has eaten up person-months of time with bugs and instabilities. 
> has eaten up person-months of time with bugs and instabilities.

That's *precisely* why we wanted to create a much more robust extension system, à la JupyterLab. The original notebook was not meant to be extended, and trying to extend it meaningfully was hard and error-prone.
> That's precisely why we wanted to create a much more robust extension system, à la JupyterLab. The original notebook was not meant to be extended, and trying to extend it meaningfully was hard and error-prone. So to quote the movie Moana, on behalf of the JLab team: "You're welcome!" :)

@jasongrout I get it, and I hope all the promises turn out, I'm just saying based on my current experience, which is the only experience that I have to draw on: I'll believe it's easy and care free when I see it... I look forward to being pleasantly surprised. 
> I'll believe it's easy and care free when I see it

The JLab team (especially @blink1073 and @sccolbert who really dove deep into the extension work) would probably agree that it hasn't been easy or carefree to develop it ("person-months of [dealing with] bugs and instabilities [in browsers, packaging systems, etc.]" would just *start* describing the work they've done), but we hope the end product will be a great experience.

That said, we're certainly glad when people willing to live on the bleeding edge test things and write extensions to help us iron out issues. So we welcome your involvement!
But back to the issue - do you see the way forward now?
> But back to the issue - do you see the way forward now?

Assuming we try the "`output_notebook` publishes JS for the classic notebook to install the handler, that gets ignored in JLab" route, is there a way in classic notebook to know when the handler is installed? We try to block `output_notebook` until it is certain things that will be subsequently needed are actually in place (or show a giant error on timeout). Right now, that trigger of success `window.Bokeh` being present. What can we wait on/check for if the JS code installs a handler? 

The problem situation trying to avoid: user executes "run all cells" and a cell with show executes before the JS published by `output_notebook` is actually finished installing the handler. 
Actually, how will things work with BokehJS and JLab at all? What happens when a new version is released? We are not at a stage where we can guarantee compat between independent Bokeh and BokehJS versions. They need to be locked together. What happens when a user updates bokeh? it seems like things will break? Also will BokehJS be embedded in static notebooks? We had a huge outcry about large notebooks and a huge call to use CDN resources by default. How is that supposed to work with JLab?

Do we need instead to consider a tiny shim npm module that loads the appropriate BokehJS version from CDN? 

You can check for the existence of the mimetype in [OutputArea.append_map](https://github.com/gnestor/notebook/blob/4cf93c8c5769b035df598796b7d43153ef3c5b89/notebook/static/notebook/js/outputarea.js#L988).
A JLab extension can do whatever it wants, including execute script tags. Installed extensions are implicitly trusted. So the Bokeh extension could loads it's JS once on app startup, and then execute whatever extra stuff is contained in the mime bundle (if needed) when it's rendering the output.
> A JLab extension can do whatever it wants, including execute script tags. Installed extensions are implicitly trusted. So the Bokeh extension could loads it's JS once on app startup, and then execute whatever extra stuff is contained in the mime bundle (if needed) when it's rendering the output.

ping @mattpap @philippjfr @jlstevens I think this is potentially important information. Based on this I think we should consider making a small stable (unchanging, versionless) extension that then loads the property BokehJS version from CDN from the local copy in the package. This would allow us to punt on the NPM packages for BokehJS itself for now, and proceed immediately. 

I think this would mean lazy loading, i.e. the call to `display_data` includes the Bokeh version and that's when the load happens if necessary. But maybe the extension can inspect an installed Bokeh for its version on startup. 
@bryevdv That does sounds like a very promising avenue to experiment with first.
@jasongrout @sccolbert are there any hooks when statically rendering? A meta-extension that loads BokehJS from CDN or the local package would *by far* the most manageable scenario for us, but I am not sure how embedded/inline BokehJS resources (e.g. in order to have static notebooks that work behind an airgap) for a statically rendered notebook could work in that case. The actual BokehJS source code would need to be include in the statically rendered output. 
What do you mean by "hooks"?

Assuming you mean something like "load your stuff", then each extension defines an "activate" function which is called on app startup. That function returns a `Promise` which it resolves when activation is "done". The app waits until all extensions are "done" loading before the user can interact with it. You could load your static JS assets there by putting a `<script>` tag on the page, and resolving the promise when the script is done loading.
If you need to ship the source as part of the mime bundle, you can do something similar. In your mime renderer, check to see if you already loaded the source for a particular version, and if not, slap the script tag on the page for that version at that time.
> If you need to ship the source as part of the mime bundle

That might work
But if you're packaging up a platform with a known version of Bokeh, that extension can just be installed upfront, so the bokeh assets are loaded any time the app is run. Then you don't need to ship the sources with each mime bundle.
Or taken a step further, the Bokeh extension could bundle a number of versions, and the mime renderer can request the Bokeh API object for a particular version when it needs to render. The extension could load those versions on-demand.
> and the mime renderer can request the Bokeh API object for a particular version when it needs to render.

I am unclear what all the renderer can do / has access to. The mime renderer is JavaScript, correct? How does it request loading BokehJS from local disk (absolutely needed for developing Bokeh, among other things)? It seems to me that for inline resources the python side of things would have to send the BokehJS as part of the display message, as originally suggested. (Especially in the static render case) 
The mime renderer has access to whatever you give it access to as part of your extension. When you create your extension package (Javascript), it can load the JS resources in a number of ways:

- bundle multiple versions of the JS source in the package itself
- load the JS source from CDN using a script tag
- load the JS source from a url pointing to somewhere in the server root
- load the JS source from disk by making REST request to the appropriate endpoint

I'm not sure where "static rendering" enters into the JLab equation.
> I'm not sure where "static rendering" enters into the JLab equation.

Maybe it doesn't, I am assuming that JLab will have a similar capability to save pure HTML/JS files  ala statically rendered notebooks, e.g. like http://nbviewer.jupyter.org/github/bokeh/bokeh-notebooks/blob/master/quickstart/quickstart.ipynb that can still be viewed without a running kernel. Is that not the case? 

If that is the case then for inline resources (what you'd want behind an air gap) then that HTML output needs to have BokehJS *embedded inside the HTML* when the HTML is generated, for things to work. 
Where can we find an example or docs for how to send a display_data message? 

I see 

http://jupyter-client.readthedocs.io/en/latest/messaging.html#display-data

But what do I *do* with such a JSON blob, once constructed?
You can implicitly use it like we do in the jupyterlab-fasta project - see the example in the readme: https://github.com/jupyterlab/jupyterlab-fasta

To explicitly send such a message, here's how we do it in ipywidgets: https://github.com/jupyter-widgets/ipywidgets/blob/master/ipywidgets/widgets/widget.py#L671
Actually for loading BokehJS is there any reason we couldn't just use another mime-type? Then `output_notebook` would either load Bokeh BokehJS from CDN or load it from the `display_data` message. I am assuming a renderer could install `window.Bokeh` to be used later by other renderers?
The extension is a singleton, it can keep a reference to the shared state.  An example is the `WidgetManager` used by [ipywidgets](https://github.com/jupyter-widgets/ipywidgets/blob/1ab39c03304d3fafe6654d1ac8fdbc6c2baa4c35/packages/jupyterlab-manager/src/plugin.ts#L94).
You already need to install an extension in order to make the mime renderer available, so why not just load the `window.Bokeh` in that extension, rather that shipping all that JS over the wire in each mime bundle?
> You already need to install an extension in order to make the mime renderer available, so why not just load the window.Bokeh in that extension, rather that shipping all that JS over the wire in each mime bundle?

That's what I was suggesting, one mime extension to load `window.bokeh`, either from CDN or from local source (used once, by `output_notebook`) and another mime extension to send just Bokeh doc JSON that uses the previously loaded BokehJS
Those can both be the same extension.
To be clear a "mime extension" is just a simplified version of a full-blown jlab extension. You can create a single JLab extension which enables everything we're talking about.
What is he best method to determine if an output cell has been deleted that will work in both notebook 4.3+ and JuplyterLab. We currently set up a MutationObservers when we show bokeh apps inline, so that the appropriate tear-down and resource release can happen. 
> Right now, Lab ignores any script tags in the text/html mimetype, but the notebook executes it. So that's one way to do that.

@jasongrout JLab ignores, but not silently:

<img width="336" alt="screen shot 2017-08-08 at 12 42 48" src="https://user-images.githubusercontent.com/1078448/29086598-c96607e4-7c38-11e7-879c-82990b200e9b.png">

Is there a programmatic way to silence this `"JavaScript output is disabled in JupyterLab"` warning for a particular output cell? If not, I am back to not knowing how to proceed in a way that

* works for both classic notebook 4.3+ and JupyterLab
* doesn't break 4+ years of guidance all over the internet (i.e. keeps `output_notebook`)
* doesn't issue warnings for normal correct usage
> Is there a programmatic way to silence this "JavaScript output is disabled in JupyterLab" warning for a particular output cell?

Great question - I didn't realize there was a warning. @blink1073, @sccolbert?
Perhaps we can log a warning to the console, but not in the output? I realize it's a balance.
In JupyterLab, the bokeh manager can connect to each created bokeh widget's `disposed` [signal](https://github.com/phosphorjs/phosphor/blob/23b9d075ebc5b73ab148b6ebfc20af97f85714c4/packages/widgets/src/widget.ts#L95).

In the classic notebook, there is a `clear_output.CodeCell` [event](https://github.com/jupyter/notebook/blob/da2d54f73e5aa2f8f5285b2e477b15407dc2e9c3/notebook/static/notebook/js/codecell.js#L524) and a `delete.Cell` [event](https://github.com/jupyter/notebook/blob/master/notebook/static/notebook/js/notebook.js#L1163) that you can spy on. 

We had a request for the warning because otherwise from a user standpoint it wasn't clear why the anticipated JavaScript output was being suppressed.
> We had a request for the warning because otherwise from a user standpoint it wasn't clear why the anticipated JavaScript output was being suppressed.

@blink1073 That makes perfect sense in general, but it would be useful to be able to override and suppress it intentionally for specific purposes. Is that feasible?
That message is generated by the mime renderer (IIRC). If you are creating your own mime renderer for bokeh, it shouldn't be a problem.
> That message is generated by the mime renderer (IIRC). If you are creating your own mime renderer for bokeh, it shouldn't be a problem.

This is half wrong. Looking for where that message is generated. The default mime renderers log to the console.
Ok here's what's generating that message for the JS output:
https://github.com/jupyterlab/jupyterlab/blob/c956182b6027566146d49d0efa2b91ebb260f4e3/packages/rendermime/src/widgets.ts#L402

If you write your own mime renderer, it wouldn't use this class, so you wouldnt get the message.
If Bokeh uses the `JavaScript` display function from `ipython`, it will generate this output.
Yep, [here](https://github.com/bokeh/bokeh/blob/170b23fa85d5bdea9ebc8914361f570ed4fa2e11/bokeh/util/notebook.py#L35).
Those lines could be modified to `publish_display_data({'text/html': '<script> + js + </script>' + html})`, which would only show the console log in JupyterLab.
We don't use `JavaScript`, we do this:
```
    if notebook_type=='jupyter':
        publish_display_data({'text/html': html})
        publish_display_data({'application/javascript': js})
```
Which seems to be equivalent as fas as getting the warning is concerned. In any case, since there is not any way to know if we are in the notebook, or in jupyterlab, we evidently must find a single solution that works for both. So I would like to unconditionally publish JS code that *installs the mime extension* when `output_notebook` is called:

* In the classic notebook, This JS registers the extension, and all subsequent work goes through `display` with a mimetpye
* in JLab, this JS is just ignored! the extension is installed via normal JLab means and all subsequent work goes though `display` with a mime type

That would satisfy the requirements as I see them:

* works for both classic notebook 4.3+ and JupyterLab  *(this is imposed from the Jupyter side, since there is no way to switch on whether we are in the notebook or JLab)*
* doesn't break 4+ years of guidance all over the internet (i.e. keeps output_notebook) *(otherwise eery existing notebook breaks, which is unacceptable)* 
* doesn't issue warnings for normal correct usage *(because the resulting support/bad bug report  burden would be impossible to deal with)*

But for this to be viable, there would need to be way to selectively suppress the warning in JLab, i.e. it needs to be ignored *silently*. Is it possible to add this capability? I am asking for it to be added. 

Otherwise, what are the alternative? 
I think @sccolbert is right - there should *also* be a mimetype that is bokeh-specific. In jlab, the bokeh renderer has precedence, so the html and javascript mimetypes are ignored, and you don't get a message.
> `publish_display_data({'text/html': '<script> + js + </script>' + html})`

Does this go through jquery at any point?
In the classic notebook, yes.  In JupyterLab there is no global jquery.
I agree there should be a mime type! But the mime extension needs to be *installed* in classic notebook. I am proposing to have `output_notebook` do that by publishing the JS, so that all existing notebooks *continue to function*, and since there is no way to switch based on classic/jlab, to have it do that unconditionally (which is perfectly fine if JL will be quiet about ignoring it).

>  Does this go through jquery at any point?

I don't know. BokehJS itself does not rely on JQ at all. This is what `publish_display_data` does: 
```
    import IPython.core.displaypub as displaypub
    try:
        displaypub.publish_display_data(source, data)
    except TypeError:
        displaypub.publish_display_data(data)
```
I ask, because embedding complex javascript in HTML and passing it through jquery (`$.html()` in particular) fails quite often. For example, using inline `bokeh.min.js` in the notebook is possible only with `application/javascript` MIME.
We could provide a default mime renderer in JupyterLab that is a higher priority than `application/javascript` that is a no-op for this purpose.  Then the call would be:

`publish_display_data({'application/javascript': js, '<suppress_in_jlab>', '' })`
> I agree there should be a mime type! But the mime extension needs to be installed in classic notebook! I am proposing to have output_notebook do that, and since there is no way to switch on classic/jlab, to have it do that unconditionally.

Since the bokeh extension will already be installed in jlab, you can still include a bokeh-specific mimetype (maybe `application/vnd.bokeh.ignore` :) that essentially is 

Yeah, @blink1073 beat me to it. What he said.

*Edit: well, almost what @blink1073 said. I was thinking that your bokeh extension would provide that no-op mimetype...*
>  So I would like to unconditionally publish JS code that installs the mime extension when output_notebook is called:

That's not how the extension mechanism works. The user will install the bokeh extension into jupyter lab *as a separate step*. **You are not able to install extensions by shipping code from the kernel to the client**.

You can put multiple mime types in a single bundle, and jupyterlab will pick the "best" renderer based on what's available to service the various types contained in the bundle.

Your *separately installed* bokeh extension should do this:

1. Register a mime renderer for bokeh-specific mimetypes (like `application/vnd.bokeh.js`) with a higher rank than the default `application/javascript` renderer.
2. Load the bokeh js from cdn (or wherever) the first time a bokeh renderer is created
3. Render the data for the plot.
> That's not how the extension mechanism works. The user will install the bokeh extension into jupyter lab as a separate step. You are not able to install extensions by shipping code from the kernel to the client.

In the *classic notebook*? Because I outlined this plan to install a mime renderer this way way up above and there was no comment to this effect then
No sorry, in jlab. I misunderstood you.
I think the missing piece for you is that you can ship multiple mime types in the same bundle, and have your jlab renderer take precedence over the default `application/javascript` renderer.

OK, in that case then I think what @blink1073  suggested would be perfect solution. Just to be clear, that involves some small work on the Jupyter side, correct?
> We could provide a default mime renderer in JupyterLab that is a higher priority than application/javascript that is a no-op for this purpose.

I would really hesitate setting a precedent for a no-op mimetype that is specific to a frontend.
> OK, in that case then I think what @blink1073 suggested would be perfect solution. Just to be clear, that involves some small work on the Jupyter side, correct?

If you register that no-op mimetype in your bokeh extension, there's no need for any changes in jlab.
> If you register that no-op mimetype in your bokeh extension

Why is a no-op mimetype needed, if there's a mimetype/renderer with higher precedence?
> If you register that no-op mimetype in your bokeh extension, there's no need for any changes in jlab.

OK that was not evident from earlier discussion, or from the docs that I was looking at. Will have to give it a try.
> Why is a no-op mimetype needed, if there's a mimetype/renderer with higher precedence?

As I understand it, there are two types of messages he is talking about. One is an html+javascript message that should only be run in the classic notebook, that installs the extension in the classic notebook. The other is a rendering message that actually has a plot. The discussion above is about the first type of message.
> As I understand it, there are two types of messages he is talking about. One is an html+javascript message that should only be run in the classic notebook, that installs the extension in the classic notebook. The other is a rendering message that actually has a plot. The discussion above is about the first type of message.

Yes this is exactly correct. (Or, if "run" in JLAB, should be silently ignored.) Thank you for putting it more succinctly than I have been able to. 
So you register a jlab mimetype renderer for the first type, say with a mimetype of `application/vnd.bokeh.bootstrap_classic_notebook_extension`, and in jlab that is a no-op to render. You also register your more standard `application/vnd.bokeh.plot` mimetype, with whatever you do to render it.
> So you register a jlab mimetype renderer for the first type, say with a mimetype of application/vnd.bokeh.bootstrap_classic_notebook_extension, and in jlab that is a no-op to render.

If the bundle doesn't include any `application/javascript` at all, would a no-op renderer be needed? If there is no renderer for any mimetype, the output item will be empty by default.
Are you saying that if the first message type is just `{'text/html': '<script>...</script>'}`, then nothing is noted to the user (is there a message to the console?), and the js is not run in jlab, so it effectively is a no-op renderer?
As @mattpap said, bokeh relies specifically on the `application/javascript` mimetype in the classic notebook.
> So you register a jlab mimetype renderer for the first type, say with a mimetype of application/vnd.bokeh.bootstrap_classic_notebook_extension, and in jlab that is a no-op to render. 

OK, that seems potentially possible. But I'd like to refer back to @mattpap comment:

> I ask, because embedding complex javascript in HTML and passing it through jquery ($.html() in particular) fails quite often. For example, using inline bokeh.min.js in the notebook is possible only with application/javascript MIME.

We didn't used to pubslish to `application/javascript` in the classic notebook, we had to switch to that because things just stopped working otherwise at some point. Are you saying if we do (in the classic notebook):
```
   publish_display_data({'application/vnd.bokeh.bootstrap_classic_notebook_extension': js })
```
that there is a way to make it behave otherwise identically to  `application/javascript` in the classic notebook? 

I'm saying if bokeh only sends messages with bokeh-specific mimetypes, and there are no renderers for those types, then nothing will happen.
They will need to run at least some JS in the classic notebook to register a mimetype handler.
Wait, no, that can be done with a notebook extension.
> Wait, no, that can be done with a notebook extension.

They don't want to require the user to install a notebook extension in the classic notebook.



> Are you saying if we do (in the classic notebook):
> 
>    publish_display_data({'application/vnd.bokeh.bootstrap_classic_notebook_extension': js })
> that there is a way to make it behave otherwise identically to application/javascript in the classic notebook?

I'm saying your `output_notebook()` mimebundle looks like `{'application/javascript': js, 'application/vnd.bokeh.bootstrap_classic_notebook_extension': ''}`. In the classic notebook, it ignores the bokeh mimetype and runs the js. In jlab, the bokeh mimetype has a higher precedence, so it ignores the js mimetype.
@jasongrout has summarized things perfectly above. I will try to run some preliminary experiments to validate this design approach. 

Please also know I'm not trying to be snarky, or short with anyone. If I have seemed so (or been so) I apologize. I genuinely appreciate any and all help, because the perception below is the source of my extreme frustration and stress right now:

<img width="515" alt="screen shot 2017-08-08 at 17 25 19" src="https://user-images.githubusercontent.com/1078448/29097288-9924bae6-7c5e-11e7-9626-8315470c18cc.png">

>  In jlab, the bokeh mimetype has a higher precedence, so it ignores the js mimetype.

It still seems to print the warning

<img width="475" alt="screen shot 2017-08-10 at 14 50 51" src="https://user-images.githubusercontent.com/1078448/29189230-62bce31a-7ddb-11e7-827d-849e3af3ed64.png">

@jasongrout is this just because the bokeh mimetype is not installed? i.e. will go away as soon as it is?
> @jasongrout is this just because the bokeh mimetype is not installed? i.e. will go away as soon as it is?

Yes.
OK great, I just wanted to make sure there was not some explicit step we would need to take

Resolving patch:
diff --git a/bokeh/io.py b/bokeh/io.py
--- a/bokeh/io.py
+++ b/bokeh/io.py
@@ -36,7 +36,7 @@
 from .resources import INLINE
 import bokeh.util.browser as browserlib  # full import needed for test mocking to work
 from .util.dependencies import import_required, detect_phantomjs
-from .util.notebook import get_comms, load_notebook
+from .util.notebook import get_comms, load_notebook, EXEC_MIME_TYPE
 from .util.string import decode_utf8
 from .util.serialization import make_id
 
@@ -48,8 +48,6 @@
 
 _state = State()
 
-LOAD_MIME_TYPE = 'application/vnd.bokehjs_exec.v0+json'
-
 #-----------------------------------------------------------------------------
 # Local utilities
 #-----------------------------------------------------------------------------
@@ -308,7 +306,7 @@ def _show_notebook_app_with_state(app, state, app_path, notebook_url):
     url = 'http://%s:%d%s' % (notebook_url.split(':')[0], server.port, app_path)
     script = server_document(url)
 
-    publish_display_data({LOAD_MIME_TYPE: {"div": script}}, metadata={LOAD_MIME_TYPE: {"server_id": server_id}})
+    publish_display_data({EXEC_MIME_TYPE: {"div": script}}, metadata={EXEC_MIME_TYPE: {"server_id": server_id}})
 
 def _show_with_state(obj, state, browser, new, notebook_handle=False):
     controller = browserlib.get_browser_controller(browser=browser)
@@ -335,7 +333,7 @@ def _show_file_with_state(obj, state, new, controller):
 def _show_jupyter_with_state(obj, state, notebook_handle):
     comms_target = make_id() if notebook_handle else None
     (script, div) = notebook_content(obj, comms_target)
-    publish_display_data({LOAD_MIME_TYPE: {"script": script, "div": div}}, metadata={LOAD_MIME_TYPE: {"id": obj._id}})
+    publish_display_data({EXEC_MIME_TYPE: {"script": script, "div": div}}, metadata={EXEC_MIME_TYPE: {"id": obj._id}})
     if comms_target:
         handle = _CommsHandle(get_comms(comms_target), state.document,
                               state.document.to_json())
diff --git a/bokeh/util/notebook.py b/bokeh/util/notebook.py
--- a/bokeh/util/notebook.py
+++ b/bokeh/util/notebook.py
@@ -7,6 +7,9 @@
 
 from ..embed import _wrap_in_script_tag
 
+LOAD_MIME_TYPE = 'application/vnd.bokehjs_load.v0+json'
+EXEC_MIME_TYPE = 'application/vnd.bokehjs_exec.v0+json'
+
 _notebook_loaded = None
 
 def load_notebook(resources=None, verbose=False, hide_banner=False, load_timeout=5000, notebook_type='jupyter'):
@@ -36,11 +39,13 @@ def load_notebook(resources=None, verbose=False, hide_banner=False, load_timeout
         None
 
     '''
-    html, js = _load_notebook_html(resources, verbose, hide_banner, load_timeout)
+    nb_html, nb_js = _load_notebook_html(resources, verbose, hide_banner, load_timeout)
+    lab_html, lab_js = _load_notebook_html(resources, verbose, hide_banner, load_timeout, register_mime=False)
     if notebook_type=='jupyter':
-        publish_display_data({'text/html': html + _wrap_in_script_tag(js)})
+        publish_display_data({'text/html': nb_html + _wrap_in_script_tag(nb_js),
+                              LOAD_MIME_TYPE: {"script": lab_js, "div": lab_html}})
     else:
-        _publish_zeppelin_data(html, js)
+        _publish_zeppelin_data(lab_html, lab_js)
 
 
 FINALIZE_JS = """
@@ -52,7 +57,7 @@ def _publish_zeppelin_data(html, js):
     print('%html ' + '<script type="text/javascript">' + js + "</script>")
 
 def _load_notebook_html(resources=None, verbose=False, hide_banner=False,
-                        load_timeout=5000):
+                        load_timeout=5000, register_mime=True):
     global _notebook_loaded
 
     from .. import __version__
@@ -99,7 +104,8 @@ def _load_notebook_html(resources=None, verbose=False, hide_banner=False,
         js_raw   = resources.js_raw + [custom_models_js] + ([] if hide_banner else [FINALIZE_JS % element_id]),
         css_raw  = resources.css_raw_str,
         force    = True,
-        timeout  = load_timeout
+        timeout  = load_timeout,
+        register_mime = register_mime
     )
 
     return html, js


Test patch: