From 04e6db8ece2120daad2bfa759faa1089257f947b Mon Sep 17 00:00:00 2001 From: Maryna Balioura Date: Mon, 26 Jan 2026 23:11:28 +0100 Subject: [PATCH 1/3] fix(selectors): Make isAfterContentFlowStart flow-aware (skip hidden nodes) --- src/node/modules/selectors.js | 5 +- .../case_010_templates_first.html | 46 +++++++++++++++++++ .../case_011_display_none.html | 35 ++++++++++++++ .../findBetterForcedPageStarter/test_case.py | 15 +++++- 4 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 test/end2end/1400_service_elements/findBetterForcedPageStarter/case_010_templates_first.html create mode 100644 test/end2end/1400_service_elements/findBetterForcedPageStarter/case_011_display_none.html diff --git a/src/node/modules/selectors.js b/src/node/modules/selectors.js index 1b43e99..a284079 100644 --- a/src/node/modules/selectors.js +++ b/src/node/modules/selectors.js @@ -139,7 +139,10 @@ export function isContentFlowStart(element) { * @this {Node} */ export function isAfterContentFlowStart(element) { - const elementBeforeInspected = this._DOM.getLeftNeighbor(element); + let elementBeforeInspected = this._DOM.getLeftNeighbor(element); + while (elementBeforeInspected && this.shouldSkipFlowElement(elementBeforeInspected, { context: 'isAfterContentFlowStart' })) { + elementBeforeInspected = this._DOM.getLeftNeighbor(elementBeforeInspected); + } return this.isSelectorMatching(elementBeforeInspected, this._selector.contentFlowStart) } diff --git a/test/end2end/1400_service_elements/findBetterForcedPageStarter/case_010_templates_first.html b/test/end2end/1400_service_elements/findBetterForcedPageStarter/case_010_templates_first.html new file mode 100644 index 0000000..01121c2 --- /dev/null +++ b/test/end2end/1400_service_elements/findBetterForcedPageStarter/case_010_templates_first.html @@ -0,0 +1,46 @@ + + + + + + + + Test page + + + + + + + + + + + + +
Header1 (break-before) after templates
+
Text 1 (html2pdf4doc-no-hanging)
+
Header2 (break-before)
+
Text 2 (simple)
+ + + + diff --git a/test/end2end/1400_service_elements/findBetterForcedPageStarter/case_011_display_none.html b/test/end2end/1400_service_elements/findBetterForcedPageStarter/case_011_display_none.html new file mode 100644 index 0000000..bcbfd0d --- /dev/null +++ b/test/end2end/1400_service_elements/findBetterForcedPageStarter/case_011_display_none.html @@ -0,0 +1,35 @@ + + + + + + + + Test page + + + + + + + +
Text 0 (simple)
+ +
Header1 (break-before) after display:none
+
Text 1 (html2pdf4doc-no-hanging)
+
Header2 (break-before)
+
Text 2 (simple)
+ + + + diff --git a/test/end2end/1400_service_elements/findBetterForcedPageStarter/test_case.py b/test/end2end/1400_service_elements/findBetterForcedPageStarter/test_case.py index 485661f..5a52c40 100644 --- a/test/end2end/1400_service_elements/findBetterForcedPageStarter/test_case.py +++ b/test/end2end/1400_service_elements/findBetterForcedPageStarter/test_case.py @@ -1,6 +1,5 @@ import os -from selenium.webdriver.common.by import By from seleniumbase import BaseCase from test.end2end.helpers.helper import Helper @@ -47,3 +46,17 @@ def test_005(self): self.helper.assert_document_has_pages(3) self.helper.assert_element_on_the_page(h1, 2) self.helper.assert_element_on_the_page(h2, 3) + + def test_010(self): + # Templates at the beginning of the BODY should not be counted, + # and H1 should be considered the first + # (and should not receive an additional break before it). + self.helper.open_case(path_to_this_test_file_folder, '010_templates_first') + self.helper.assert_document_has_pages(3) + self.helper.assert_element_on_the_page(h1, 2) + + def test_011(self): + # display:none at the beginning of the BODY should not be counted. + self.helper.open_case(path_to_this_test_file_folder, '011_display_none') + self.helper.assert_document_has_pages(2) + self.helper.assert_element_on_the_page(h1, 1) From 203834aa81d3cfadb79dfa2c977a56eceb98c77b Mon Sep 17 00:00:00 2001 From: Maryna Balioura Date: Mon, 26 Jan 2026 23:24:16 +0100 Subject: [PATCH 2/3] feat(flowFilters): Skip non-rendered tags in shouldSkipFlowElement --- src/node/modules/flowfilters.js | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/node/modules/flowfilters.js b/src/node/modules/flowfilters.js index 3bacef8..84b3dbd 100644 --- a/src/node/modules/flowfilters.js +++ b/src/node/modules/flowfilters.js @@ -37,6 +37,18 @@ const SKIP_RULES = [ }, ]; +const SKIP_TAGS = new Set([ + 'SOURCE', + 'TEMPLATE', + 'SCRIPT', + 'NOSCRIPT', + 'STYLE', + 'LINK', + 'META', + 'HEAD', + 'TITLE', +]); + function logSkip(node, context, cache, element, { cached } = { cached: false }) { if (!_isDebug(node)) return; const prefix = context ? `(${context}) ` : ''; @@ -49,22 +61,25 @@ export function shouldSkipFlowElement(element, { context = '', computedStyle } = return false; } + // * cached result const cached = element[FLOW_SKIP_FLAG]; if (cached) { logSkip(this, context, cached, element, { cached: true }); return true; } - if (this._DOM.getElementTagName(element) === 'SOURCE') { - logSkip(this, context, "ignore ", element); + // * by TAG name + const tagName = this._DOM.getElementTagName(element); + if (SKIP_TAGS.has(tagName)) { + logSkip(this, context, { message: `* <${tagName}> — skipped` }, element); return true; } + // * by CSS styles const style = computedStyle ?? this._DOM.getComputedStyle(element); if (!style) { return false; } - for (const rule of SKIP_RULES) { if (rule.test(style)) { element[FLOW_SKIP_FLAG] = rule.cache; From 6f0f7dc3286b249f2149c31d43195d9a51fc06dd Mon Sep 17 00:00:00 2001 From: Maryna Balioura Date: Mon, 26 Jan 2026 23:25:17 +0100 Subject: [PATCH 3/3] fix(flowFilters): Fix debug group name --- src/node/modules/flowfilters.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node/modules/flowfilters.js b/src/node/modules/flowfilters.js index 84b3dbd..56430dd 100644 --- a/src/node/modules/flowfilters.js +++ b/src/node/modules/flowfilters.js @@ -1,7 +1,7 @@ // ♻️ flowfilters import { debugFor } from '../utils/debugFor.js'; -const _isDebug = debugFor('flowfilters'); +const _isDebug = debugFor('flowFilters'); // Cache marker so each element is evaluated at most once per run. const FLOW_SKIP_FLAG = '__html2pdf4docFlowFilter';