Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix rendering on JupyterHub 4.1: respect absolute_url for protocol determination, support data-absolute-url #14003

Merged
merged 10 commits into from
Aug 5, 2024

Conversation

krassowski
Copy link
Contributor

This PR:

  • fixes ws/wss determination when absolute_url is passed
  • supports reading the absolute URL from data-absolute-url of an iframe as srcdoc iframes cannot pass it otherwise (e.g. there is no motion of URL parameters for these)
Before After
Screenshot from 2024-07-29 17-16-06 Screenshot from 2024-07-29 17-29-59
How to test without installing JupyterHub
  1. Install pyviz_comms with Fix panel preview rendering on JupyterHub 4.1+ holoviz/pyviz_comms#125 applied
  2. Run jupyter --paths and choose a writable config path from the config: section, e.g. /home/username/my-env/etc/jupyter
  3. Open (create if needed) a /home/username/my-env/etc/jupyter/jupyter_server_config.py, paste the following:
     c.LabApp.tornado_settings = {
         'hub_prefix': '/hub',
         'hub_host': 'test',
         'user': 'test',
         'headers': {'Content-Security-Policy': "frame-ancestors 'none'"}
     }
  4. Start jupyter lab
  5. Open dev tools Network tab, click on a request and expand Response Headers, verify that CSP for frame ancestors is set to none:
    image
  6. Ensure that page data includes hubPrefix by viewing the source of JupyterLab page
    Screenshot from 2024-07-30 10-06-57
  7. Follow reproduction steps in Panel preview does not work on JupyterHub 4.1+ hosted on a single domain holoviz/panel#7039

Copy link

codecov bot commented Jul 30, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Please upload report for BASE (branch-3.6@b902fb5). Learn more about missing BASE report.

Additional details and impacted files
@@              Coverage Diff              @@
##             branch-3.6   #14003   +/-   ##
=============================================
  Coverage              ?   93.11%           
=============================================
  Files                 ?      281           
  Lines                 ?    19828           
  Branches              ?        0           
=============================================
  Hits                  ?    18462           
  Misses                ?     1366           
  Partials              ?        0           

