Repository: holoviz/panel

Issue:
ValueError when embedding Panel Server App in Iframe
#### Description of expected behavior and the observed behavior

The [Location regex `"^$|[\/].*$"`](https://github.com/holoviz/panel/blob/ff43d36e14ff84238e0450bd56c7541ca5a0147a/panel/io/location.py#L41) fails when injecting a bokeh app in an iframe using `iframe.srcdoc`

I'm getting the following error in my Python logs:
```python-traceback
ValueError: String parameter 'pathname' value 'srcdoc' does not match regex '^$|[\\/].*$'.
```
However, the panel app seems to be rendered ok.


#### Complete, minimal, self-contained example code that reproduces the issue

- Webpage with iframe to hold panel/bokeh app containing the following:
    ```html
  <!DOCTYPE html>
  <html>
      <head>
          <title>Test iframe</title>
      </head>
      <body>
          <p>This is a web app.</p>
          <iframe id="myIframe" frameborder="10" marginwidth="10" marginheight="0" scrolling="NO" width="100%" height="500px" sandbox="allow-scripts"></iframe>
          <script>
              async function getSrc(iframe, url) {
                  const resp = await fetch(url, {
                      method: 'GET',
                      headers: {
                      'Accept': 'application/json',
                      'ContentType': 'application/json',
                      }
  
                  });
                  console.log("resp", resp)
                  const app_html = await resp.text();
                  console.log("app_html", app_html)
                  iframe.srcdoc = app_html
                  }
  
              const load = () => {
                  var iframe = document.getElementById("myIframe");
                  var url = "http://localhost:8000/bokeh-app";
                  getSrc(iframe, url)
              }
              load()
          </script>
          <p>This is the footer of the web app.</p>
      </body>
  </html>
    ```
- [FastAPI Server wrapping around Panel Server](https://panel.holoviz.org/how_to/integrations/FastAPI.html) in backend:
  ```python
  import panel as pn
  from bokeh.embed import server_document
  from fastapi import FastAPI, Request
  from fastapi.templating import Jinja2Templates
  
  app = FastAPI()
  templates = Jinja2Templates(directory="templates")
  
  
  @app.get("/bokeh-app")
  async def bkapp_page(request: Request):
      script = server_document(
          "http://127.0.0.1:5000/app",
      )
      return templates.TemplateResponse(
          "base.html", {"request": request, "script": script}
      )
  
  def createApp():
      return pn.pane.Markdown("Hello World!").servable()
  
  pn.serve(
      {"/app": createApp},
      port=5000,
      allow_websocket_origin=["*"],
      address="127.0.0.1",
      show=False,
  )
  ```
  - `base.html` template:
  ```
  <!DOCTYPE html>
  <html>
      <head>
          <title>Panel in FastAPI</title>
      </head>
      <body>
          {{ script|safe }}
      </body>
  </html>
  ```
  - Run with `uvicorn --port 8000 main_fastapi:app --host localhost`

#### Stack traceback and/or browser JavaScript console output

```python-traceback
2023-09-13 12:32:18,378 ERROR: panel.reactive - Callback failed for object named "Location00115" changing properties {'href': 'about:srcdoc', 'pathname': 'srcdoc', 'protocol': 'about:'} 
Traceback (most recent call last):
  File "/python_env/lib/python3.11/site-packages/panel/reactive.py", line 384, in _process_events
    self.param.update(**self_events)
  File "/python_env/lib/python3.11/site-packages/param/parameterized.py", line 1895, in update
    setattr(self_or_cls, k, v)
  File "/python_env/lib/python3.11/site-packages/param/parameterized.py", line 367, in _f
    instance_param.__set__(obj, val)
  File "/python_env/lib/python3.11/site-packages/param/parameterized.py", line 369, in _f
    return f(self, obj, val)
           ^^^^^^^^^^^^^^^^^
  File "/python_env/lib/python3.11/site-packages/param/parameterized.py", line 1201, in __set__
    self._validate(val)
  File "/python_env/lib/python3.11/site-packages/param/parameterized.py", line 1350, in _validate
    self._validate_regex(val, self.regex)
  File "/python_env/lib/python3.11/site-packages/param/parameterized.py", line 1338, in _validate_regex
    raise ValueError("String parameter %r value %r does not match regex %r."
ValueError: String parameter 'pathname' value 'srcdoc' does not match regex '^$|[\\/].*$'.
Exception in callback functools.partial(<bound method IOLoop._discard_future_result of <tornado.platform.asyncio.AsyncIOMainLoop object at 0x7fa4a2344990>>, <Task finished name='Task-87' coro=<ServerSession.with_document_locked() done, defined at /python_env/lib/python3.11/site-packages/bokeh/server/session.py:77> exception=ValueError("String parameter 'pathname' value 'srcdoc' does not match regex '^$|[\\\\/].*$'.")>)
Traceback (most recent call last):
  File "/python_env/lib/python3.11/site-packages/tornado/ioloop.py", line 738, in _run_callback
    ret = callback()
          ^^^^^^^^^^
  File "/python_env/lib/python3.11/site-packages/tornado/ioloop.py", line 762, in _discard_future_result
    future.result()
  File "/python_env/lib/python3.11/site-packages/bokeh/server/session.py", line 98, in _needs_document_lock_wrapper
    result = await result
             ^^^^^^^^^^^^
  File "/python_env/lib/python3.11/site-packages/panel/reactive.py", line 429, in _change_coroutine
    state._handle_exception(e)
  File "/python_env/lib/python3.11/site-packages/panel/io/state.py", line 436, in _handle_exception
    raise exception
  File "/python_env/lib/python3.11/site-packages/panel/reactive.py", line 427, in _change_coroutine
    self._change_event(doc)
  File "/python_env/lib/python3.11/site-packages/panel/reactive.py", line 445, in _change_event
    self._process_events(events)
  File "/python_env/lib/python3.11/site-packages/panel/reactive.py", line 384, in _process_events
    self.param.update(**self_events)
  File "/python_env/lib/python3.11/site-packages/param/parameterized.py", line 1895, in update
    setattr(self_or_cls, k, v)
  File "/python_env/lib/python3.11/site-packages/param/parameterized.py", line 367, in _f
    instance_param.__set__(obj, val)
  File "/python_env/lib/python3.11/site-packages/param/parameterized.py", line 369, in _f
    return f(self, obj, val)
           ^^^^^^^^^^^^^^^^^
  File "/python_env/lib/python3.11/site-packages/param/parameterized.py", line 1201, in __set__
    self._validate(val)
  File "/python_env/lib/python3.11/site-packages/param/parameterized.py", line 1350, in _validate
    self._validate_regex(val, self.regex)
  File "/python_env/lib/python3.11/site-packages/param/parameterized.py", line 1338, in _validate_regex
    raise ValueError("String parameter %r value %r does not match regex %r."
ValueError: String parameter 'pathname' value 'srcdoc' does not match regex '^$|[\\/].*$'.
```


#### ALL software version info
- python=3.11.5
- bokeh=3.2.2
- panel=1.2.2
- param=1.13.0
- fastapi=0.103.1
- uvicorn=0.23.2


Discussion:
I experience the same issue when trying to figure out the best way to create a Panel Playground based on Pyodide.

```python
import param
import panel as pn
from pathlib import Path
import base64

html = (Path(__file__).parent/"script.html").read_text(encoding="utf8")
html = base64.b64encode(html.encode("ascii")).decode("ascii")
pn.extension()

class CustomComponent(pn.reactive.ReactiveHTML):
    _template = f"""<iframe id="iframe_el" style="height:100%;width:100%"></iframe>
    """
    html = param.String(html)

    _scripts = {
        "render": f"""
html = atob(data.html)
console.log(html)
iframe_el.srcdoc=html
"""
    }

CustomComponent(width=500, height=200).servable()
```
[script.html.csv](https://github.com/holoviz/panel/files/13198649/script.html.csv)

![image](https://github.com/holoviz/panel/assets/42288570/01098140-12ef-47e6-80c0-03583c6c520b)


## Workaround

If I add

```python
from panel.io.location import Location
Location.param.pathname.regex=r"^$|[\/]|srcdoc$"
```

to the top of the `code` section inside my `script.html` file, it works.

Resolving patch:
diff --git a/panel/io/location.py b/panel/io/location.py
--- a/panel/io/location.py
+++ b/panel/io/location.py
@@ -38,7 +38,7 @@ class Location(Syncable):
     hostname = param.String(readonly=True, doc="""
         hostname in window.location e.g. 'panel.holoviz.org'""")
 
-    pathname = param.String(regex=r"^$|[\/].*$", doc="""
+    pathname = param.String(regex=r"^$|[\/]|srcdoc$", doc="""
         pathname in window.location e.g. '/user_guide/Interact.html'""")
 
     protocol = param.String(readonly=True, doc="""


Test patch:
diff --git a/panel/tests/io/test_location.py b/panel/tests/io/test_location.py
--- a/panel/tests/io/test_location.py
+++ b/panel/tests/io/test_location.py
@@ -161,3 +161,6 @@ def app():
     assert loc.hostname == 'localhost'
     assert loc.pathname == '/'
     assert loc.search == '?foo=1'
+
+def test_iframe_srcdoc_location():
+    Location(pathname="srcdoc")