From 1ad4b7abb82b478aa6b8dfcb36355f47bd128537 Mon Sep 17 00:00:00 2001 From: freedomFu <174780597@qq.com> Date: Tue, 30 Apr 2024 22:11:36 +0800 Subject: [PATCH 1/3] [FIX # 2184] Add clip, clip-path check; Add parent visibility check; Does not provide detection for fields covered by overlays for performance. --- keepassxc-browser/content/fields.js | 44 +++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/keepassxc-browser/content/fields.js b/keepassxc-browser/content/fields.js index d27f0a2..80c6809 100644 --- a/keepassxc-browser/content/fields.js +++ b/keepassxc-browser/content/fields.js @@ -391,18 +391,56 @@ kpxcFields.isVisible = function(elem) { if (elemStyle.visibility && (elemStyle.visibility === 'hidden' || elemStyle.visibility === 'collapse') || (opacity < MIN_OPACITY || opacity > MAX_OPACITY) || parseInt(elemStyle.width, 10) <= MIN_INPUT_FIELD_WIDTH_PX - || parseInt(elemStyle.height, 10) <= MIN_INPUT_FIELD_WIDTH_PX) { + || parseInt(elemStyle.height, 10) <= MIN_INPUT_FIELD_WIDTH_PX + // [FIX #2184] added by felix + || kpxcFields.isElementClipped(elem) + ) { return false; } - // Check for parent opacity - if (kpxcFields.traverseParents(elem, f => f.style.opacity === '0')) { + // [FIX #2184] added by felix + // Check for parent visibility + if ( + kpxcFields.traverseParents( + elem, + (f) => { + const fStyle = getComputedStyle(f, null); + const isVisible = fStyle.visibility !== 'hidden' && fStyle.visibility !== 'collapse'; + const isOpaqueEnough = Number(fStyle.opacity) >= MIN_OPACITY && Number(fStyle.opacity) <= MAX_OPACITY; + const hasValidSize = parseInt(fStyle.width, 10) > MIN_INPUT_FIELD_WIDTH_PX && parseInt(fStyle.height, 10) > MIN_INPUT_FIELD_WIDTH_PX; + const isNotClipped = !kpxcFields.isElementClipped(f); + return !(isVisible && isOpaqueEnough && hasValidSize && isNotClipped); + } + ) + ) { return false; } return true; }; +// [FIX #2184] added by felix +// TODO: This set is limited, but can cover many cases +kpxcFields.isElementClipped = function(elem) { + var clipPathStyles = new Set([ + "inset(50%)", + "inset(100%)", + "inset(100% 100% 0% 0%)", + "circle(0)", + "circle(0px)", + "circle(0px at 50% 50%)", + "polygon(0 0, 0 0, 0 0, 0 0)", + "polygon(0px 0px, 0px 0px, 0px 0px, 0px 0px)" + ]); + var clipStyles = new Set([ + "rect(0px, 0px, 0px, 0px)" + ]); + const eStyle = getComputedStyle(elem, 'null'); + const clipPath = eStyle.clipPath; + const clip = eStyle.clip; + return clipPathStyles.has(clipPath) || clipStyles.has(clip); +} + kpxcFields.prepareId = function(id) { return (id + '').replace(kpxcFields.rcssescape, kpxcFields.fcssescape); }; From 8279f0b0e7ddba0aa41ed74d2baf9f8f8fa7e834 Mon Sep 17 00:00:00 2001 From: freedomFu <174780597@qq.com> Date: Tue, 30 Apr 2024 23:13:49 +0800 Subject: [PATCH 2/3] [FIX --- keepassxc-browser/content/fields.js | 78 ++++++++++++++++------------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/keepassxc-browser/content/fields.js b/keepassxc-browser/content/fields.js index 80c6809..4bfbc9b 100644 --- a/keepassxc-browser/content/fields.js +++ b/keepassxc-browser/content/fields.js @@ -392,53 +392,61 @@ kpxcFields.isVisible = function(elem) { || (opacity < MIN_OPACITY || opacity > MAX_OPACITY) || parseInt(elemStyle.width, 10) <= MIN_INPUT_FIELD_WIDTH_PX || parseInt(elemStyle.height, 10) <= MIN_INPUT_FIELD_WIDTH_PX - // [FIX #2184] added by felix - || kpxcFields.isElementClipped(elem) + || kpxcFields.isElementClipped(elem) ) { return false; } - // [FIX #2184] added by felix // Check for parent visibility - if ( - kpxcFields.traverseParents( - elem, - (f) => { - const fStyle = getComputedStyle(f, null); - const isVisible = fStyle.visibility !== 'hidden' && fStyle.visibility !== 'collapse'; - const isOpaqueEnough = Number(fStyle.opacity) >= MIN_OPACITY && Number(fStyle.opacity) <= MAX_OPACITY; - const hasValidSize = parseInt(fStyle.width, 10) > MIN_INPUT_FIELD_WIDTH_PX && parseInt(fStyle.height, 10) > MIN_INPUT_FIELD_WIDTH_PX; - const isNotClipped = !kpxcFields.isElementClipped(f); - return !(isVisible && isOpaqueEnough && hasValidSize && isNotClipped); - } - ) - ) { + if (kpxcFields.traverseParents(elem, (f) => { + const fStyle = getComputedStyle(elem, null); + return !( + kpxcFields.isElementInvisible(elem, fStyle) && + kpxcFields.isElementOpaqueEnough(elem, fStyle) && + kpxcFields.isElementHasValidSize(elem, fStyle) && + !kpxcFields.isElementClipped(f) + )})) { return false; } return true; }; -// [FIX #2184] added by felix -// TODO: This set is limited, but can cover many cases -kpxcFields.isElementClipped = function(elem) { - var clipPathStyles = new Set([ - "inset(50%)", - "inset(100%)", - "inset(100% 100% 0% 0%)", - "circle(0)", - "circle(0px)", - "circle(0px at 50% 50%)", - "polygon(0 0, 0 0, 0 0, 0 0)", - "polygon(0px 0px, 0px 0px, 0px 0px, 0px 0px)" - ]); - var clipStyles = new Set([ - "rect(0px, 0px, 0px, 0px)" - ]); - const eStyle = getComputedStyle(elem, 'null'); - const clipPath = eStyle.clipPath; +kpxcFields.isElementInvisible = function(elem, fStyle) { + if (fStyle === null) { + const fStyle = getComputedStyle(elem, null); + } + return fStyle.visibility !== 'hidden' && fStyle.visibility !== 'collapse'; +} + +kpxcFields.isElementOpaqueEnough = function(elem, fStyle) { + if (fStyle === null) { + const fStyle = getComputedStyle(f, null); + } + return Number(fStyle.opacity) >= MIN_OPACITY && Number(fStyle.opacity) <= MAX_OPACITY; +} + +kpxcFields.isElementHasValidSize = function(elem, fStyle) { + if (fStyle === null) { + const fStyle = getComputedStyle(f, null); + } + return parseInt(fStyle.width, 10) > MIN_INPUT_FIELD_WIDTH_PX && parseInt(fStyle.height, 10) > MIN_INPUT_FIELD_WIDTH_PX; +} + +kpxcFields.isElementClipped = function(elem, fStyle) { + const eStyle = getComputedStyle(elem, null); const clip = eStyle.clip; - return clipPathStyles.has(clipPath) || clipStyles.has(clip); + const clipPath = eStyle.clipPath; + // if the value is not auto, in this case, password are considered invisible + if (clip !== 'auto' && clip.trim() !== '') { + return true; + } + // if the value is not none, in this case, password are considered invisible + if (clipPath !== 'none' && clipPath.trim() !== '') { + return true; + } + + return false; } kpxcFields.prepareId = function(id) { From 55dc51fa06a7bc4d92876f484e536c0bf26db231 Mon Sep 17 00:00:00 2001 From: freedomFu <174780597@qq.com> Date: Wed, 1 May 2024 15:33:17 +0800 Subject: [PATCH 3/3] [FIX #2184] Add inset and circle for clip-path, fix typos --- keepassxc-browser/content/fields.js | 108 +++++++++++++++++++++------- 1 file changed, 82 insertions(+), 26 deletions(-) diff --git a/keepassxc-browser/content/fields.js b/keepassxc-browser/content/fields.js index 4bfbc9b..95de95f 100644 --- a/keepassxc-browser/content/fields.js +++ b/keepassxc-browser/content/fields.js @@ -392,19 +392,19 @@ kpxcFields.isVisible = function(elem) { || (opacity < MIN_OPACITY || opacity > MAX_OPACITY) || parseInt(elemStyle.width, 10) <= MIN_INPUT_FIELD_WIDTH_PX || parseInt(elemStyle.height, 10) <= MIN_INPUT_FIELD_WIDTH_PX - || kpxcFields.isElementClipped(elem) + || kpxcFields.isElementClipped(elemStyle) ) { return false; } // Check for parent visibility if (kpxcFields.traverseParents(elem, (f) => { - const fStyle = getComputedStyle(elem, null); + const fStyle = getComputedStyle(f, null); return !( - kpxcFields.isElementInvisible(elem, fStyle) && - kpxcFields.isElementOpaqueEnough(elem, fStyle) && - kpxcFields.isElementHasValidSize(elem, fStyle) && - !kpxcFields.isElementClipped(f) + kpxcFields.isElementInvisible(fStyle) && + kpxcFields.isElementOpaqueEnough(fStyle) && + kpxcFields.isElementHasValidSize(fStyle) && + !kpxcFields.isElementClipped(fStyle) )})) { return false; } @@ -412,43 +412,99 @@ kpxcFields.isVisible = function(elem) { return true; }; -kpxcFields.isElementInvisible = function(elem, fStyle) { - if (fStyle === null) { - const fStyle = getComputedStyle(elem, null); - } +kpxcFields.isElementInvisible = function(fStyle) { return fStyle.visibility !== 'hidden' && fStyle.visibility !== 'collapse'; } -kpxcFields.isElementOpaqueEnough = function(elem, fStyle) { - if (fStyle === null) { - const fStyle = getComputedStyle(f, null); - } +kpxcFields.isElementOpaqueEnough = function(fStyle) { + return Number(fStyle.opacity) >= MIN_OPACITY && Number(fStyle.opacity) <= MAX_OPACITY; } -kpxcFields.isElementHasValidSize = function(elem, fStyle) { - if (fStyle === null) { - const fStyle = getComputedStyle(f, null); - } +kpxcFields.isElementHasValidSize = function(fStyle) { return parseInt(fStyle.width, 10) > MIN_INPUT_FIELD_WIDTH_PX && parseInt(fStyle.height, 10) > MIN_INPUT_FIELD_WIDTH_PX; } -kpxcFields.isElementClipped = function(elem, fStyle) { - const eStyle = getComputedStyle(elem, null); +kpxcFields.isElementClipped = function(eStyle) { const clip = eStyle.clip; const clipPath = eStyle.clipPath; - // if the value is not auto, in this case, password are considered invisible - if (clip !== 'auto' && clip.trim() !== '') { - return true; - } - // if the value is not none, in this case, password are considered invisible - if (clipPath !== 'none' && clipPath.trim() !== '') { + const position = eStyle.position; + const isClipped = kpxcFields.isClipped(clip, position); + const isClippedPath = kpxcFields.isClippedPath(clipPath); + + if (isClipped || isClippedPath) { return true; } return false; } +kpxcFields.isClipped = function(clip, position) { + if (clip !== "auto" && clip.trim() !== "" && (position === "absolute" || position === "fixed")) { + const clipMatches = clip.match(/rect\((\d+)\s*\D*(\d+)\s*\D*(\d+)\s*\D*(\d+)\s*\D*\)/); + if (clipMatches) { + const [top, right, bottom, left] = clipMatches.slice(1, 5).map(Number); + if (Math.abs(top - bottom) <= 0 || Math.abs(right - left) <= 0) { + return true; + } + } + } + return false; +} + +kpxcFields.isClippedPath = function(clipPath) { + clipPath = clipPath.trim(); + const ret = false; + if (clipPath.startsWith("inset")) { + ret = kpxcFields.isClippedPathInset(clipPath); + } else if (clipPath.startsWith("circle")) { + ret = kpxcFields.isClippedPathCircle(clipPath); + } + return ret; +} + +kpxcFields.isClippedPathInset = function(clipPath) { + const insetRegex = /inset\(([^)]+?)(?:\sround.*)?\)/; + const percentRegex = /\b(0|(\d+\.?\d*%))/g; + const insetMatch = clipPath.match(insetRegex); + if (insetMatch) { + const insetParams = insetMatch[1]; + let percentValues = []; + let percentMatch; + let i = 0; + while ((percentMatch = percentRegex.exec(insetParams)) !== null) { + i += 1; + percentValues.push(percentMatch[1]); + if (i > 4) { return false; } + } + if (percentValues.length === 0) { + return false; + } else if (percentValues.length === 1) { + let number = parseInt(percentValues[0].replace(/\D/g, ''), 10); + if (number >= 50) { return true; } + } else if (percentValues.length === 2) { + const topBottom = parseInt(percentValues[0].replace(/\D/g, ''), 10); + const leftRight = parseInt(percentValues[1].replace(/\D/g, ''), 10); + if (topBottom >= 50 || leftRight >= 50) {return true;} + } else if (percentValues.length === 4) { + const top = parseInt(percentValues[0].replace(/\D/g, ''), 10); + const right = parseInt(percentValues[1].replace(/\D/g, ''), 10); + const bottom = parseInt(percentValues[2].replace(/\D/g, ''), 10); + const left = parseInt(percentValues[3].replace(/\D/g, ''), 10); + if ((top + bottom) >= 100 || (right + left) >= 50) {return true;} + } + } + return false; +} + +kpxcFields.isClippedPathCircle = function(clipPath) { + const circleRegex = /circle\(\s*0[^)]*\)/i; + if (circleRegex.test(clipPath)) { + return true; + } + return false; +} + kpxcFields.prepareId = function(id) { return (id + '').replace(kpxcFields.rcssescape, kpxcFields.fcssescape); };