From 345b5e0bc97d10e97e32d6cd5288a8f0fb3b071b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20V=C3=A4nttinen?= Date: Tue, 2 Sep 2025 15:14:48 +0300 Subject: [PATCH] Fix topmost input element check with labels (#2667) Fix topmost input element check with labels --- .eslintrc | 1 + keepassxc-browser/common/global.js | 6 ++++-- keepassxc-browser/content/fields.js | 21 ++++++++++++++++----- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/.eslintrc b/.eslintrc index 967b08d..c194a97 100644 --- a/.eslintrc +++ b/.eslintrc @@ -124,6 +124,7 @@ "IMPROVED_DETECTION_PREDEFINED_SITELIST": "readonly", "initColorTheme": "readonly", "isEdge": "readonly", + "isElementInside": "readonly", "isFirefox": "readonly", "keepass": "readonly", "keepassClient": "readonly", diff --git a/keepassxc-browser/common/global.js b/keepassxc-browser/common/global.js index 36f3e1c..7b70890 100755 --- a/keepassxc-browser/common/global.js +++ b/keepassxc-browser/common/global.js @@ -182,11 +182,13 @@ const getCurrentTab = async function() { return tabs?.length > 0 ? tabs[0] : undefined; }; +// Check if element b is inside a +const isElementInside = (a, b) => (b.x >= a.x || b.right <= a.right) && (b.y >= a.y || b.bottom <= a.bottom); + // Check if two elements overlap const elementsOverlap = function(rect1, rect2) { - const isInside = (a, b) => (b.x >= a.x || b.right <= a.right) && (b.y >= a.y || b.bottom <= a.bottom); const overlaps = (a, b) => !(a.right < b.left || a.left > b.right || a.bottom < b.top || a.top > b.bottom); - return isInside(rect1, rect2) || overlaps(rect1, rect2); + return isElementInside(rect1, rect2) || overlaps(rect1, rect2); }; // Exports for tests diff --git a/keepassxc-browser/content/fields.js b/keepassxc-browser/content/fields.js index 161df8e..9446db0 100644 --- a/keepassxc-browser/content/fields.js +++ b/keepassxc-browser/content/fields.js @@ -416,13 +416,24 @@ kpxcFields.isTopElement = function(elem, rect) { return false; } - // Check topmost element from three points inside the input - const verticalMiddle = rect.top + (rect.height / 2); const rootNode = elem.getRootNode() ?? document; + + // Returns the topmost element from point x, height/2 + // If the input has a label as the top element and it's inside the input, allow it. + const getTopmostElement = (element, x, elementRect) => { + const topElement = rootNode.elementFromPoint(x, elementRect.top + (elementRect.height / 2)); + return element?.labels && + element.labels[0] === topElement && + isElementInside(elementRect, topElement.getBoundingClientRect()) + ? element + : topElement; + }; + + // Check topmost element from three points inside the input if (matchesWithNodeName(elem, 'INPUT') && [ - rootNode.elementFromPoint(rect.left + (rect.width / 4), verticalMiddle), // First third - rootNode.elementFromPoint(rect.left + (rect.width / 2), verticalMiddle), // Middle - rootNode.elementFromPoint(rect.left + (rect.width / 1.33), verticalMiddle), // Last third + getTopmostElement(elem, rect.left + (rect.width / 4), rect), // First third + getTopmostElement(elem, rect.left + (rect.width / 2), rect), // Middle + getTopmostElement(elem, rect.left + (rect.width / 1.33), rect), // Last third ].some((e) => e !== elem)) { return false; }