@bryevdv bryevdv modified the milestone: 3.6 Jul 30, 2024
@bryevdv bryevdv requested a review from mattpap July 30, 2024 17:11
@mattpap mattpap added this to the 3.6 milestone Jul 31, 2024
Comment on lines 13 to 14
if (absolute_url === undefined && frameElement !== null && (frameElement as HTMLIFrameElement).dataset.absoluteUrl !== undefined) {
absolute_url = (frameElement as HTMLIFrameElement).dataset.absoluteUrl
Copy link
Contributor

@mattpap mattpap Jul 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove all this casting with:

Suggested change
if (absolute_url === undefined && frameElement !== null && (frameElement as HTMLIFrameElement).dataset.absoluteUrl !== undefined) {
absolute_url = (frameElement as HTMLIFrameElement).dataset.absoluteUrl
if (absolute_url === undefined && frameElement instanceof HTMLElement && frameElement.dataset.absoluteUrl !== undefined) {
absolute_url = frameElement.dataset.absoluteUrl
}

or alternatively use HTMLIFrameElement instead of HTMLElement, though I'm not sure if we need to be that specific.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thinking was the same here! But it does not work. This is because frameElement is an HTMLElement, but not window.HTMLElement. It is an HTMLElement from the context of the parent frame. I can make this work without type cast by checking for the right instance - which is just a bit more code - but frameElement by definition is an HTMLElement ("element" is in the name).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you still think I should modify it here or do you agree with the type cast here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example, using frameElement instanceof window.parent.HTMLElement can result in:

DOMException: Failed to read a named property 'HTMLElement' from 'Window': Blocked a frame with origin "http://localhost:5151" from accessing a cross-origin frame.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved in c639a85

Co-authored-by: Mateusz Paprocki <[email protected]>
@mattpap
Copy link
Contributor

mattpap commented Jul 31, 2024

We need to be able to test this somehow, preferably outside of jupyter. Perhaps an example showing <iframe> embedding would suffice (under examples/server).

@krassowski
Copy link
Contributor Author

@mattpap thank you for your review (dziękuję!) - I've added an example which both demonstrates the problem and a solution under examples/server/embed:

image

I also added a dedicated type guard to remove the explicit type cast (I do not like them either!).

@krassowski krassowski requested a review from mattpap July 31, 2024 11:16
@bryevdv
Copy link
Member

bryevdv commented Jul 31, 2024

examples/server should only contain subdirectories to match the docs structure, not individual examples. Also "embed" by itself is too general, there are lots of concepts about embedding. Possible suggestions:

  • examples/server/app/iframe_embed
  • somewhere under examples/integration

@krassowski
Copy link
Contributor Author

Thank you @bryevdv, I moved it to examples/server/app/iframe_embed following your suggestion.

@bryevdv
Copy link
Member

bryevdv commented Jul 31, 2024

@mattpap will have to comment on the test screenshot error

ERROR: Path for Bokeh server application does not exist: /home/runner/work/bokeh/bokeh/bokeh_server.py
tests/test_examples.py::test_server_examples[/home/runner/work/bokeh/bokeh/examples/server/app/iframe_embed-example12-config12] 
[FAIL] Unhandled rejection at:  ProtocolError: Cannot take screenshot with 0 width.
    at /home/runner/work/bokeh/bokeh/bokehjs/node_modules/chrome-remote-interface/lib/chrome.js:94:35
    at Chrome._handleMessage (/home/runner/work/bokeh/bokeh/bokehjs/node_modules/chrome-remote-interface/lib/chrome.js:272:17)
    at WebSocket.<anonymous> (/home/runner/work/bokeh/bokeh/bokehjs/node_modules/chrome-remote-interface/lib/chrome.js:240:22)
    at WebSocket.emit (node:events:518:28)
    at Receiver.receiverOnMessage (/home/runner/work/bokeh/bokeh/bokehjs/node_modules/chrome-remote-interface/node_modules/ws/lib/websocket.js:1070:20)
    at Receiver.emit (node:events:518:28)
    at Receiver.dataMessage (/home/runner/work/bokeh/bokeh/bokehjs/node_modules/chrome-remote-interface/node_modules/ws/lib/receiver.js:517:14)
    at Receiver.getData (/home/runner/work/bokeh/bokeh/bokehjs/node_modules/chrome-remote-interface/node_modules/ws/lib/receiver.js:435:17)
    at Receiver.startLoop (/home/runner/work/bokeh/bokeh/bokehjs/node_modules/chrome-remote-interface/node_modules/ws/lib/receiver.js:143:22)
    at Receiver._write (/home/runner/work/bokeh/bokeh/bokehjs/node_modules/chrome-remote-interface/node_modules/ws/lib/receiver.js:78:10) {
  request: {
    method: 'Page.captureScreenshot',
    params: { format: 'png', clip: [Object] },
    sessionId: undefined
  },
  response: { code: -32000, message: 'Cannot take screenshot with 0 width.' }
}

FAILED

@krassowski
Copy link
Contributor Author

Looking at:

- path: "examples/output/apis/*"
skip: ["autoload_static.py", "autoload_static_flask.py", "components.py", "components_themed.py", "json_item.py", "json_item_themed.py", "server_document", "server_session"]
- path: "examples/plotting/*"
- path: "examples/server/app/*"
type: server
skip: [
"faces",
"simple_hdf5",
"spectrogram",
"stocks", # Bokeh.index not avaiable, can't compute bbox
"surface3d", # Bokeh.index not avaiable, can't compute bbox
]

It seems that similar examples (e.g. examples server_document, json_item) are skipped. Should this one be skipped too?

@bryevdv
Copy link
Member

bryevdv commented Aug 1, 2024

Should this one be skipped too?

Let's just go with that option for now

@krassowski
Copy link
Contributor Author

It did not help. I think this is because the top-level file is not an example of Bokeh server with a single handler, but a flask application which embeds code which connects to the server running in a subprocess.

I am not sure how to proceed, possibly moving it elsewhere would be better. If a maintainer would prefer to rehash it please do push to my branch.

@krassowski
Copy link
Contributor Author

Sorry, I was wrong it did help, another job failed which appears unrelated. I will restart CI by pushing an empty commit to see if it helps.

@krassowski
Copy link
Contributor Author

Previously Bokeh-CI / unit-test (macos-latest, 3.12) failed and now Bokeh-CI / unit-test (macos-latest, 3.11) failed - it looks like the macos tests are flaky, is this correct?

@bryevdv
Copy link
Member

bryevdv commented Aug 2, 2024

Yes there is one server test that probably ought to get a retry policy or similar.

@bryevdv
Copy link
Member

bryevdv commented Aug 2, 2024

@krassowski I really appreciate your patience, I have one more request. After thinking about it, I would prefer this live under examples/server/api instead of app. All the examples under app are meant to be run with bokeh serve by the user, this example is much more like the embedding examples under api. I don't want to create confusion where someone tries to run bokeh serve iframe_embed (like all the other directory examples there) and it does not work.

@krassowski
Copy link
Contributor Author

@bryevdv make sense - done, and it looks all tests passed!

@bryevdv bryevdv merged commit 0300cc4 into bokeh:branch-3.6 Aug 5, 2024
24 checks passed
@bryevdv
Copy link
Member

bryevdv commented Aug 5, 2024

Thanks @krassowski !

@krassowski
Copy link
Contributor Author

Amazing, thank you! Is there a chance for backporting this PR or will it be only available in 3.6?

@bryevdv
Copy link
Member

bryevdv commented Aug 5, 2024

It seems like it would be easy to backport, so I have added the label. However, that just means that if there is a new release on previous branches, then this is a candidate to go in it. It's reasonably likely there will end up being more earlier point releases, but I can't guarantee it.

Edit: though looking at other PRs marked for backport, it seems fairly like there will be at some point.

mattpap added a commit that referenced this pull request Aug 9, 2024
…determination, support `data-absolute-url` (#14003)

* Fix rendering on JupyterHub 4.1

* Get rid of the mutable variable

Co-authored-by: Mateusz Paprocki <[email protected]>

* Add an example

* Do not use type casting, define a custom type guard

* Reduce size of the negative example iframe

* Lint

* Move to `app/iframe_embed`

* Skip `iframe_embed` for now

* Retrigger CI

* Move to `examples/server/api`

---------

Co-authored-by: Mateusz Paprocki <[email protected]>
@mattpap mattpap mentioned this pull request Aug 9, 2024
12 tasks
@mattpap mattpap modified the milestones: 3.6, 3.5.2 Aug 22, 2024
@bryevdv
Copy link
Member

bryevdv commented Aug 23, 2024

@krassowski this fix is available now in 3.5.2

Chiemezuo pushed a commit to Chiemezuo/bokeh that referenced this pull request Aug 27, 2024
…determination, support `data-absolute-url` (bokeh#14003)

* Fix rendering on JupyterHub 4.1

* Get rid of the mutable variable

Co-authored-by: Mateusz Paprocki <[email protected]>

* Add an example

* Do not use type casting, define a custom type guard

* Reduce size of the negative example iframe

* Lint

* Move to `app/iframe_embed`

* Skip `iframe_embed` for now

* Retrigger CI

* Move to `examples/server/api`

---------

Co-authored-by: Mateusz Paprocki <[email protected]>
Copy link

github-actions bot commented Dec 4, 2024

This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Dec 4, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
3 participants