From 4e8d00f8012eda5916c9b74efd23e57ade1bb969 Mon Sep 17 00:00:00 2001 From: hksamm Date: Sun, 23 Aug 2026 02:26:23 +0900 Subject: [PATCH 1/2] Fix malformed fullscreen attributes on Figure iframe with a height Figure._repr_html_ has two iframe templates. The one used when height is None emits the bare boolean attributes allowfullscreen webkitallowfullscreen mozallowfullscreen but the one used when a height is set wrapped each in quotes: "allowfullscreen" "webkitallowfullscreen" "mozallowfullscreen" so the attribute names came out with literal quote characters around them and browsers ignored all three -- fullscreen did not work whenever a height was given. The same branch was also missing the space before the style attribute, giving height="..."style="...". Emit the attributes the same way as the height-less branch. Output is now identical to that branch apart from the width/height. Co-Authored-By: Claude Opus 5 --- branca/element.py | 4 ++-- tests/test_element.py | 44 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 tests/test_element.py diff --git a/branca/element.py b/branca/element.py index 853ca16..e6f0571 100644 --- a/branca/element.py +++ b/branca/element.py @@ -425,9 +425,9 @@ def _repr_html_(self, **kwargs) -> str: ).format(html=html, width=self.width, ratio=self.ratio) else: iframe = ( - '" ).format(html=html, width=self.width, height=self.height) return iframe diff --git a/tests/test_element.py b/tests/test_element.py new file mode 100644 index 0000000..846619f --- /dev/null +++ b/tests/test_element.py @@ -0,0 +1,44 @@ +""" +Tests for branca.element +------------------------ +""" + +from html.parser import HTMLParser + +import branca.element as elem + + +class _IframeAttrs(HTMLParser): + """Collect the attributes of every " "" - ).format(html=html, width=self.width, ratio=self.ratio) + ).format( + html=html, + width=self.width, + ratio=self.ratio, + title_attr=title_attr, + ) else: iframe = ( - '" - ).format(html=html, width=self.width, height=self.height) + ).format( + html=html, + width=self.width, + height=self.height, + title_attr=title_attr, + ) return iframe def add_subplot(self, x: int, y: int, n: int, margin: float = 0.05) -> "Div": @@ -640,6 +654,10 @@ class IFrame(Element): height. Values will be converted into pixels in using 60 dpi. For example figsize=(10, 5) will result in width="600px", height="300px". + title : str, default None + Value for the iframe's ``title`` attribute, used as the frame's + accessible name. Set it to satisfy accessibility audits that + require every frame to have an accessible name. """ def __init__( @@ -649,10 +667,12 @@ def __init__( height: Optional[str] = None, ratio: str = "60%", figsize: Optional[Tuple[int, int]] = None, + title: Optional[str] = None, ): super().__init__() self._name = "IFrame" + self.title = title self.width = width self.height = height self.ratio = ratio @@ -671,21 +691,32 @@ def render(self, **kwargs) -> str: html = "data:text/html;charset=utf-8;base64," + base64.b64encode( html.encode("utf8"), ).decode("utf8") + title_attr = f' title="{escape(self.title)}"' if self.title else "" if self.height is None: iframe = ( '
' '
' # noqa - '" "
" - ).format(html=html, width=self.width, ratio=self.ratio) + ).format( + html=html, + width=self.width, + ratio=self.ratio, + title_attr=title_attr, + ) else: iframe = ( - '' - ).format(html=html, width=self.width, height=self.height) + ).format( + html=html, + width=self.width, + height=self.height, + title_attr=title_attr, + ) return iframe diff --git a/tests/test_element.py b/tests/test_element.py index 846619f..934703a 100644 --- a/tests/test_element.py +++ b/tests/test_element.py @@ -42,3 +42,35 @@ def test_figure_repr_html_fullscreen_attrs_with_height(): assert name in attrs, f"{name!r} missing; got {sorted(attrs)}" assert attrs["height"] == "400px" assert attrs["width"] == "100%" + + +def test_figure_iframe_title_absent_by_default(): + for height in (None, "400px"): + attrs = _iframe_attrs(elem.Figure(height=height)._repr_html_()) + assert "title" not in attrs + + +def test_figure_iframe_title_set(): + for height in (None, "400px"): + attrs = _iframe_attrs(elem.Figure(height=height, title="My Map")._repr_html_()) + assert attrs["title"] == "My Map" + + +def test_figure_iframe_title_is_escaped(): + # A title with a double quote must not break out of the attribute. + attrs = _iframe_attrs(elem.Figure(title='a "b" ')._repr_html_()) + assert attrs["title"] == 'a "b" ' + + +def test_iframe_title_absent_by_default(): + for height in (None, "300px"): + attrs = _iframe_attrs(elem.IFrame("

x

", height=height).render()) + assert "title" not in attrs + + +def test_iframe_title_set(): + for height in (None, "300px"): + attrs = _iframe_attrs( + elem.IFrame("

x

", height=height, title="Popup").render(), + ) + assert attrs["title"] == "Popup"