From c4cc9fb214f072b20f0bd616588433ee156da11b Mon Sep 17 00:00:00 2001 From: Morten Stenshorne Date: Wed, 25 Feb 2026 08:20:24 +0100 Subject: [PATCH 1/9] Add plumbing for safe printable inset to wptrunner and webdriver. For print reftests, check to simulate an unprintable region along the paper edges. The `content` attribute takes a non-negative number, which is the safe printable inset specified in centimeters. Only implemented for executorwebdriver. Spec discussion: https://github.com/w3c/csswg-drafts/issues/11395 Spec change: https://github.com/w3c/csswg-drafts/pull/13190 RFC: https://github.com/web-platform-tests/rfcs/pull/233 For background, see https://drafts.csswg.org/css-page-3/#printable-area --- tools/manifest/item.py | 6 ++++++ tools/manifest/sourcefile.py | 9 +++++++++ tools/webdriver/webdriver/client.py | 2 ++ tools/wptrunner/wptrunner/executors/base.py | 19 ++++++++++--------- .../wptrunner/executors/executoredge.py | 4 ++-- .../wptrunner/executors/executormarionette.py | 10 +++++----- .../wptrunner/executors/executorselenium.py | 2 +- .../executors/executorservolegacy.py | 2 +- .../wptrunner/executors/executorwebdriver.py | 10 ++++++---- .../wptrunner/wptrunner/executors/protocol.py | 2 +- tools/wptrunner/wptrunner/wpttest.py | 11 ++++++++++- 11 files changed, 53 insertions(+), 24 deletions(-) diff --git a/tools/manifest/item.py b/tools/manifest/item.py index 99df09d1320dc9..e905861a8fcd0f 100644 --- a/tools/manifest/item.py +++ b/tools/manifest/item.py @@ -293,10 +293,16 @@ class PrintRefTest(RefTest): def page_ranges(self) -> PageRanges: return cast(PageRanges, self._extras.get("page_ranges", {})) + @property + def safe_printable_inset(self) -> float: + return self._extras.get("safe_printable_inset") + def to_json(self): # type: ignore rv = super().to_json() if self.page_ranges: rv[-1]["page_ranges"] = self.page_ranges + if self.safe_printable_inset: + rv[-1]["safe_printable_inset"] = self.safe_printable_inset return rv diff --git a/tools/manifest/sourcefile.py b/tools/manifest/sourcefile.py index 9e8d8302f6a568..6aa9ea83967981 100644 --- a/tools/manifest/sourcefile.py +++ b/tools/manifest/sourcefile.py @@ -675,6 +675,14 @@ def page_ranges(self) -> Dict[Text, List[List[Optional[int]]]]: rv[key].append(range_parts) return rv + @cached_property + def safe_printable_inset(self) -> Optional[float]: + """The safe printable inset to simulate (in centimeters)""" + for entry in self.root.findall(".//{http://www.w3.org/1999/xhtml}meta[@name='safe-printable-inset']"): + key_data, value = self.parse_ref_keyed_meta(entry) + return float(value) + return None + @cached_property def testharness_nodes(self) -> List[ElementTree.Element]: """List of ElementTree Elements corresponding to nodes representing a @@ -1023,6 +1031,7 @@ def manifest_items(self) -> Tuple[Text, List[ManifestItem]]: viewport_size=self.viewport_size, fuzzy=self.fuzzy, page_ranges=self.page_ranges, + safe_printable_inset=self.safe_printable_inset, testdriver=self.has_testdriver, )] diff --git a/tools/webdriver/webdriver/client.py b/tools/webdriver/webdriver/client.py index 8fcb663398dde1..28c10d98ffb36e 100644 --- a/tools/webdriver/webdriver/client.py +++ b/tools/webdriver/webdriver/client.py @@ -791,6 +791,7 @@ def print( orientation=None, page=None, page_ranges=None, + safe_printable_inset=None, scale=None, shrink_to_fit=None, ): @@ -801,6 +802,7 @@ def print( "orientation": orientation, "page": page, "pageRanges": page_ranges, + "safePrintableInset": safe_printable_inset, "scale": scale, "shrinkToFit": shrink_to_fit, }.items(): diff --git a/tools/wptrunner/wptrunner/executors/base.py b/tools/wptrunner/wptrunner/executors/base.py index dd10aca589c72f..6af323f5d47e9d 100644 --- a/tools/wptrunner/wptrunner/executors/base.py +++ b/tools/wptrunner/wptrunner/executors/base.py @@ -418,11 +418,11 @@ def teardown(self): def logger(self): return self.executor.logger - def get_hash(self, test, viewport_size, dpi, page_ranges): + def get_hash(self, test, viewport_size, dpi, page_ranges, safe_printable_inset): key = (test.url, viewport_size, dpi) if key not in self.screenshot_cache: - success, data = self.get_screenshot_list(test, viewport_size, dpi, page_ranges) + success, data = self.get_screenshot_list(test, viewport_size, dpi, page_ranges, safe_printable_inset) if not success: return False, data @@ -524,9 +524,9 @@ def run_test(self, test): viewport_size = test.viewport_size dpi = test.dpi page_ranges = test.page_ranges + safe_printable_inset = test.safe_printable_inset self.message = [] - # Depth-first search of reference tree, with the goal # of reachings a leaf node with only pass results @@ -541,7 +541,7 @@ def run_test(self, test): fuzzy = self.get_fuzzy(test, nodes, relation) for i, node in enumerate(nodes): - success, data = self.get_hash(node, viewport_size, dpi, page_ranges) + success, data = self.get_hash(node, viewport_size, dpi, page_ranges, safe_printable_inset) if success is False: return {"status": data[0], "message": data[1]} @@ -575,7 +575,7 @@ def run_test(self, test): for i, (node, screenshot) in enumerate(zip(nodes, screenshots)): if screenshot is None: - success, screenshot = self.retake_screenshot(node, viewport_size, dpi, page_ranges) + success, screenshot = self.retake_screenshot(node, viewport_size, dpi, page_ranges, safe_printable_inset) if success: screenshots[i] = screenshot @@ -606,11 +606,12 @@ def get_fuzzy(self, root_test, test_nodes, relation): break return value - def retake_screenshot(self, node, viewport_size, dpi, page_ranges): + def retake_screenshot(self, node, viewport_size, dpi, page_ranges, safe_printable_inset): success, data = self.get_screenshot_list(node, viewport_size, dpi, - page_ranges) + page_ranges, + safe_printable_inset) if not success: return False, data @@ -641,8 +642,8 @@ def get_png_dimensions( return struct.unpack(">LL", image_data[16:24]) - def get_screenshot_list(self, node, viewport_size, dpi, page_ranges): - success, data = self.executor.screenshot(node, viewport_size, dpi, page_ranges) + def get_screenshot_list(self, node, viewport_size, dpi, page_ranges, safe_printable_inset): + success, data = self.executor.screenshot(node, viewport_size, dpi, page_ranges, safe_printable_inset) viewport_size = (800, 600) if viewport_size is None else viewport_size dpi = 96 if dpi is None else dpi dpcm = dpi / 2.54 diff --git a/tools/wptrunner/wptrunner/executors/executoredge.py b/tools/wptrunner/wptrunner/executors/executoredge.py index 2ddfdb3ad8f47a..3b1e3a28c6ba24 100644 --- a/tools/wptrunner/wptrunner/executors/executoredge.py +++ b/tools/wptrunner/wptrunner/executors/executoredge.py @@ -35,7 +35,7 @@ def setup(self, runner, protocol=None): with open(os.path.join(here, "reftest.js")) as f: self.script = f.read() - def screenshot(self, test, viewport_size, dpi, page_ranges): + def screenshot(self, test, viewport_size, dpi, page_ranges, safe_printable_inset): # https://github.com/web-platform-tests/wpt/issues/7140 assert dpi is None @@ -62,7 +62,7 @@ def _render(self, protocol, url, timeout): protocol.base.execute_script(self.wait_script, asynchronous=True) - pdf = protocol.pdf_print.render_as_pdf(*self.viewport_size) + pdf = protocol.pdf_print.render_as_pdf(*self.viewport_size, self.safe_printable_inset) screenshots = protocol.pdf_print.pdf_to_png(pdf, self.page_ranges) for i, screenshot in enumerate(screenshots): # strip off the data:img/png, part of the url diff --git a/tools/wptrunner/wptrunner/executors/executormarionette.py b/tools/wptrunner/wptrunner/executors/executormarionette.py index ce9462f0523317..fb916bcf4b0095 100644 --- a/tools/wptrunner/wptrunner/executors/executormarionette.py +++ b/tools/wptrunner/wptrunner/executors/executormarionette.py @@ -683,7 +683,7 @@ def load_runner(self): raise self.runner_handle = self.marionette.current_window_handle - def render_as_pdf(self, width, height): + def render_as_pdf(self, width, height, safe_printable_inset): margin = 0.5 * 2.54 body = { "page": { @@ -701,7 +701,7 @@ def render_as_pdf(self, width, height): } return self.marionette._send_message("WebDriver:Print", body, key="value") - def pdf_to_png(self, pdf_base64, page_ranges): + def pdf_to_png(self, pdf_base64, page_ranges, safe_printable_inset): handle = self.marionette.current_window_handle _switch_to_window(self.marionette, self.runner_handle) try: @@ -1227,7 +1227,7 @@ def do_test(self, test): return self.convert_result(test, result) - def screenshot(self, test, viewport_size, dpi, page_ranges): + def screenshot(self, test, viewport_size, dpi, page_ranges, safe_printable_inset): # https://github.com/web-platform-tests/wpt/issues/7135 assert viewport_size is None assert dpi is None @@ -1431,7 +1431,7 @@ def setup(self, runner, protocol=None): if not isinstance(self.implementation, InternalRefTestImplementation): self.protocol.pdf_print.load_runner() - def screenshot(self, test, viewport_size, dpi, page_ranges): + def screenshot(self, test, viewport_size, dpi, page_ranges, safe_printable_inset): # https://github.com/web-platform-tests/wpt/issues/7140 assert dpi is None @@ -1453,7 +1453,7 @@ def _render(self, protocol, url, timeout): protocol.base.execute_script(self.wait_script, asynchronous=True) - pdf = protocol.pdf_print.render_as_pdf(*self.viewport_size) + pdf = protocol.pdf_print.render_as_pdf(*self.viewport_size, self.safe_printable_inset) screenshots = protocol.pdf_print.pdf_to_png(pdf, self.page_ranges) for i, screenshot in enumerate(screenshots): # strip off the data:img/png, part of the url diff --git a/tools/wptrunner/wptrunner/executors/executorselenium.py b/tools/wptrunner/wptrunner/executors/executorselenium.py index 0080367f6ed194..73f041e15bc069 100644 --- a/tools/wptrunner/wptrunner/executors/executorselenium.py +++ b/tools/wptrunner/wptrunner/executors/executorselenium.py @@ -442,7 +442,7 @@ def do_test(self, test): return self.convert_result(test, result) - def screenshot(self, test, viewport_size, dpi, page_ranges): + def screenshot(self, test, viewport_size, dpi, page_ranges, safe_printable_inset): # https://github.com/web-platform-tests/wpt/issues/7135 assert viewport_size is None assert dpi is None diff --git a/tools/wptrunner/wptrunner/executors/executorservolegacy.py b/tools/wptrunner/wptrunner/executors/executorservolegacy.py index a03c2d4ee6fff5..8118845b96d7da 100644 --- a/tools/wptrunner/wptrunner/executors/executorservolegacy.py +++ b/tools/wptrunner/wptrunner/executors/executorservolegacy.py @@ -244,7 +244,7 @@ def teardown(self): os.rmdir(self.tempdir) super().teardown() - def screenshot(self, test, viewport_size, dpi, page_ranges): + def screenshot(self, test, viewport_size, dpi, page_ranges, safe_printable_inset): with TempFilename(self.tempdir) as output_path: extra_args = ["--exit", "--output=%s" % output_path, diff --git a/tools/wptrunner/wptrunner/executors/executorwebdriver.py b/tools/wptrunner/wptrunner/executors/executorwebdriver.py index 808d96f61d00e7..47d7343a44dd17 100644 --- a/tools/wptrunner/wptrunner/executors/executorwebdriver.py +++ b/tools/wptrunner/wptrunner/executors/executorwebdriver.py @@ -519,7 +519,7 @@ def load_runner(self): raise self.runner_handle = self.webdriver.window_handle - def render_as_pdf(self, width, height): + def render_as_pdf(self, width, height, safe_printable_inset_param): # All units passed to `print()` are in cm. See [0] for testing specifications. # # [0]: https://web-platform-tests.org/writing-tests/print-reftests.html @@ -527,6 +527,7 @@ def render_as_pdf(self, width, height): pdf_base64 = self.webdriver.print(page={"width": width, "height": height}, margin={"top": margin, "right": margin, "bottom": margin, "left": margin}, + safe_printable_inset=safe_printable_inset_param, background=True, shrink_to_fit=False) return pdf_base64 @@ -1383,7 +1384,7 @@ def do_test(self, test): return self.convert_result(test, result) - def screenshot(self, test, viewport_size, dpi, page_ranges): + def screenshot(self, test, viewport_size, dpi, page_ranges, safe_printable_inset): # https://github.com/web-platform-tests/wpt/issues/7135 assert viewport_size is None assert dpi is None @@ -1423,7 +1424,7 @@ def setup(self, runner, protocol=None): with open(os.path.join(here, "reftest.js")) as f: self.script = f.read() - def screenshot(self, test, viewport_size, dpi, page_ranges): + def screenshot(self, test, viewport_size, dpi, page_ranges, safe_printable_inset): # https://github.com/web-platform-tests/wpt/issues/7140 assert dpi is None @@ -1434,6 +1435,7 @@ def screenshot(self, test, viewport_size, dpi, page_ranges): self.viewport_size = viewport_size self.page_ranges = page_ranges.get(test.url) + self.safe_printable_inset = safe_printable_inset timeout = self.timeout_multiplier * test.timeout if self.debug_info is None else None test_url = self.test_url(test) @@ -1449,7 +1451,7 @@ def _render(self, protocol, url, timeout): # return value. protocol.testdriver.run(url, self.wait_script) - pdf = protocol.pdf_print.render_as_pdf(*self.viewport_size) + pdf = protocol.pdf_print.render_as_pdf(*self.viewport_size, self.safe_printable_inset) screenshots = protocol.pdf_print.pdf_to_png(pdf, self.page_ranges) for i, screenshot in enumerate(screenshots): # strip off the data:img/png, part of the url diff --git a/tools/wptrunner/wptrunner/executors/protocol.py b/tools/wptrunner/wptrunner/executors/protocol.py index b7dfac9de960dc..53744970b0983d 100644 --- a/tools/wptrunner/wptrunner/executors/protocol.py +++ b/tools/wptrunner/wptrunner/executors/protocol.py @@ -1134,7 +1134,7 @@ class PrintProtocolPart(ProtocolPart): name = "pdf_print" @abstractmethod - def render_as_pdf(self, width, height): + def render_as_pdf(self, width, height, safe_printable_inset): """Output document as PDF""" pass diff --git a/tools/wptrunner/wptrunner/wpttest.py b/tools/wptrunner/wptrunner/wpttest.py index 9616f9c429f309..0ba61e5803801d 100644 --- a/tools/wptrunner/wptrunner/wpttest.py +++ b/tools/wptrunner/wptrunner/wpttest.py @@ -699,22 +699,28 @@ def fuzzy_override(self): def page_ranges(self): return {} + @property + def safe_printable_inset(self): + return 0 class PrintReftestTest(ReftestTest): test_type = "print-reftest" def __init__(self, url_base, tests_root, url, inherit_metadata, test_metadata, references, timeout=None, path=None, viewport_size=None, dpi=None, fuzzy=None, - page_ranges=None, protocol="http", subdomain=False, testdriver=False): + page_ranges=None, safe_printable_inset=None, protocol="http", subdomain=False, + testdriver=False): super().__init__(url_base, tests_root, url, inherit_metadata, test_metadata, references, timeout, path, viewport_size, dpi, fuzzy, protocol, subdomain=subdomain, testdriver=testdriver) self._page_ranges = page_ranges + self._safe_printable_inset = safe_printable_inset @classmethod def cls_kwargs(cls, manifest_test): rv = super().cls_kwargs(manifest_test) rv["page_ranges"] = manifest_test.page_ranges + rv["safe_printable_inset"] = manifest_test.safe_printable_inset return rv def get_viewport_size(self, override): @@ -725,6 +731,9 @@ def get_viewport_size(self, override): def page_ranges(self): return self._page_ranges + @property + def safe_printable_inset(self): + return self._safe_printable_inset class WdspecTest(Test): result_cls = WdspecResult From 8262f8417f9b3fea5819518070f79dacd91361b9 Mon Sep 17 00:00:00 2001 From: Morten Stenshorne Date: Wed, 25 Feb 2026 11:59:13 +0100 Subject: [PATCH 2/9] Fix errors --- tools/manifest/item.py | 2 +- tools/manifest/sourcefile.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/manifest/item.py b/tools/manifest/item.py index e905861a8fcd0f..47b9acd37ed15d 100644 --- a/tools/manifest/item.py +++ b/tools/manifest/item.py @@ -295,7 +295,7 @@ def page_ranges(self) -> PageRanges: @property def safe_printable_inset(self) -> float: - return self._extras.get("safe_printable_inset") + return cast(float, self._extras.get("safe_printable_inset")) def to_json(self): # type: ignore rv = super().to_json() diff --git a/tools/manifest/sourcefile.py b/tools/manifest/sourcefile.py index 6aa9ea83967981..823b7945654f65 100644 --- a/tools/manifest/sourcefile.py +++ b/tools/manifest/sourcefile.py @@ -678,6 +678,7 @@ def page_ranges(self) -> Dict[Text, List[List[Optional[int]]]]: @cached_property def safe_printable_inset(self) -> Optional[float]: """The safe printable inset to simulate (in centimeters)""" + assert self.root is not None for entry in self.root.findall(".//{http://www.w3.org/1999/xhtml}meta[@name='safe-printable-inset']"): key_data, value = self.parse_ref_keyed_meta(entry) return float(value) From 43a335aee9f2e92a6777f87421263c10b04a17a5 Mon Sep 17 00:00:00 2001 From: Morten Stenshorne Date: Thu, 5 Mar 2026 08:33:57 +0100 Subject: [PATCH 3/9] Update tools/manifest/item.py Co-authored-by: Jonathan Lee --- tools/manifest/item.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/manifest/item.py b/tools/manifest/item.py index 47b9acd37ed15d..7bd857dbce98e0 100644 --- a/tools/manifest/item.py +++ b/tools/manifest/item.py @@ -294,8 +294,8 @@ def page_ranges(self) -> PageRanges: return cast(PageRanges, self._extras.get("page_ranges", {})) @property - def safe_printable_inset(self) -> float: - return cast(float, self._extras.get("safe_printable_inset")) + def safe_printable_inset(self) -> Optional[float]: + return self._extras.get("safe_printable_inset") def to_json(self): # type: ignore rv = super().to_json() From a424f533b02d735b73dc4bb28046a55ab2ad25c5 Mon Sep 17 00:00:00 2001 From: Morten Stenshorne Date: Thu, 5 Mar 2026 08:34:24 +0100 Subject: [PATCH 4/9] Update tools/wptrunner/wptrunner/executors/base.py Co-authored-by: Jonathan Lee --- tools/wptrunner/wptrunner/executors/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/wptrunner/wptrunner/executors/base.py b/tools/wptrunner/wptrunner/executors/base.py index 6af323f5d47e9d..595fe6c39e68fc 100644 --- a/tools/wptrunner/wptrunner/executors/base.py +++ b/tools/wptrunner/wptrunner/executors/base.py @@ -419,7 +419,7 @@ def logger(self): return self.executor.logger def get_hash(self, test, viewport_size, dpi, page_ranges, safe_printable_inset): - key = (test.url, viewport_size, dpi) + key = (test.url, viewport_size, dpi, safe_printable_inset) if key not in self.screenshot_cache: success, data = self.get_screenshot_list(test, viewport_size, dpi, page_ranges, safe_printable_inset) From f9fdff9408ec9346be2218d3cd3904a8f120bdba Mon Sep 17 00:00:00 2001 From: Morten Stenshorne Date: Thu, 5 Mar 2026 10:15:44 +0100 Subject: [PATCH 5/9] Add test --- tools/manifest/tests/test_sourcefile.py | 26 +++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tools/manifest/tests/test_sourcefile.py b/tools/manifest/tests/test_sourcefile.py index e77f09e2a79f99..6303bf21b619da 100644 --- a/tools/manifest/tests/test_sourcefile.py +++ b/tools/manifest/tests/test_sourcefile.py @@ -973,6 +973,32 @@ def test_page_ranges_invalid(page_ranges): s.page_ranges +@pytest.mark.parametrize("safe_printable_area, expected", [ + (b"0", 0), + (b"0.5", 0.5), + (b"2.54", 2.54), + (b"100", 100)]) +def test_safe_printable_area(safe_printable_area, expected): + content = b""" + +""" % safe_printable_area + + s = create("foo/test-print.html", content) + + assert s.safe_printable_area == {"/foo/test-print.html": expected} + + +@pytest.mark.parametrize("safe_printable_area", [b"banana", b"auto", b"-1", b"0,0"]) +def test_safe_printable_area_invalid(safe_printable_area): + content = b""" + +""" % safe_printable_area + + s = create("foo/test-print.html", content) + with pytest.raises(ValueError): + s.safe_printable_area + + def test_hash(): s = SourceFile("/", "foo", "/", contents=b"Hello, World!") assert "b45ef6fec89518d314f546fd6c3025367b721684" == s.hash From a2f503744a51935553cce7ce1a5af1e07a6317f2 Mon Sep 17 00:00:00 2001 From: Morten Stenshorne Date: Thu, 5 Mar 2026 10:28:42 +0100 Subject: [PATCH 6/9] Typo. Since I don't know how to run this locally, here's more fun for you bots. --- tools/manifest/tests/test_sourcefile.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tools/manifest/tests/test_sourcefile.py b/tools/manifest/tests/test_sourcefile.py index 6303bf21b619da..286b8b54db2f1e 100644 --- a/tools/manifest/tests/test_sourcefile.py +++ b/tools/manifest/tests/test_sourcefile.py @@ -973,30 +973,30 @@ def test_page_ranges_invalid(page_ranges): s.page_ranges -@pytest.mark.parametrize("safe_printable_area, expected", [ +@pytest.mark.parametrize("safe_printable_inset, expected", [ (b"0", 0), (b"0.5", 0.5), (b"2.54", 2.54), (b"100", 100)]) -def test_safe_printable_area(safe_printable_area, expected): +def test_safe_printable_inset(safe_printable_inset, expected): content = b""" -""" % safe_printable_area +""" % safe_printable_inset s = create("foo/test-print.html", content) - assert s.safe_printable_area == {"/foo/test-print.html": expected} + assert s.safe_printable_inset == {"/foo/test-print.html": expected} -@pytest.mark.parametrize("safe_printable_area", [b"banana", b"auto", b"-1", b"0,0"]) -def test_safe_printable_area_invalid(safe_printable_area): +@pytest.mark.parametrize("safe_printable_inset", [b"banana", b"auto", b"-1", b"0,0"]) +def test_safe_printable_inset_invalid(safe_printable_inset): content = b""" -""" % safe_printable_area +""" % safe_printable_inset s = create("foo/test-print.html", content) with pytest.raises(ValueError): - s.safe_printable_area + s.safe_printable_inset def test_hash(): From 0b78e10e7858008dc70254f060751c7635dbb2ee Mon Sep 17 00:00:00 2001 From: Morten Stenshorne Date: Thu, 5 Mar 2026 11:35:55 +0100 Subject: [PATCH 7/9] Dull --- tools/manifest/tests/test_sourcefile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/manifest/tests/test_sourcefile.py b/tools/manifest/tests/test_sourcefile.py index 286b8b54db2f1e..c9d60532612f58 100644 --- a/tools/manifest/tests/test_sourcefile.py +++ b/tools/manifest/tests/test_sourcefile.py @@ -980,7 +980,7 @@ def test_page_ranges_invalid(page_ranges): (b"100", 100)]) def test_safe_printable_inset(safe_printable_inset, expected): content = b""" - + """ % safe_printable_inset s = create("foo/test-print.html", content) @@ -991,7 +991,7 @@ def test_safe_printable_inset(safe_printable_inset, expected): @pytest.mark.parametrize("safe_printable_inset", [b"banana", b"auto", b"-1", b"0,0"]) def test_safe_printable_inset_invalid(safe_printable_inset): content = b""" - + """ % safe_printable_inset s = create("foo/test-print.html", content) From 6989d71aff78ab9d760db89a37ac4f97dd1ab2eb Mon Sep 17 00:00:00 2001 From: Morten Stenshorne Date: Thu, 5 Mar 2026 12:19:24 +0100 Subject: [PATCH 8/9] Throw error on negative values. --- tools/manifest/sourcefile.py | 5 ++++- tools/manifest/tests/test_sourcefile.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/manifest/sourcefile.py b/tools/manifest/sourcefile.py index 823b7945654f65..efca597a4fc2f6 100644 --- a/tools/manifest/sourcefile.py +++ b/tools/manifest/sourcefile.py @@ -681,7 +681,10 @@ def safe_printable_inset(self) -> Optional[float]: assert self.root is not None for entry in self.root.findall(".//{http://www.w3.org/1999/xhtml}meta[@name='safe-printable-inset']"): key_data, value = self.parse_ref_keyed_meta(entry) - return float(value) + result = float(value) + if result < 0: + raise ValueError("Negative value") + return result return None @cached_property diff --git a/tools/manifest/tests/test_sourcefile.py b/tools/manifest/tests/test_sourcefile.py index c9d60532612f58..ff145a2ebf884c 100644 --- a/tools/manifest/tests/test_sourcefile.py +++ b/tools/manifest/tests/test_sourcefile.py @@ -988,7 +988,7 @@ def test_safe_printable_inset(safe_printable_inset, expected): assert s.safe_printable_inset == {"/foo/test-print.html": expected} -@pytest.mark.parametrize("safe_printable_inset", [b"banana", b"auto", b"-1", b"0,0"]) +@pytest.mark.parametrize("safe_printable_inset", [b"ananas", b"auto", b"-1", b"0,0"]) def test_safe_printable_inset_invalid(safe_printable_inset): content = b""" From a35d493d051cdad438e58d6645c39660102288a4 Mon Sep 17 00:00:00 2001 From: Morten Stenshorne Date: Thu, 5 Mar 2026 12:34:50 +0100 Subject: [PATCH 9/9] Ok... maybe this? --- tools/manifest/tests/test_sourcefile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/manifest/tests/test_sourcefile.py b/tools/manifest/tests/test_sourcefile.py index ff145a2ebf884c..bc120bae9f9bde 100644 --- a/tools/manifest/tests/test_sourcefile.py +++ b/tools/manifest/tests/test_sourcefile.py @@ -985,7 +985,7 @@ def test_safe_printable_inset(safe_printable_inset, expected): s = create("foo/test-print.html", content) - assert s.safe_printable_inset == {"/foo/test-print.html": expected} + assert s.safe_printable_inset == expected @pytest.mark.parametrize("safe_printable_inset", [b"ananas", b"auto", b"-1", b"0,0"])