Check overlays before fill (#2733)

Update and check overlays before fill
This commit is contained in:
Sami Vänttinen 2025-10-26 15:14:11 +02:00 committed by GitHub
parent 87b949a008
commit 20f1a99356
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 93 additions and 70 deletions

View file

@ -466,6 +466,28 @@ kpxcFields.checkExistingFields = function() {
}
};
// Check for popup overlays
kpxcFields.isOverlayOnTop = function(rect) {
for (const overlay of kpxcFields.overlays ?? []) {
if (kpxcSites.overlayExceptionFound(overlay)) {
continue;
}
const overlayRect = overlay?.getBoundingClientRect();
if (overlayRect && elementsOverlap(rect, overlayRect)) {
return true;
}
}
return false;
};
/**
* Check if element is the topmost element
* @param {HTMLElement} elem Element to be checked
* @param {DOMRect} rect Precalculated DOMRect of the element
* @returns {boolean} True if element is the topmost
*/
kpxcFields.isTopElement = function(elem, rect) {
if (!elem || !rect) {
return false;
@ -493,16 +515,9 @@ kpxcFields.isTopElement = function(elem, rect) {
return false;
}
// Check for popup overlays
for (const overlay of kpxcFields.overlays ?? []) {
if (kpxcSites.overlayExceptionFound(overlay)) {
continue;
}
const overlayRect = overlay?.getBoundingClientRect();
if (overlayRect && elementsOverlap(rect, overlayRect)) {
return false;
}
// Check if element has an overlay
if (kpxcFields.isOverlayOnTop(rect)) {
return false;
}
return true;

View file

@ -18,7 +18,7 @@ kpxcFill.fillAttributeToActiveElementWith = async function(attr) {
return;
}
kpxc.setValue(el, value[0]);
kpxcFill.setValue(el, value[0]);
};
// Fill requested from the context menu. Active element is used for combination detection
@ -178,7 +178,7 @@ kpxcFill.setTOTPValue = function(elem, val) {
}
}
kpxc.setValue(elem, val);
kpxcFill.setValue(elem, val);
};
// Fill TOTP in parts
@ -188,7 +188,7 @@ kpxcFill.fillSegmentedTotp = function(elem, val, totpInputs) {
}
for (let i = 0; i < totpInputs.length; ++i) {
kpxc.setValue(totpInputs[i], val[i]);
kpxcFill.setValue(totpInputs[i], val[i]);
}
};
@ -260,7 +260,7 @@ kpxcFill.fillInCredentials = async function(combination, predefinedUsername, uui
return;
}
kpxc.setValueWithChange(combination.password, selectedCredentials.password);
kpxcFill.setValueWithChange(combination.password, selectedCredentials.password);
await kpxc.setPasswordFilled(true);
}
@ -268,7 +268,7 @@ kpxcFill.fillInCredentials = async function(combination, predefinedUsername, uui
if (combination.username && usernameValue && combination.username !== combination.password &&
(!combination.username.value || combination.username.value !== usernameValue)) {
if (!passOnly) {
kpxc.setValueWithChange(combination.username, usernameValue);
kpxcFill.setValueWithChange(combination.username, usernameValue);
}
}
@ -307,7 +307,7 @@ kpxcFill.fillInStringFields = function(fields, stringFields) {
const currentField = fields[i];
if (currentField && stringFieldValue[0]) {
kpxc.setValue(currentField, stringFieldValue[0], true);
kpxcFill.setValue(currentField, stringFieldValue[0], true);
filledInFields.push(currentField);
}
}
@ -340,6 +340,68 @@ kpxcFill.performAutoSubmit = async function(combination, skipAutoSubmit) {
}
};
// Special handling for setting value to select and checkbox elements
kpxcFill.setValue = function(field, value, forced = false) {
if (field.matches('select')) {
value = value.toLowerCase().trim();
const options = field.querySelectorAll('option');
for (const o of options) {
if (o.textContent.toLowerCase().trim() === value) {
kpxcFill.setValueWithChange(field, o.value);
return false;
}
}
return;
} else if (field.getLowerCaseAttribute('type') === 'checkbox' && value?.toLowerCase() === 'true') {
field.checked = true;
}
// Make sure the input is not wrapped inside another element (custom INPUT element)
if (field?.nodeName !== 'INPUT' && field?.nodeName?.includes('INPUT')) {
const childInput = field?.querySelector('input');
const fieldsFromShadowDOM = kpxcObserverHelper.findInputsFromShadowDOM(field);
field = childInput ?? fieldsFromShadowDOM[0];
}
kpxcFill.setValueWithChange(field, value, forced);
};
// Sets a new value to input field and triggers necessary events
kpxcFill.setValueWithChange = function(field, value, forced = false) {
if (!field || (field?.readOnly && !forced)) {
return;
}
// Check for overlays before fill
kpxcFields.discoverOverlays();
const rect = field.getBoundingClientRect();
if (kpxcFields.isOverlayOnTop(rect)) {
return;
}
const dispatchLegacyEvent = function(elem, eventName) {
const legacyEvent = elem.ownerDocument.createEvent('Event');
legacyEvent.initEvent(eventName, true, false);
elem.dispatchEvent(legacyEvent);
};
field.focus();
field.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, cancelable: false }));
field.dispatchEvent(new KeyboardEvent('keypress', { bubbles: true, cancelable: false }));
field.dispatchEvent(new KeyboardEvent('keyup', { bubbles: true, cancelable: false }));
field.dispatchEvent(new Event('input', { bubbles: true, cancelable: false }));
field.dispatchEvent(new Event('change', { bubbles: true, cancelable: false }));
field.value = value;
// Some pages will not accept the value change without dispatching events directly to the document
dispatchLegacyEvent(field, 'input');
dispatchLegacyEvent(field, 'change');
};
// Check if password fill is done to a plain text field
const passwordFillIsAllowed = function(elem) {
const elementIsPasswordField =

View file

@ -719,60 +719,6 @@ kpxc.setPasswordFilled = async function(state) {
await sendMessage('password_set_filled', state);
};
// Special handling for setting value to select and checkbox elements
kpxc.setValue = function(field, value, forced = false) {
if (field.matches('select')) {
value = value.toLowerCase().trim();
const options = field.querySelectorAll('option');
for (const o of options) {
if (o.textContent.toLowerCase().trim() === value) {
kpxc.setValueWithChange(field, o.value);
return false;
}
}
return;
} else if (field.getLowerCaseAttribute('type') === 'checkbox' && value?.toLowerCase() === 'true') {
field.checked = true;
}
// Make sure the input is not wrapped inside another element (custom INPUT element)
if (field?.nodeName !== 'INPUT' && field?.nodeName?.includes('INPUT')) {
const childInput = field?.querySelector('input');
const fieldsFromShadowDOM = kpxcObserverHelper.findInputsFromShadowDOM(field);
field = childInput ?? fieldsFromShadowDOM[0];
}
kpxc.setValueWithChange(field, value, forced);
};
// Sets a new value to input field and triggers necessary events
kpxc.setValueWithChange = function(field, value, forced = false) {
if (!field || (field?.readOnly && !forced)) {
return;
}
const dispatchLegacyEvent = function(elem, eventName) {
const legacyEvent = elem.ownerDocument.createEvent('Event');
legacyEvent.initEvent(eventName, true, false);
elem.dispatchEvent(legacyEvent);
};
field.focus();
field.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, cancelable: false }));
field.dispatchEvent(new KeyboardEvent('keypress', { bubbles: true, cancelable: false }));
field.dispatchEvent(new KeyboardEvent('keyup', { bubbles: true, cancelable: false }));
field.dispatchEvent(new Event('input', { bubbles: true, cancelable: false }));
field.dispatchEvent(new Event('change', { bubbles: true, cancelable: false }));
field.value = value;
// Some pages will not accept the value change without dispatching events directly to the document
dispatchLegacyEvent(field, 'input');
dispatchLegacyEvent(field, 'change');
};
kpxc.showGroupNameInAutocomplete = function() {
return !kpxc.settings.useCompactMode
|| (kpxc.settings.showGroupNameInAutocomplete && kpxc.getUniqueGroupCount(kpxc.credentials) > 1);