mirror of
https://github.com/keepassxreboot/keepassxc-browser.git
synced 2026-03-11 08:54:43 +00:00
Custom Login Fields banner selector (#1390)
New Custom Login Fields banner selector.
This commit is contained in:
parent
32ee510565
commit
ba5a7141fb
15 changed files with 924 additions and 638 deletions
|
|
@ -99,7 +99,7 @@
|
|||
"kpxc": true,
|
||||
"kpActions": true,
|
||||
"kpxcBanner": true,
|
||||
"kpxcDefine": true,
|
||||
"kpxcCustomLoginFieldsBanner": true,
|
||||
"kpxcFields": true,
|
||||
"kpxcFill": true,
|
||||
"kpxcForm": true,
|
||||
|
|
|
|||
|
|
@ -183,6 +183,10 @@
|
|||
"message": "Username field icon",
|
||||
"description": "Username field icon text."
|
||||
},
|
||||
"totp": {
|
||||
"message": "TOTP",
|
||||
"description": "TOTP text."
|
||||
},
|
||||
"totpFieldText": {
|
||||
"message": "Fill TOTP from KeePassXC",
|
||||
"description": "OTP field icon hover text."
|
||||
|
|
@ -191,9 +195,9 @@
|
|||
"message": "TOTP field icon",
|
||||
"description": "OTP field icon text."
|
||||
},
|
||||
"defineDismiss": {
|
||||
"message": "Dismiss",
|
||||
"description": "Dismiss button text when choosing custom login fields."
|
||||
"defineClose": {
|
||||
"message": "Close",
|
||||
"description": "Close button text when choosing custom login fields."
|
||||
},
|
||||
"defineSkip": {
|
||||
"message": "Skip",
|
||||
|
|
@ -203,9 +207,9 @@
|
|||
"message": "Show more",
|
||||
"description": "More button text when choosing custom login fields."
|
||||
},
|
||||
"defineAgain": {
|
||||
"message": "Again",
|
||||
"description": "Again button text when choosing custom login fields."
|
||||
"defineReset": {
|
||||
"message": "Reset",
|
||||
"description": "Reset button text when choosing custom login fields."
|
||||
},
|
||||
"defineConfirm": {
|
||||
"message": "Confirm",
|
||||
|
|
@ -215,29 +219,29 @@
|
|||
"message": "Login fields for this page are already selected and will be overwritten.",
|
||||
"description": "A text shown when custom credentials fields are already set for the page."
|
||||
},
|
||||
"defineDiscard": {
|
||||
"message": "Discard selection",
|
||||
"description": "Discard selection button text when choosing custom login fields."
|
||||
"defineClearData": {
|
||||
"message": "Clear saved data",
|
||||
"description": "Clear save data button text when choosing custom login fields."
|
||||
},
|
||||
"defineStringField": {
|
||||
"message": "String field #",
|
||||
"message": "String field",
|
||||
"description": "Text for string field."
|
||||
},
|
||||
"defineChooseUsername": {
|
||||
"message": "1. Choose a username field",
|
||||
"message": "Choose a username field",
|
||||
"description": "Choosing a username field text when choosing custom login fields."
|
||||
},
|
||||
"defineChoosePassword": {
|
||||
"message": "2. Now choose a password field",
|
||||
"message": "Choose a password field",
|
||||
"description": "Choosing a password field text when choosing custom login fields."
|
||||
},
|
||||
"defineChooseTOTP": {
|
||||
"message": "3. Choose a TOTP field",
|
||||
"message": "Choose a TOTP field",
|
||||
"description": "Choosing a TOTP field text when choosing custom login fields."
|
||||
},
|
||||
"defineConfirmSelection": {
|
||||
"message": "4. Confirm selection",
|
||||
"description": "Confirm a selection text when choosing custom login fields."
|
||||
"defineChooseStringFields": {
|
||||
"message": "Choose String Fields",
|
||||
"description": "Choose String Fields a selection text when choosing custom login fields."
|
||||
},
|
||||
"defineHelpText": {
|
||||
"message": "Please confirm your selection or choose more fields as String fields.",
|
||||
|
|
@ -247,6 +251,10 @@
|
|||
"message": "You can also use the numbers to choose the input fields from keyboard.",
|
||||
"description": "Help text when choosing custom login fields."
|
||||
},
|
||||
"defineChooseCustomLoginFieldText": {
|
||||
"message": "Choose a Custom Login Field",
|
||||
"description": "Help text for Custom Login Field banner."
|
||||
},
|
||||
"username": {
|
||||
"message": "Username",
|
||||
"description": "General text for username."
|
||||
|
|
@ -255,6 +263,10 @@
|
|||
"message": "Password",
|
||||
"description": "General text for password."
|
||||
},
|
||||
"stringFields": {
|
||||
"message": "String Fields",
|
||||
"description": "General text for a String Fields."
|
||||
},
|
||||
"credentialsNoUsername": {
|
||||
"message": "- no username -",
|
||||
"description": "Shown when no username is set in the credentials."
|
||||
|
|
|
|||
|
|
@ -126,7 +126,6 @@ kpxcBanner.create = async function(credentials = {}) {
|
|||
const colorStyleSheet = createStylesheet('css/colors.css');
|
||||
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.setAttribute('id', 'kpxc-banner');
|
||||
this.shadowRoot = wrapper.attachShadow({ mode: 'closed' });
|
||||
this.shadowRoot.append(colorStyleSheet);
|
||||
this.shadowRoot.append(styleSheet);
|
||||
|
|
|
|||
767
keepassxc-browser/content/custom-fields-banner.js
Normal file
767
keepassxc-browser/content/custom-fields-banner.js
Normal file
|
|
@ -0,0 +1,767 @@
|
|||
'use strict';
|
||||
|
||||
const STEP_NONE = 0;
|
||||
const STEP_SELECT_USERNAME = 1;
|
||||
const STEP_SELECT_PASSWORD = 2;
|
||||
const STEP_SELECT_TOTP = 3;
|
||||
const STEP_SELECT_STRING_FIELDS = 4;
|
||||
|
||||
const BLUE_BUTTON = 'kpxc-button kpxc-blue-button';
|
||||
const GREEN_BUTTON = 'kpxc-button kpxc-green-button';
|
||||
const ORANGE_BUTTON = 'kpxc-button kpxc-orange-button';
|
||||
const RED_BUTTON = 'kpxc-button kpxc-red-button';
|
||||
const GRAY_BUTTON_CLASS = 'kpxc-gray-button';
|
||||
|
||||
const DEFINED_CUSTOM_FIELDS = 'defined-custom-fields';
|
||||
const FIXED_FIELD_CLASS = 'kpxcDefine-fixed-field';
|
||||
const DARK_FIXED_FIELD_CLASS = 'kpxcDefine-fixed-field-dark';
|
||||
const HOVER_FIELD_CLASS = 'kpxcDefine-fixed-hover-field';
|
||||
const DARK_HOVER_FIELD_CLASS = 'kpxcDefine-fixed-hover-field-dark';
|
||||
const DARK_TEXT_CLASS = 'kpxcDefine-dark-text';
|
||||
const USERNAME_FIELD_CLASS = 'kpxcDefine-fixed-username-field';
|
||||
const PASSWORD_FIELD_CLASS = 'kpxcDefine-fixed-password-field';
|
||||
const TOTP_FIELD_CLASS = 'kpxcDefine-fixed-totp-field';
|
||||
const STRING_FIELD_CLASS = 'kpxcDefine-fixed-string-field';
|
||||
|
||||
const kpxcCustomLoginFieldsBanner = {};
|
||||
kpxcCustomLoginFieldsBanner.banner = undefined;
|
||||
kpxcCustomLoginFieldsBanner.chooser = undefined;
|
||||
kpxcCustomLoginFieldsBanner.created = false;
|
||||
kpxcCustomLoginFieldsBanner.dataStep = STEP_NONE;
|
||||
kpxcCustomLoginFieldsBanner.infoText = undefined;
|
||||
kpxcCustomLoginFieldsBanner.wrapper = undefined;
|
||||
kpxcCustomLoginFieldsBanner.inputQueryPattern = 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=date]):not([type=datetime-local]):not([type=file]):not([type=hidden]):not([type=image]):not([type=month]):not([type=range]):not([type=reset]):not([type=submit]):not([type=time]):not([type=week]), select, textarea';
|
||||
kpxcCustomLoginFieldsBanner.markedFields = [];
|
||||
kpxcCustomLoginFieldsBanner.nonSelectedElementsPattern = `div.${FIXED_FIELD_CLASS}:not(.${USERNAME_FIELD_CLASS}):not(.${PASSWORD_FIELD_CLASS}):not(.${TOTP_FIELD_CLASS}):not(.${STRING_FIELD_CLASS})`;
|
||||
|
||||
kpxcCustomLoginFieldsBanner.selection = {
|
||||
username: undefined,
|
||||
usernameElement: undefined,
|
||||
password: undefined,
|
||||
passwordElement: undefined,
|
||||
totp: undefined,
|
||||
totpElement: undefined,
|
||||
fields: [],
|
||||
fieldElements: [],
|
||||
};
|
||||
|
||||
kpxcCustomLoginFieldsBanner.buttons = {
|
||||
reset: undefined,
|
||||
confirm: undefined,
|
||||
clearData: undefined,
|
||||
close: undefined,
|
||||
};
|
||||
|
||||
kpxcCustomLoginFieldsBanner.destroy = async function() {
|
||||
if (!kpxcCustomLoginFieldsBanner.created) {
|
||||
return;
|
||||
}
|
||||
|
||||
kpxcCustomLoginFieldsBanner.resetSelection();
|
||||
kpxcCustomLoginFieldsBanner.created = false;
|
||||
kpxcCustomLoginFieldsBanner.close();
|
||||
|
||||
if (kpxcCustomLoginFieldsBanner.wrapper && window.self.document.body.contains(kpxcCustomLoginFieldsBanner.wrapper)) {
|
||||
window.self.document.body.removeChild(kpxcCustomLoginFieldsBanner.wrapper);
|
||||
} else {
|
||||
window.self.document.body.removeChild(window.parent.document.body.querySelector('#kpxc-banner'));
|
||||
}
|
||||
};
|
||||
|
||||
kpxcCustomLoginFieldsBanner.close = function() {
|
||||
kpxcCustomLoginFieldsBanner.chooser.remove();
|
||||
document.removeEventListener('keydown', kpxcCustomLoginFieldsBanner.keyDown);
|
||||
};
|
||||
|
||||
kpxcCustomLoginFieldsBanner.create = async function() {
|
||||
if (await kpxc.siteIgnored() || kpxcCustomLoginFieldsBanner.created) {
|
||||
return;
|
||||
}
|
||||
|
||||
const banner = kpxcUI.createElement('div', 'kpxc-banner', { 'id': 'container' });
|
||||
banner.style.zIndex = '2147483646';
|
||||
kpxcCustomLoginFieldsBanner.chooser = kpxcUI.createElement('div', '', { 'id': 'kpxcDefine-fields' });
|
||||
|
||||
const bannerInfo = kpxcUI.createElement('div', 'banner-info');
|
||||
const bannerButtons = kpxcUI.createElement('div', 'banner-buttons');
|
||||
|
||||
const iconClassName = isFirefox() ? 'kpxc-banner-icon-moz' : 'kpxc-banner-icon';
|
||||
const icon = kpxcUI.createElement('span', iconClassName);
|
||||
const infoText = kpxcUI.createElement('span', '', {}, tr('defineChooseCustomLoginFieldText'));
|
||||
const separator = kpxcUI.createElement('div', 'kpxc-separator');
|
||||
const secondSeparator = kpxcUI.createElement('div', 'kpxc-separator');
|
||||
|
||||
const resetButton = kpxcUI.createButton(BLUE_BUTTON, tr('defineReset'), kpxcCustomLoginFieldsBanner.reset);
|
||||
const usernameButton = kpxcUI.createButton(ORANGE_BUTTON, tr('username'), kpxcCustomLoginFieldsBanner.usernameButtonClicked);
|
||||
const passwordButton = kpxcUI.createButton(RED_BUTTON, tr('password'), kpxcCustomLoginFieldsBanner.passwordButtonClicked);
|
||||
const totpButton = kpxcUI.createButton(GREEN_BUTTON, 'TOTP', kpxcCustomLoginFieldsBanner.totpButtonClicked);
|
||||
const stringFieldsButton = kpxcUI.createButton(BLUE_BUTTON, tr('stringFields'), kpxcCustomLoginFieldsBanner.stringFieldsButtonClicked);
|
||||
const clearDataButton = kpxcUI.createButton(RED_BUTTON, tr('defineClearData'), kpxcCustomLoginFieldsBanner.clearData);
|
||||
const confirmButton = kpxcUI.createButton(GREEN_BUTTON, tr('defineConfirm'), kpxcCustomLoginFieldsBanner.confirm);
|
||||
const closeButton = kpxcUI.createButton(RED_BUTTON, tr('defineClose'), kpxcCustomLoginFieldsBanner.closeButtonClicked);
|
||||
closeButton.style.minWidth = Pixels(64);
|
||||
|
||||
confirmButton.disabled = true;
|
||||
kpxcCustomLoginFieldsBanner.banner = banner;
|
||||
kpxcCustomLoginFieldsBanner.infoText = infoText;
|
||||
kpxcCustomLoginFieldsBanner.buttons.reset = resetButton;
|
||||
kpxcCustomLoginFieldsBanner.buttons.clearData = clearDataButton;
|
||||
kpxcCustomLoginFieldsBanner.buttons.confirm = confirmButton;
|
||||
kpxcCustomLoginFieldsBanner.buttons.close = closeButton;
|
||||
kpxcCustomLoginFieldsBanner.buttons.username = usernameButton;
|
||||
kpxcCustomLoginFieldsBanner.buttons.password = passwordButton;
|
||||
kpxcCustomLoginFieldsBanner.buttons.totp = totpButton;
|
||||
kpxcCustomLoginFieldsBanner.buttons.stringFields = stringFieldsButton;
|
||||
|
||||
bannerInfo.appendMultiple(icon, infoText);
|
||||
bannerButtons.appendMultiple(resetButton, separator, usernameButton,
|
||||
passwordButton, totpButton, stringFieldsButton, secondSeparator, clearDataButton, confirmButton, closeButton);
|
||||
banner.appendMultiple(bannerInfo, bannerButtons);
|
||||
|
||||
const location = kpxc.getDocumentLocation();
|
||||
kpxcCustomLoginFieldsBanner.buttons.clearData.style.display
|
||||
= kpxc.settings[DEFINED_CUSTOM_FIELDS] && kpxc.settings[DEFINED_CUSTOM_FIELDS][location]
|
||||
? 'inline-block' : 'none';
|
||||
if (window.self !== window.top && kpxcCustomLoginFieldsBanner.buttons.clearData.style.display === 'inline-block') {
|
||||
sendMessageToParent('enable_clear_data_button');
|
||||
}
|
||||
|
||||
initColorTheme(banner);
|
||||
|
||||
const bannerStyleSheet = createStylesheet('css/banner.css');
|
||||
const defineStyleSheet = createStylesheet('css/define.css');
|
||||
const buttonStyleSheet = createStylesheet('css/button.css');
|
||||
const colorStyleSheet = createStylesheet('css/colors.css');
|
||||
|
||||
const wrapper = document.createElement('div');
|
||||
this.shadowRoot = wrapper.attachShadow({ mode: 'closed' });
|
||||
this.shadowRoot.append(colorStyleSheet);
|
||||
this.shadowRoot.append(bannerStyleSheet);
|
||||
this.shadowRoot.append(defineStyleSheet);
|
||||
this.shadowRoot.append(buttonStyleSheet);
|
||||
|
||||
// Only create the banner to top window
|
||||
if (window.self === window.top) {
|
||||
this.shadowRoot.append(banner);
|
||||
}
|
||||
|
||||
this.shadowRoot.append(kpxcCustomLoginFieldsBanner.chooser);
|
||||
kpxcCustomLoginFieldsBanner.wrapper = wrapper;
|
||||
|
||||
if (!kpxcCustomLoginFieldsBanner.created) {
|
||||
window.self.document.body.appendChild(wrapper);
|
||||
kpxcCustomLoginFieldsBanner.created = true;
|
||||
|
||||
if (window.self === window.top) {
|
||||
// Listen messages from iframes
|
||||
window.addEventListener('message', handleTopWindowMessage, false);
|
||||
} else {
|
||||
// Listen messages from top window
|
||||
window.addEventListener('message', handleParentWindowMessage, false);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', kpxcCustomLoginFieldsBanner.keyDown);
|
||||
};
|
||||
|
||||
kpxcCustomLoginFieldsBanner.removeSelection = function(selection, fieldClass) {
|
||||
const inputField = kpxcFields.getElementFromXPathId(selection[0]);
|
||||
const index = kpxcCustomLoginFieldsBanner.markedFields.indexOf(inputField);
|
||||
if (index >= 0) {
|
||||
removeContent(fieldClass);
|
||||
kpxcCustomLoginFieldsBanner.markedFields.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
kpxcCustomLoginFieldsBanner.enableAllButtons = function() {
|
||||
for (const button of Object.values(kpxcCustomLoginFieldsBanner.buttons)) {
|
||||
button.classList.remove(GRAY_BUTTON_CLASS);
|
||||
}
|
||||
};
|
||||
|
||||
kpxcCustomLoginFieldsBanner.usernameButtonClicked = function(e) {
|
||||
// Cancel the current selection if button is clicked again
|
||||
if (!e.isTrusted || kpxcCustomLoginFieldsBanner.dataStep === STEP_SELECT_USERNAME) {
|
||||
kpxcCustomLoginFieldsBanner.backToStart();
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset username field selection if already set
|
||||
if (kpxcCustomLoginFieldsBanner.selection.username) {
|
||||
kpxcCustomLoginFieldsBanner.removeSelection(kpxcCustomLoginFieldsBanner.selection.username, `div.${USERNAME_FIELD_CLASS}`);
|
||||
kpxcCustomLoginFieldsBanner.selection.username = undefined;
|
||||
}
|
||||
|
||||
kpxcCustomLoginFieldsBanner.prepareUsernameSelection();
|
||||
kpxcCustomLoginFieldsBanner.buttons.confirm.disabled = true;
|
||||
|
||||
sendMessageToFrames('username_button_clicked');
|
||||
};
|
||||
|
||||
kpxcCustomLoginFieldsBanner.passwordButtonClicked = function(e) {
|
||||
if (!e.isTrusted || kpxcCustomLoginFieldsBanner.dataStep === STEP_SELECT_PASSWORD) {
|
||||
kpxcCustomLoginFieldsBanner.backToStart();
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset password field selection if already set
|
||||
if (kpxcCustomLoginFieldsBanner.selection.password) {
|
||||
kpxcCustomLoginFieldsBanner.removeSelection(kpxcCustomLoginFieldsBanner.selection.password, `div.${PASSWORD_FIELD_CLASS}`);
|
||||
kpxcCustomLoginFieldsBanner.selection.password = undefined;
|
||||
}
|
||||
|
||||
kpxcCustomLoginFieldsBanner.preparePasswordSelection();
|
||||
kpxcCustomLoginFieldsBanner.buttons.confirm.disabled = true;
|
||||
|
||||
sendMessageToFrames('password_button_clicked');
|
||||
};
|
||||
|
||||
kpxcCustomLoginFieldsBanner.totpButtonClicked = function(e) {
|
||||
if (!e.isTrusted || kpxcCustomLoginFieldsBanner.dataStep === STEP_SELECT_TOTP) {
|
||||
kpxcCustomLoginFieldsBanner.backToStart();
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset TOTP field selection if already set
|
||||
if (kpxcCustomLoginFieldsBanner.selection.totp) {
|
||||
kpxcCustomLoginFieldsBanner.removeSelection(kpxcCustomLoginFieldsBanner.selection.totp, `div.${TOTP_FIELD_CLASS}`);
|
||||
kpxcCustomLoginFieldsBanner.selection.totp = undefined;
|
||||
}
|
||||
|
||||
kpxcCustomLoginFieldsBanner.prepareTOTPSelection();
|
||||
kpxcCustomLoginFieldsBanner.buttons.confirm.disabled = true;
|
||||
|
||||
sendMessageToFrames('totp_button_clicked');
|
||||
};
|
||||
|
||||
kpxcCustomLoginFieldsBanner.stringFieldsButtonClicked = function(e) {
|
||||
if (!e.isTrusted || kpxcCustomLoginFieldsBanner.dataStep === STEP_SELECT_STRING_FIELDS) {
|
||||
kpxcCustomLoginFieldsBanner.backToStart();
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset String Field selection if already set
|
||||
if (kpxcCustomLoginFieldsBanner.selection.fields.length > 0) {
|
||||
for (const field of kpxcCustomLoginFieldsBanner.selection.fields) {
|
||||
kpxcCustomLoginFieldsBanner.removeSelection(field, `div.${STRING_FIELD_CLASS}`);
|
||||
}
|
||||
|
||||
kpxcCustomLoginFieldsBanner.selection.fields = [];
|
||||
}
|
||||
|
||||
kpxcCustomLoginFieldsBanner.prepareStringFieldSelection();
|
||||
kpxcCustomLoginFieldsBanner.buttons.confirm.disabled = true;
|
||||
|
||||
sendMessageToFrames('string_field_button_clicked');
|
||||
};
|
||||
|
||||
kpxcCustomLoginFieldsBanner.closeButtonClicked = function(e) {
|
||||
if (!e.isTrusted) {
|
||||
return;
|
||||
}
|
||||
|
||||
kpxcCustomLoginFieldsBanner.destroy();
|
||||
|
||||
sendMessageToFrames('close_button_clicked');
|
||||
};
|
||||
|
||||
// Updates the possible selections if the page content has been changed
|
||||
kpxcCustomLoginFieldsBanner.updateFieldSelections = function() {
|
||||
if (kpxcCustomLoginFieldsBanner.dataStep === STEP_NONE && kpxcCustomLoginFieldsBanner.markedFields.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
kpxcCustomLoginFieldsBanner.removeMarkedFields();
|
||||
|
||||
if (kpxcCustomLoginFieldsBanner.dataStep === STEP_SELECT_USERNAME) {
|
||||
kpxcCustomLoginFieldsBanner.prepareUsernameSelection();
|
||||
} else if (kpxcCustomLoginFieldsBanner.dataStep === STEP_SELECT_PASSWORD) {
|
||||
kpxcCustomLoginFieldsBanner.preparePasswordSelection();
|
||||
} else if (kpxcCustomLoginFieldsBanner.dataStep === STEP_SELECT_TOTP) {
|
||||
kpxcCustomLoginFieldsBanner.prepareTOTPSelection();
|
||||
} else if (kpxcCustomLoginFieldsBanner.dataStep === STEP_SELECT_STRING_FIELDS) {
|
||||
kpxcCustomLoginFieldsBanner.prepareStringFieldSelection();
|
||||
}
|
||||
};
|
||||
|
||||
// Reset selections
|
||||
kpxcCustomLoginFieldsBanner.reset = function() {
|
||||
kpxcCustomLoginFieldsBanner.resetSelection();
|
||||
|
||||
kpxcCustomLoginFieldsBanner.buttons.confirm.disabled = true;
|
||||
kpxcCustomLoginFieldsBanner.infoText.textContent = tr('defineChooseCustomLoginFieldText');
|
||||
kpxcCustomLoginFieldsBanner.buttons.close.textContent = tr('defineClose');
|
||||
kpxcCustomLoginFieldsBanner.dataStep = STEP_NONE;
|
||||
|
||||
kpxcCustomLoginFieldsBanner.enableAllButtons();
|
||||
|
||||
sendMessageToFrames('reset_button_clicked');
|
||||
};
|
||||
|
||||
// Confirm and save the selections
|
||||
kpxcCustomLoginFieldsBanner.confirm = async function() {
|
||||
if (!kpxc.settings[DEFINED_CUSTOM_FIELDS]) {
|
||||
kpxc.settings[DEFINED_CUSTOM_FIELDS] = {};
|
||||
}
|
||||
|
||||
// If the new selection is already used in some other field, clear it
|
||||
const clearIdenticalField = function(path, location) {
|
||||
const currentSite = kpxc.settings[DEFINED_CUSTOM_FIELDS][location];
|
||||
if (currentSite.username && currentSite.username[0] === path[0]) {
|
||||
kpxc.settings[DEFINED_CUSTOM_FIELDS][location].username = undefined;
|
||||
} else if (currentSite.password && currentSite.password[0] === path[0]) {
|
||||
kpxc.settings[DEFINED_CUSTOM_FIELDS][location].password = undefined;
|
||||
} else if (currentSite.totp && currentSite.totp[0] === path[0]) {
|
||||
kpxc.settings[DEFINED_CUSTOM_FIELDS][location].totp = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const usernamePath = kpxcCustomLoginFieldsBanner.selection.username;
|
||||
const passwordPath = kpxcCustomLoginFieldsBanner.selection.password;
|
||||
const totpPath = kpxcCustomLoginFieldsBanner.selection.totp;
|
||||
const stringFieldsPaths = kpxcCustomLoginFieldsBanner.selection.fields;
|
||||
const location = kpxc.getDocumentLocation();
|
||||
const currentSettings = kpxc.settings[DEFINED_CUSTOM_FIELDS][location];
|
||||
|
||||
if (currentSettings) {
|
||||
// Update the single selection to current settings
|
||||
if (usernamePath) {
|
||||
clearIdenticalField(usernamePath, location);
|
||||
kpxc.settings[DEFINED_CUSTOM_FIELDS][location].username = usernamePath;
|
||||
}
|
||||
|
||||
if (passwordPath) {
|
||||
clearIdenticalField(passwordPath, location);
|
||||
kpxc.settings[DEFINED_CUSTOM_FIELDS][location].password = passwordPath;
|
||||
}
|
||||
|
||||
if (totpPath) {
|
||||
clearIdenticalField(totpPath, location);
|
||||
kpxc.settings[DEFINED_CUSTOM_FIELDS][location].totp = totpPath;
|
||||
}
|
||||
|
||||
if (stringFieldsPaths.length > 0) {
|
||||
kpxc.settings[DEFINED_CUSTOM_FIELDS][location].fields = stringFieldsPaths;
|
||||
}
|
||||
} else {
|
||||
// Override all fields (default)
|
||||
kpxc.settings[DEFINED_CUSTOM_FIELDS][location] = {
|
||||
username: usernamePath,
|
||||
password: passwordPath,
|
||||
totp: totpPath,
|
||||
fields: stringFieldsPaths
|
||||
};
|
||||
}
|
||||
|
||||
await sendMessage('save_settings', kpxc.settings);
|
||||
kpxcCustomLoginFieldsBanner.destroy();
|
||||
|
||||
sendMessageToFrames('confirm_button_clicked');
|
||||
};
|
||||
|
||||
// Clears the previously saved data from settings
|
||||
kpxcCustomLoginFieldsBanner.clearData = async function() {
|
||||
const location = kpxc.getDocumentLocation();
|
||||
delete kpxc.settings[DEFINED_CUSTOM_FIELDS][location];
|
||||
|
||||
await sendMessage('save_settings', kpxc.settings);
|
||||
await sendMessage('load_settings');
|
||||
|
||||
kpxcCustomLoginFieldsBanner.buttons.clearData.style.display = 'none';
|
||||
|
||||
sendMessageToFrames('clear_data_button_clicked');
|
||||
};
|
||||
|
||||
// Resets all selections and marked fields
|
||||
kpxcCustomLoginFieldsBanner.resetSelection = function() {
|
||||
kpxcCustomLoginFieldsBanner.selection = {
|
||||
username: undefined,
|
||||
password: undefined,
|
||||
totp: undefined,
|
||||
fields: []
|
||||
};
|
||||
|
||||
kpxcCustomLoginFieldsBanner.removeMarkedFields();
|
||||
};
|
||||
|
||||
kpxcCustomLoginFieldsBanner.removeMarkedFields = function() {
|
||||
while (kpxcCustomLoginFieldsBanner.chooser.firstChild) {
|
||||
kpxcCustomLoginFieldsBanner.chooser.firstChild.remove();
|
||||
}
|
||||
|
||||
kpxcCustomLoginFieldsBanner.markedFields = [];
|
||||
};
|
||||
|
||||
kpxcCustomLoginFieldsBanner.prepareUsernameSelection = function() {
|
||||
kpxcCustomLoginFieldsBanner.infoText.textContent = tr('defineChooseUsername');
|
||||
kpxcCustomLoginFieldsBanner.dataStep = STEP_SELECT_USERNAME;
|
||||
kpxcCustomLoginFieldsBanner.buttons.username.classList.remove(GRAY_BUTTON_CLASS);
|
||||
kpxcCustomLoginFieldsBanner.selectField('username');
|
||||
};
|
||||
|
||||
kpxcCustomLoginFieldsBanner.preparePasswordSelection = function() {
|
||||
kpxcCustomLoginFieldsBanner.infoText.textContent = tr('defineChoosePassword');
|
||||
kpxcCustomLoginFieldsBanner.dataStep = STEP_SELECT_PASSWORD;
|
||||
kpxcCustomLoginFieldsBanner.buttons.password.classList.remove(GRAY_BUTTON_CLASS);
|
||||
kpxcCustomLoginFieldsBanner.selectField('password');
|
||||
};
|
||||
|
||||
kpxcCustomLoginFieldsBanner.prepareTOTPSelection = function() {
|
||||
kpxcCustomLoginFieldsBanner.infoText.textContent = tr('defineChooseTOTP');
|
||||
kpxcCustomLoginFieldsBanner.dataStep = STEP_SELECT_TOTP;
|
||||
kpxcCustomLoginFieldsBanner.buttons.totp.classList.remove(GRAY_BUTTON_CLASS);
|
||||
kpxcCustomLoginFieldsBanner.selectField('totp');
|
||||
};
|
||||
|
||||
kpxcCustomLoginFieldsBanner.prepareStringFieldSelection = function() {
|
||||
kpxcCustomLoginFieldsBanner.infoText.textContent = tr('defineChooseStringFields');
|
||||
kpxcCustomLoginFieldsBanner.dataStep = STEP_SELECT_STRING_FIELDS;
|
||||
kpxcCustomLoginFieldsBanner.buttons.stringFields.classList.remove(GRAY_BUTTON_CLASS);
|
||||
kpxcCustomLoginFieldsBanner.selectStringFields();
|
||||
};
|
||||
|
||||
kpxcCustomLoginFieldsBanner.isFieldSelected = function(field) {
|
||||
const currentFieldId = kpxcFields.setId(field);
|
||||
|
||||
if (kpxcCustomLoginFieldsBanner.markedFields.some(f => f === field)) {
|
||||
return (
|
||||
(kpxcCustomLoginFieldsBanner.selection.username && kpxcCustomLoginFieldsBanner.selection.usernameElement === field)
|
||||
|| (kpxcCustomLoginFieldsBanner.selection.password && kpxcCustomLoginFieldsBanner.selection.passwordElement === field)
|
||||
|| (kpxcCustomLoginFieldsBanner.selection.totp && kpxcCustomLoginFieldsBanner.selection.totpElement === field)
|
||||
|| kpxcCustomLoginFieldsBanner.selection.fields.some(f => f[0] === currentFieldId[0])
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
kpxcCustomLoginFieldsBanner.getSelectedField = function(e, elem) {
|
||||
if (!e.isTrusted) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const field = elem || e.currentTarget;
|
||||
if (kpxcCustomLoginFieldsBanner.markedFields.includes(field.originalElement)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return field;
|
||||
};
|
||||
|
||||
kpxcCustomLoginFieldsBanner.setSelectedField = function(elem) {
|
||||
if (elem) {
|
||||
kpxcCustomLoginFieldsBanner.markedFields.push(elem);
|
||||
}
|
||||
|
||||
kpxcCustomLoginFieldsBanner.buttons.confirm.disabled = false;
|
||||
kpxcCustomLoginFieldsBanner.buttons.close.textContent = tr('optionsButtonCancel');
|
||||
};
|
||||
|
||||
// Expects 'username', 'password' or 'totp'
|
||||
kpxcCustomLoginFieldsBanner.selectField = function(fieldType) {
|
||||
kpxcCustomLoginFieldsBanner.eventFieldClick = function(e) {
|
||||
const field = kpxcCustomLoginFieldsBanner.getSelectedField(e);
|
||||
if (!field) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isLightThemeBackground(field.originalElement)) {
|
||||
field.classList.add(DARK_TEXT_CLASS);
|
||||
}
|
||||
|
||||
field.classList.add(`kpxcDefine-fixed-${fieldType}-field`);
|
||||
field.textContent = tr(fieldType);
|
||||
field.onclick = undefined;
|
||||
|
||||
kpxcCustomLoginFieldsBanner.selection[fieldType] = kpxcFields.setId(field.originalElement);
|
||||
kpxcCustomLoginFieldsBanner.selection[`${fieldType}Element`] = field.originalElement;
|
||||
kpxcCustomLoginFieldsBanner.setSelectedField(field.originalElement);
|
||||
kpxcCustomLoginFieldsBanner.backToStart();
|
||||
|
||||
kpxcCustomLoginFieldsBanner.buttons[fieldType].classList.add(GRAY_BUTTON_CLASS);
|
||||
sendMessageToParent(`${fieldType}_selected`, kpxcCustomLoginFieldsBanner.selection[fieldType]);
|
||||
};
|
||||
|
||||
kpxcCustomLoginFieldsBanner.markFields();
|
||||
};
|
||||
|
||||
kpxcCustomLoginFieldsBanner.selectStringFields = function() {
|
||||
kpxcCustomLoginFieldsBanner.eventFieldClick = function(e) {
|
||||
const field = kpxcCustomLoginFieldsBanner.getSelectedField(e);
|
||||
if (!field) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isLightThemeBackground(field.originalElement)) {
|
||||
field.classList.add(DARK_TEXT_CLASS);
|
||||
}
|
||||
|
||||
kpxcCustomLoginFieldsBanner.selection.fields.push(kpxcFields.setId(field.originalElement));
|
||||
kpxcCustomLoginFieldsBanner.setSelectedField(field.originalElement);
|
||||
|
||||
field.classList.add(STRING_FIELD_CLASS);
|
||||
field.textContent = `${tr('defineStringField')} #${String(kpxcCustomLoginFieldsBanner.selection.fields.length)}`;
|
||||
field.onclick = undefined;
|
||||
|
||||
kpxcCustomLoginFieldsBanner.buttons.stringFields.classList.add(GRAY_BUTTON_CLASS);
|
||||
sendMessageToParent('string_field_selected', kpxcCustomLoginFieldsBanner.selection.fields);
|
||||
};
|
||||
|
||||
kpxcCustomLoginFieldsBanner.markFields();
|
||||
};
|
||||
|
||||
kpxcCustomLoginFieldsBanner.markFields = function() {
|
||||
let index = 1;
|
||||
let firstInput;
|
||||
const inputs = document.querySelectorAll(kpxcCustomLoginFieldsBanner.inputQueryPattern);
|
||||
|
||||
for (const i of inputs) {
|
||||
if (kpxcCustomLoginFieldsBanner.isFieldSelected(i) || inputFieldIsSelected(i)) {
|
||||
// Switch texts to current selection type
|
||||
for (const selection of kpxcCustomLoginFieldsBanner.getNonSelectedElements()) {
|
||||
selection.textContent = dataStepToString();
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
const field = kpxcUI.createElement('div', FIXED_FIELD_CLASS);
|
||||
field.originalElement = i;
|
||||
|
||||
const rect = i.getBoundingClientRect();
|
||||
field.style.top = Pixels(rect.top);
|
||||
field.style.left = Pixels(rect.left);
|
||||
field.style.width = Pixels(rect.width);
|
||||
field.style.height = Pixels(rect.height);
|
||||
field.textContent = dataStepToString();
|
||||
|
||||
// Change selection theme if needed
|
||||
const isLightTheme = isLightThemeBackground(i);
|
||||
if (isLightTheme) {
|
||||
field.classList.add(DARK_FIXED_FIELD_CLASS);
|
||||
}
|
||||
|
||||
field.addEventListener('click', function(e) {
|
||||
kpxcCustomLoginFieldsBanner.eventFieldClick(e);
|
||||
});
|
||||
|
||||
field.addEventListener('mouseenter', function() {
|
||||
field.classList.add(isLightTheme ? DARK_HOVER_FIELD_CLASS : HOVER_FIELD_CLASS);
|
||||
});
|
||||
|
||||
field.addEventListener('mouseleave', function() {
|
||||
field.classList.remove(HOVER_FIELD_CLASS, DARK_HOVER_FIELD_CLASS);
|
||||
});
|
||||
|
||||
i.addEventListener('focus', function() {
|
||||
field.classList.add(isLightTheme ? DARK_HOVER_FIELD_CLASS : HOVER_FIELD_CLASS);
|
||||
});
|
||||
|
||||
i.addEventListener('blur', function() {
|
||||
field.classList.remove(HOVER_FIELD_CLASS, DARK_HOVER_FIELD_CLASS);
|
||||
});
|
||||
|
||||
if (kpxcCustomLoginFieldsBanner.chooser) {
|
||||
kpxcCustomLoginFieldsBanner.setSelectionPosition(field);
|
||||
kpxcCustomLoginFieldsBanner.monitorSelectionPosition(field);
|
||||
|
||||
kpxcCustomLoginFieldsBanner.chooser.append(field);
|
||||
firstInput = field;
|
||||
++index;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstInput) {
|
||||
firstInput.focus();
|
||||
}
|
||||
};
|
||||
|
||||
// Returns to start after a single selection
|
||||
kpxcCustomLoginFieldsBanner.backToStart = function() {
|
||||
removeContent(kpxcCustomLoginFieldsBanner.nonSelectedElementsPattern);
|
||||
kpxcCustomLoginFieldsBanner.infoText.textContent = tr('defineChooseCustomLoginFieldText');
|
||||
kpxcCustomLoginFieldsBanner.dataStep = STEP_NONE;
|
||||
|
||||
kpxcCustomLoginFieldsBanner.buttons.confirm.disabled = kpxcCustomLoginFieldsBanner.markedFields.length === 0;
|
||||
};
|
||||
|
||||
// Handle keyboard events
|
||||
kpxcCustomLoginFieldsBanner.keyDown = function(e) {
|
||||
if (!e.isTrusted) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Works as a cancel when selection process is active
|
||||
if (e.key === 'Escape') {
|
||||
if (kpxcCustomLoginFieldsBanner.dataStep === STEP_NONE) {
|
||||
kpxcCustomLoginFieldsBanner.destroy();
|
||||
} else {
|
||||
kpxcCustomLoginFieldsBanner.backToStart();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Detect page scroll or resize changes
|
||||
kpxcCustomLoginFieldsBanner.monitorSelectionPosition = function(selection) {
|
||||
// Handle icon position on resize
|
||||
window.addEventListener('resize', function(e) {
|
||||
kpxcCustomLoginFieldsBanner.setSelectionPosition(selection);
|
||||
});
|
||||
|
||||
// Handle icon position on scroll
|
||||
window.addEventListener('scroll', function(e) {
|
||||
kpxcCustomLoginFieldsBanner.setSelectionPosition(selection);
|
||||
});
|
||||
};
|
||||
|
||||
// Set selection input field position dynamically including the scroll position
|
||||
kpxcCustomLoginFieldsBanner.setSelectionPosition = function(field) {
|
||||
const rect = field.originalElement.getBoundingClientRect();
|
||||
const left = kpxcUI.getRelativeLeftPosition(rect);
|
||||
const top = kpxcUI.getRelativeTopPosition(rect);
|
||||
const scrollTop = document.scrollingElement ? document.scrollingElement.scrollTop : 0;
|
||||
const scrollLeft = document.scrollingElement ? document.scrollingElement.scrollLeft : 0;
|
||||
|
||||
field.style.top = Pixels(top + scrollTop);
|
||||
field.style.left = Pixels(left + scrollLeft);
|
||||
};
|
||||
|
||||
kpxcCustomLoginFieldsBanner.getNonSelectedElements = function() {
|
||||
return kpxcCustomLoginFieldsBanner.chooser.querySelectorAll(kpxcCustomLoginFieldsBanner.nonSelectedElementsPattern);
|
||||
};
|
||||
|
||||
const removeContent = function(pattern) {
|
||||
const elems = kpxcCustomLoginFieldsBanner.chooser.querySelectorAll(pattern);
|
||||
for (const e of elems) {
|
||||
e.remove();
|
||||
}
|
||||
};
|
||||
|
||||
const inputFieldIsSelected = function(field) {
|
||||
for (const child of kpxcCustomLoginFieldsBanner.chooser.children) {
|
||||
if (child.originalElement === field) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// Checks if an element has a light background, using luminance. Luminance with >= 127 is considered 'light'.
|
||||
const isLightThemeBackground = function(elem) {
|
||||
const inputStyle = getComputedStyle(elem);
|
||||
const bgColor = inputStyle.backgroundColor.match(/[\.\d]+/g).map(e => Number(e));
|
||||
if (bgColor.length < 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const luminance = 0.299 * bgColor[0] + 0.587 * bgColor[1] + 0.114 * bgColor[2];
|
||||
return luminance >= 128;
|
||||
};
|
||||
|
||||
const dataStepToString = function() {
|
||||
if (kpxcCustomLoginFieldsBanner.dataStep === STEP_SELECT_USERNAME) {
|
||||
return tr('username');
|
||||
} else if (kpxcCustomLoginFieldsBanner.dataStep === STEP_SELECT_PASSWORD) {
|
||||
return tr('password');
|
||||
} else if (kpxcCustomLoginFieldsBanner.dataStep === STEP_SELECT_TOTP) {
|
||||
return tr('totp');
|
||||
} else if (kpxcCustomLoginFieldsBanner.dataStep === STEP_SELECT_STRING_FIELDS) {
|
||||
return tr('defineStringField');
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// IFrame support
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
// A simple check for top-level-domain
|
||||
const topLevelDomainMatches = function(host) {
|
||||
if (!host) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const originUrl = new URL(host);
|
||||
const frameUrl = new URL(window.self.document.location.origin);
|
||||
const urlParts = originUrl.host.split('.');
|
||||
const dotCount = urlParts.length - 1;
|
||||
|
||||
// Simple host like google.com, check directly
|
||||
if (dotCount < 1) {
|
||||
return false;
|
||||
} else if (dotCount === 1) {
|
||||
return frameUrl.host.includes(originUrl.host);
|
||||
}
|
||||
|
||||
// Get the top-level-domain using counts of '.' but backwards, max 3.
|
||||
// A basic host is like idmsa.apple.com, a more complex one like www.bbva.com.ar.
|
||||
const index = Math.min(dotCount, 3);
|
||||
const subDomain = `${urlParts[dotCount - index]}.`;
|
||||
const topLevelDomain = originUrl.host.substring(originUrl.host.indexOf(subDomain) + subDomain.length);
|
||||
|
||||
return frameUrl.host.includes(topLevelDomain);
|
||||
};
|
||||
|
||||
// Handles messages sent from iframes to the top window
|
||||
const handleTopWindowMessage = function(e) {
|
||||
if (!topLevelDomainMatches(e.origin)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.data.message === 'username_selected') {
|
||||
kpxcCustomLoginFieldsBanner.selection.username = e.data.selection;
|
||||
kpxcCustomLoginFieldsBanner.setSelectedField();
|
||||
} else if (e.data.message === 'password_selected') {
|
||||
kpxcCustomLoginFieldsBanner.selection.password = e.data.selection;
|
||||
kpxcCustomLoginFieldsBanner.setSelectedField();
|
||||
} else if (e.data.message === 'totp_selected') {
|
||||
kpxcCustomLoginFieldsBanner.selection.totp = e.data.selection;
|
||||
kpxcCustomLoginFieldsBanner.setSelectedField();
|
||||
} else if (e.data.message === 'string_field_selected') {
|
||||
kpxcCustomLoginFieldsBanner.selection.stringFields = e.data.selection;
|
||||
kpxcCustomLoginFieldsBanner.setSelectedField();
|
||||
} else if (e.data.message === 'enable_clear_data_button') {
|
||||
kpxcCustomLoginFieldsBanner.buttons.clearData.style.display = 'inline-block';
|
||||
}
|
||||
};
|
||||
|
||||
// Handle Banner button clicks from the top window
|
||||
const handleParentWindowMessage = function(e) {
|
||||
if (!topLevelDomainMatches(e.origin)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.data === 'username_button_clicked') {
|
||||
kpxcCustomLoginFieldsBanner.usernameButtonClicked(e);
|
||||
} else if (e.data === 'password_button_clicked') {
|
||||
kpxcCustomLoginFieldsBanner.passwordButtonClicked(e);
|
||||
} else if (e.data === 'totp_button_clicked') {
|
||||
kpxcCustomLoginFieldsBanner.totpButtonClicked(e);
|
||||
} else if (e.data === 'string_field_button_clicked') {
|
||||
kpxcCustomLoginFieldsBanner.stringFieldsButtonClicked(e);
|
||||
} else if (e.data === 'reset_button_clicked') {
|
||||
kpxcCustomLoginFieldsBanner.reset();
|
||||
} else if (e.data === 'close_button_clicked') {
|
||||
kpxcCustomLoginFieldsBanner.closeButtonClicked(e);
|
||||
} else if (e.data === 'confirm_button_clicked') {
|
||||
kpxcCustomLoginFieldsBanner.confirm();
|
||||
} else if (e.data === 'clear_data_button_clicked') {
|
||||
kpxcCustomLoginFieldsBanner.clearData();
|
||||
}
|
||||
};
|
||||
|
||||
// Sends messages to all iframes. Works only from the top window.
|
||||
const sendMessageToFrames = function(message) {
|
||||
if (window.self === window.top) {
|
||||
for (var i = 0; i < window.frames.length; i++) {
|
||||
frames[i].postMessage(message, '*');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Sends message to parent window. Works only from iframes.
|
||||
const sendMessageToParent = function(message, selection) {
|
||||
if (window.self !== window.top) {
|
||||
window.top.postMessage({ message, selection }, '*');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,496 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var kpxcDefine = {};
|
||||
|
||||
kpxcDefine.selection = {
|
||||
username: null,
|
||||
password: null,
|
||||
totp: null,
|
||||
fields: []
|
||||
};
|
||||
|
||||
kpxcDefine.buttons = {
|
||||
again: undefined,
|
||||
confirm: undefined,
|
||||
discard: undefined,
|
||||
dismiss: undefined,
|
||||
more: undefined,
|
||||
skip: undefined
|
||||
};
|
||||
|
||||
kpxcDefine.backdrop = undefined;
|
||||
kpxcDefine.chooser = undefined;
|
||||
kpxcDefine.dialog = undefined;
|
||||
kpxcDefine.diffX = 0;
|
||||
kpxcDefine.diffY = 0;
|
||||
kpxcDefine.discardSection = undefined;
|
||||
kpxcDefine.eventFieldClick = undefined;
|
||||
kpxcDefine.headline = undefined;
|
||||
kpxcDefine.help = undefined;
|
||||
kpxcDefine.inputQueryPattern = 'input[type=email], input[type=number], input[type=password], input[type=tel], input[type=text], input[type=username], input:not([type])';
|
||||
kpxcDefine.keyboardSelectorPattern = 'div.kpxcDefine-fixed-field:not(.kpxcDefine-fixed-username-field):not(.kpxcDefine-fixed-password-field):not(.kpxcDefine-fixed-totp-field)';
|
||||
kpxcDefine.moreInputQueryPattern = 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=date]):not([type=datetime-local]):not([type=file]):not([type=hidden]):not([type=image]):not([type=month]):not([type=range]):not([type=reset]):not([type=submit]):not([type=time]):not([type=week]), select, textarea';
|
||||
kpxcDefine.markedFields = [];
|
||||
kpxcDefine.keyDown = undefined;
|
||||
kpxcDefine.startPosX = 0;
|
||||
kpxcDefine.startPosY = 0;
|
||||
|
||||
kpxcDefine.init = async function() {
|
||||
kpxcDefine.backdrop = kpxcUI.createElement('div', 'kpxcDefine-modal-backdrop', { 'id': 'kpxcDefine-backdrop' });
|
||||
kpxcDefine.chooser = kpxcUI.createElement('div', '', { 'id': 'kpxcDefine-fields' });
|
||||
kpxcDefine.dialog = kpxcUI.createElement('div', '', { 'id': 'kpxcDefine-description' });
|
||||
kpxcDefine.backdrop.append(kpxcDefine.dialog);
|
||||
|
||||
const styleSheet = createStylesheet('css/define.css');
|
||||
const buttonStyleSheet = createStylesheet('css/button.css');
|
||||
const wrapper = document.createElement('div');
|
||||
|
||||
this.shadowRoot = wrapper.attachShadow({ mode: 'closed' });
|
||||
this.shadowRoot.append(styleSheet);
|
||||
this.shadowRoot.append(buttonStyleSheet);
|
||||
this.shadowRoot.append(kpxcDefine.backdrop);
|
||||
this.shadowRoot.append(kpxcDefine.chooser);
|
||||
document.body.append(wrapper);
|
||||
|
||||
kpxcDefine.initDescription();
|
||||
kpxcDefine.resetSelection();
|
||||
kpxcDefine.prepareStep1();
|
||||
kpxcDefine.markAllUsernameFields();
|
||||
|
||||
kpxcDefine.dialog.onmousedown = function(e) {
|
||||
kpxcDefine.mouseDown(e);
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', kpxcDefine.keyDown);
|
||||
};
|
||||
|
||||
kpxcDefine.close = function() {
|
||||
kpxcDefine.backdrop.remove();
|
||||
kpxcDefine.chooser.remove();
|
||||
document.removeEventListener('keydown', kpxcDefine.keyDown);
|
||||
};
|
||||
|
||||
kpxcDefine.mouseDown = function(e) {
|
||||
kpxcDefine.selected = kpxcDefine.dialog;
|
||||
kpxcDefine.startPosX = e.clientX;
|
||||
kpxcDefine.startPosY = e.clientY;
|
||||
kpxcDefine.diffX = kpxcDefine.startPosX - kpxcDefine.dialog.offsetLeft;
|
||||
kpxcDefine.diffY = kpxcDefine.startPosY - kpxcDefine.dialog.offsetTop;
|
||||
return false;
|
||||
};
|
||||
|
||||
kpxcDefine.initDescription = function() {
|
||||
const description = kpxcDefine.dialog;
|
||||
kpxcDefine.headline = kpxcUI.createElement('div', '', { 'id': 'kpxcDefine-chooser-headline' });
|
||||
kpxcDefine.help = kpxcUI.createElement('div', 'kpxcDefine-chooser-help', { 'id': 'kpxcDefine-help' });
|
||||
|
||||
// Show keyboard shortcuts help text
|
||||
const keyboardHelp = kpxcUI.createElement('div', 'kpxcDefine-keyboardHelp', {}, `${tr('optionsKeyboardShortcutsHeader')}:`);
|
||||
keyboardHelp.style.marginBottom = '5px';
|
||||
keyboardHelp.appendMultiple(document.createElement('br'), kpxcUI.createElement('kbd', '', {}, 'Escape'), ' ' + tr('defineDismiss'));
|
||||
keyboardHelp.appendMultiple(document.createElement('br'), kpxcUI.createElement('kbd', '', {}, 'S'), ' ' + tr('defineSkip'));
|
||||
keyboardHelp.appendMultiple(document.createElement('br'), kpxcUI.createElement('kbd', '', {}, 'A'), ' ' + tr('defineAgain'));
|
||||
keyboardHelp.appendMultiple(document.createElement('br'), kpxcUI.createElement('kbd', '', {}, 'C'), ' ' + tr('defineConfirm'));
|
||||
keyboardHelp.appendMultiple(document.createElement('br'), kpxcUI.createElement('kbd', '', {}, 'M'), ' ' + tr('defineMore'));
|
||||
keyboardHelp.appendMultiple(document.createElement('br'), kpxcUI.createElement('kbd', '', {}, 'D'), ' ' + tr('defineDiscard'));
|
||||
|
||||
description.appendMultiple(kpxcDefine.headline, kpxcDefine.help, keyboardHelp);
|
||||
|
||||
const buttonDismiss = kpxcUI.createElement('button', 'kpxc-button kpxc-red-button', { 'id': 'kpxcDefine-btn-dismiss' }, tr('defineDismiss'));
|
||||
buttonDismiss.addEventListener('click', kpxcDefine.close);
|
||||
|
||||
const buttonSkip = kpxcUI.createElement('button', 'kpxc-button kpxc-orange-button', { 'id': 'kpxcDefine-btn-skip' }, tr('defineSkip'));
|
||||
buttonSkip.style.marginRight = '5px';
|
||||
buttonSkip.addEventListener('click', kpxcDefine.skip);
|
||||
|
||||
const buttonMore = kpxcUI.createElement('button', 'kpxc-button kpxc-orange-button', { 'id': 'kpxcDefine-btn-more' }, tr('defineMore'));
|
||||
buttonMore.style.marginRight = '5px';
|
||||
buttonMore.style.marginLeft = '5px';
|
||||
buttonMore.addEventListener('click', kpxcDefine.more);
|
||||
|
||||
const buttonAgain = kpxcUI.createElement('button', 'kpxc-button kpxc-blue-button', { 'id': 'kpxcDefine-btn-again' }, tr('defineAgain'));
|
||||
buttonAgain.style.marginRight = '5px';
|
||||
buttonAgain.addEventListener('click', kpxcDefine.again);
|
||||
|
||||
const buttonConfirm = kpxcUI.createElement('button', 'kpxc-button kpxc-green-button', { 'id': 'kpxcDefine-btn-confirm' }, tr('defineConfirm'));
|
||||
buttonConfirm.style.marginRight = '15px';
|
||||
buttonConfirm.style.display = 'none';
|
||||
buttonConfirm.addEventListener('click', kpxcDefine.confirm);
|
||||
|
||||
kpxcDefine.buttons.again = buttonAgain;
|
||||
kpxcDefine.buttons.confirm = buttonConfirm;
|
||||
kpxcDefine.buttons.dismiss = buttonDismiss;
|
||||
kpxcDefine.buttons.more = buttonMore;
|
||||
kpxcDefine.buttons.skip = buttonSkip;
|
||||
description.appendMultiple(buttonConfirm, buttonSkip, buttonMore, buttonAgain, buttonDismiss);
|
||||
|
||||
const location = kpxc.getDocumentLocation();
|
||||
if (kpxc.settings['defined-custom-fields'] && kpxc.settings['defined-custom-fields'][location]) {
|
||||
const div = kpxcUI.createElement('div', 'alreadySelected', {});
|
||||
const defineDiscard = kpxcUI.createElement('p', '', {}, tr('defineAlreadySelected'));
|
||||
const buttonDiscard = kpxcUI.createElement('button', 'kpxc-button kpxc-red-button', { 'id': 'kpxcDefine-btn-discard' }, tr('defineDiscard'));
|
||||
buttonDiscard.style.marginTop = '5px';
|
||||
buttonDiscard.addEventListener('click', kpxcDefine.discard);
|
||||
kpxcDefine.buttons.discard = buttonSkip;
|
||||
kpxcDefine.discardSection = div;
|
||||
|
||||
div.appendMultiple(defineDiscard, buttonDiscard);
|
||||
description.append(div);
|
||||
}
|
||||
};
|
||||
|
||||
kpxcDefine.resetSelection = function() {
|
||||
kpxcDefine.selection = {
|
||||
username: null,
|
||||
password: null,
|
||||
totp: null,
|
||||
fields: []
|
||||
};
|
||||
|
||||
kpxcDefine.markedFields = [];
|
||||
|
||||
if (kpxcDefine.chooser) {
|
||||
kpxcDefine.chooser.textContent = '';
|
||||
}
|
||||
};
|
||||
|
||||
kpxcDefine.isFieldSelected = function(field) {
|
||||
if (kpxcDefine.markedFields.some(f => f === field)) {
|
||||
return (
|
||||
(kpxcDefine.selection.username && kpxcDefine.selection.username.originalElement === field)
|
||||
|| (kpxcDefine.selection.password && kpxcDefine.selection.password.originalElement === field)
|
||||
|| (kpxcDefine.selection.totp && kpxcDefine.selection.totp.originalElement === field)
|
||||
|| kpxcDefine.selection.fields.includes(field)
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
kpxcDefine.markAllUsernameFields = function() {
|
||||
kpxcDefine.eventFieldClick = function(e, elem) {
|
||||
if (!e.isTrusted) {
|
||||
return;
|
||||
}
|
||||
|
||||
const field = elem || e.currentTarget;
|
||||
field.classList.add('kpxcDefine-fixed-username-field');
|
||||
field.textContent = tr('username');
|
||||
field.onclick = null;
|
||||
kpxcDefine.selection.username = field;
|
||||
kpxcDefine.markedFields.push(field.originalElement);
|
||||
|
||||
kpxcDefine.prepareStep2();
|
||||
kpxcDefine.markAllPasswordFields();
|
||||
};
|
||||
|
||||
kpxcDefine.markFields(kpxcDefine.inputQueryPattern);
|
||||
};
|
||||
|
||||
kpxcDefine.markAllPasswordFields = function() {
|
||||
kpxcDefine.eventFieldClick = function(e, elem) {
|
||||
if (!e.isTrusted) {
|
||||
return;
|
||||
}
|
||||
|
||||
const field = elem || e.currentTarget;
|
||||
field.classList.add('kpxcDefine-fixed-password-field');
|
||||
field.textContent = tr('password');
|
||||
field.onclick = null;
|
||||
kpxcDefine.selection.password = field;
|
||||
kpxcDefine.markedFields.push(field.originalElement);
|
||||
|
||||
kpxcDefine.prepareStep3();
|
||||
kpxcDefine.markAllTOTPFields();
|
||||
};
|
||||
|
||||
kpxcDefine.markFields('input[type=\'password\']');
|
||||
};
|
||||
|
||||
kpxcDefine.markAllStringFields = function() {
|
||||
kpxcDefine.eventFieldClick = function(e, elem) {
|
||||
if (!e.isTrusted) {
|
||||
return;
|
||||
}
|
||||
|
||||
const field = elem || e.currentTarget;
|
||||
if (kpxcDefine.isFieldSelected(field.originalElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
kpxcDefine.selection.fields.push(field.originalElement);
|
||||
kpxcDefine.markedFields.push(field.originalElement);
|
||||
|
||||
field.classList.add('kpxcDefine-fixed-string-field');
|
||||
field.textContent = tr('defineStringField') + String(kpxcDefine.selection.fields.length);
|
||||
field.onclick = null;
|
||||
};
|
||||
|
||||
kpxcDefine.markFields(kpxcDefine.inputQueryPattern + ', select');
|
||||
};
|
||||
|
||||
kpxcDefine.markAllTOTPFields = function() {
|
||||
kpxcDefine.eventFieldClick = function(e, elem) {
|
||||
if (!e.isTrusted) {
|
||||
return;
|
||||
}
|
||||
|
||||
const field = elem || e.currentTarget;
|
||||
field.classList.add('kpxcDefine-fixed-totp-field');
|
||||
field.textContent = 'TOTP';
|
||||
field.onclick = null;
|
||||
kpxcDefine.selection.totp = field;
|
||||
kpxcDefine.markedFields.push(field.originalElement);
|
||||
|
||||
kpxcDefine.prepareStep4();
|
||||
kpxcDefine.markAllStringFields();
|
||||
};
|
||||
|
||||
kpxcDefine.markFields(kpxcDefine.inputQueryPattern);
|
||||
};
|
||||
|
||||
kpxcDefine.markFields = function(pattern) {
|
||||
let index = 1;
|
||||
let firstInput = null;
|
||||
const inputs = document.querySelectorAll(pattern);
|
||||
|
||||
for (const i of inputs) {
|
||||
if (kpxcDefine.isFieldSelected(i)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!kpxcFields.isVisible(i)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const field = kpxcUI.createElement('div', 'kpxcDefine-fixed-field');
|
||||
field.originalElement = i;
|
||||
|
||||
const rect = i.getBoundingClientRect();
|
||||
field.style.top = Pixels(rect.top);
|
||||
field.style.left = Pixels(rect.left);
|
||||
field.style.width = Pixels(rect.width);
|
||||
field.style.height = Pixels(rect.height);
|
||||
field.textContent = String(index);
|
||||
|
||||
field.addEventListener('click', function(e) {
|
||||
kpxcDefine.eventFieldClick(e);
|
||||
});
|
||||
|
||||
field.addEventListener('mouseenter', function() {
|
||||
field.classList.add('kpxcDefine-fixed-hover-field');
|
||||
});
|
||||
|
||||
field.addEventListener('mouseleave', function() {
|
||||
field.classList.remove('kpxcDefine-fixed-hover-field');
|
||||
});
|
||||
|
||||
i.addEventListener('focus', function() {
|
||||
field.classList.add('kpxcDefine-fixed-hover-field');
|
||||
});
|
||||
|
||||
i.addEventListener('blur', function() {
|
||||
field.classList.remove('kpxcDefine-fixed-hover-field');
|
||||
});
|
||||
|
||||
if (kpxcDefine.chooser) {
|
||||
kpxcDefine.chooser.append(field);
|
||||
firstInput = field;
|
||||
++index;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstInput) {
|
||||
firstInput.focus();
|
||||
}
|
||||
};
|
||||
|
||||
kpxcDefine.prepareStep1 = function() {
|
||||
kpxcDefine.help.style.marginBottom = '10px';
|
||||
kpxcDefine.help.textContent = tr('defineKeyboardText');
|
||||
|
||||
removeContent('div#kpxcDefine-fixed-field');
|
||||
kpxcDefine.headline.textContent = tr('defineChooseUsername');
|
||||
kpxcDefine.dataStep = 1;
|
||||
|
||||
kpxcDefine.buttons.skip.style.display = 'inline-block';
|
||||
kpxcDefine.buttons.confirm.style.display = 'none';
|
||||
kpxcDefine.buttons.again.style.display = 'none';
|
||||
kpxcDefine.buttons.more.style.display = 'none';
|
||||
};
|
||||
|
||||
kpxcDefine.prepareStep2 = function() {
|
||||
const help = kpxcDefine.help;
|
||||
help.style.marginBottom = '10px';
|
||||
help.textContent = tr('defineKeyboardText');
|
||||
|
||||
removeContent('div.kpxcDefine-fixed-field:not(.kpxcDefine-fixed-username-field)');
|
||||
removeContent('div.kpxcDefine-fixed.field');
|
||||
kpxcDefine.headline.textContent = tr('defineChoosePassword');
|
||||
kpxcDefine.dataStep = 2;
|
||||
kpxcDefine.buttons.again.style.display = 'inline-block';
|
||||
kpxcDefine.buttons.more.style.display = 'inline-block';
|
||||
};
|
||||
|
||||
kpxcDefine.prepareStep3 = function() {
|
||||
kpxcDefine.help.style.marginBottom = '10px';
|
||||
kpxcDefine.help.textContent = tr('defineHelpText');
|
||||
|
||||
removeContent('div.kpxcDefine-fixed-field:not(.kpxcDefine-fixed-username-field):not(.kpxcDefine-fixed-password-field)');
|
||||
kpxcDefine.headline.textContent = tr('defineChooseTOTP');
|
||||
kpxcDefine.dataStep = 3;
|
||||
kpxcDefine.buttons.skip.style.display = 'inline-block';
|
||||
kpxcDefine.buttons.again.style.display = 'inline-block';
|
||||
kpxcDefine.buttons.more.style.display = 'none';
|
||||
kpxcDefine.buttons.confirm.style.display = 'none';
|
||||
};
|
||||
|
||||
kpxcDefine.prepareStep4 = function() {
|
||||
kpxcDefine.help.style.marginBottom = '10px';
|
||||
kpxcDefine.help.textContent = tr('defineHelpText');
|
||||
|
||||
removeContent('div.kpxcDefine-fixed-field:not(.kpxcDefine-fixed-username-field):not(.kpxcDefine-fixed-password-field):not(.kpxcDefine-fixed-totp-field):not(.kpxcDefine-fixed-string-field)');
|
||||
kpxcDefine.headline.textContent = tr('defineConfirmSelection');
|
||||
kpxcDefine.dataStep = 4;
|
||||
kpxcDefine.buttons.skip.style.display = 'none';
|
||||
kpxcDefine.buttons.more.style.display = 'none';
|
||||
kpxcDefine.buttons.again.style.display = 'inline-block';
|
||||
kpxcDefine.buttons.confirm.style.display = 'inline-block';
|
||||
};
|
||||
|
||||
kpxcDefine.skip = function() {
|
||||
if (kpxcDefine.dataStep === 1) {
|
||||
kpxcDefine.selection.username = null;
|
||||
kpxcDefine.prepareStep2();
|
||||
kpxcDefine.markAllPasswordFields();
|
||||
} else if (kpxcDefine.dataStep === 2) {
|
||||
kpxcDefine.selection.password = null;
|
||||
kpxcDefine.prepareStep3();
|
||||
kpxcDefine.markAllTOTPFields();
|
||||
} else if (kpxcDefine.dataStep === 3) {
|
||||
kpxcDefine.selection.totp = null;
|
||||
kpxcDefine.prepareStep4();
|
||||
kpxcDefine.markAllStringFields();
|
||||
}
|
||||
};
|
||||
|
||||
kpxcDefine.again = function() {
|
||||
kpxcDefine.resetSelection();
|
||||
kpxcDefine.prepareStep1();
|
||||
kpxcDefine.markAllUsernameFields();
|
||||
};
|
||||
|
||||
kpxcDefine.more = function() {
|
||||
if (kpxcDefine.dataStep === 1) {
|
||||
kpxcDefine.prepareStep1();
|
||||
|
||||
// Reset previous marked fields when no usernames have been selected
|
||||
if (kpxcDefine.markedFields.length === 0) {
|
||||
kpxcDefine.resetSelection();
|
||||
}
|
||||
} else if (kpxcDefine.dataStep === 2) {
|
||||
kpxcDefine.prepareStep2();
|
||||
} else if (kpxcDefine.dataStep === 3) {
|
||||
kpxcDefine.prepareStep3();
|
||||
} else if (kpxcDefine.dataStep === 4) {
|
||||
kpxcDefine.prepareStep4();
|
||||
}
|
||||
|
||||
kpxcDefine.markFields(kpxcDefine.moreInputQueryPattern);
|
||||
};
|
||||
|
||||
kpxcDefine.confirm = async function() {
|
||||
if (kpxcDefine.dataStep !== 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!kpxc.settings['defined-custom-fields']) {
|
||||
kpxc.settings['defined-custom-fields'] = {};
|
||||
}
|
||||
|
||||
if (kpxcDefine.selection.username) {
|
||||
kpxcDefine.selection.username = kpxcFields.setId(kpxcDefine.selection.username.originalElement);
|
||||
}
|
||||
|
||||
if (kpxcDefine.selection.password) {
|
||||
kpxcDefine.selection.password = kpxcFields.setId(kpxcDefine.selection.password.originalElement);
|
||||
}
|
||||
|
||||
if (kpxcDefine.selection.totp) {
|
||||
kpxcDefine.selection.totp = kpxcFields.setId(kpxcDefine.selection.totp.originalElement);
|
||||
}
|
||||
|
||||
const fieldIds = [];
|
||||
for (const i of kpxcDefine.selection.fields) {
|
||||
fieldIds.push(kpxcFields.setId(i));
|
||||
}
|
||||
|
||||
const location = kpxc.getDocumentLocation();
|
||||
kpxc.settings['defined-custom-fields'][location] = {
|
||||
username: kpxcDefine.selection.username,
|
||||
password: kpxcDefine.selection.password,
|
||||
totp: kpxcDefine.selection.totp,
|
||||
fields: fieldIds
|
||||
};
|
||||
|
||||
await sendMessage('save_settings', kpxc.settings);
|
||||
kpxcDefine.close();
|
||||
};
|
||||
|
||||
kpxcDefine.discard = async function() {
|
||||
if (!kpxcDefine.buttons.discard) {
|
||||
return;
|
||||
}
|
||||
|
||||
const location = kpxc.getDocumentLocation();
|
||||
delete kpxc.settings['defined-custom-fields'][location];
|
||||
|
||||
await sendMessage('save_settings', kpxc.settings);
|
||||
await sendMessage('load_settings');
|
||||
|
||||
kpxcDefine.discardSection.remove();
|
||||
};
|
||||
|
||||
// Handle keyboard events
|
||||
kpxcDefine.keyDown = function(e) {
|
||||
if (!e.isTrusted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === 'Escape') {
|
||||
kpxcDefine.close();
|
||||
} else if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
} else if (e.keyCode >= 49 && e.keyCode <= 57) {
|
||||
// Select input field by number
|
||||
e.preventDefault();
|
||||
const index = e.keyCode - 48;
|
||||
const inputFields = document.querySelectorAll(kpxcDefine.keyboardSelectorPattern);
|
||||
|
||||
if (inputFields.length >= index) {
|
||||
kpxcDefine.eventFieldClick(e, inputFields[index - 1]);
|
||||
}
|
||||
} else if (e.key === 's') {
|
||||
e.preventDefault();
|
||||
kpxcDefine.skip();
|
||||
} else if (e.key === 'a') {
|
||||
e.preventDefault();
|
||||
kpxcDefine.again();
|
||||
} else if (e.key === 'c') {
|
||||
e.preventDefault();
|
||||
kpxcDefine.confirm();
|
||||
} else if (e.key === 'm') {
|
||||
e.preventDefault();
|
||||
kpxcDefine.more();
|
||||
} else if (e.key === 'd') {
|
||||
e.preventDefault();
|
||||
kpxcDefine.discard();
|
||||
}
|
||||
};
|
||||
|
||||
const removeContent = function(pattern) {
|
||||
const elems = kpxcDefine.chooser.querySelectorAll(pattern);
|
||||
for (const e of elems) {
|
||||
e.remove();
|
||||
}
|
||||
};
|
||||
|
|
@ -62,6 +62,31 @@ kpxcFields.getAllCombinations = async function(inputs) {
|
|||
return combinations;
|
||||
};
|
||||
|
||||
// If there are multiple combinations, return the first one where input field can be found inside the document.
|
||||
// Used with Custom Login Fields where selected input fields might not be visible on the page yet,
|
||||
// and there's an extra combination for those. Only used from popup fill.
|
||||
kpxcFields.getCombinationFromAllInputs = function() {
|
||||
const inputs = kpxcObserverHelper.getInputs(document.body);
|
||||
|
||||
for (const combination of kpxc.combinations) {
|
||||
for (const value of Object.values(combination)) {
|
||||
if (Array.isArray(value)) {
|
||||
for (const v of value) {
|
||||
if (inputs.some(i => i === v)) {
|
||||
return combination;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (inputs.some(i => i === value)) {
|
||||
return combination;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return kpxc.combinations[0];
|
||||
};
|
||||
|
||||
// Adds segmented TOTP fields to the combination if found
|
||||
kpxcFields.getSegmentedTOTPFields = function(inputs, combinations) {
|
||||
if (!kpxc.settings.showOTPIcon) {
|
||||
|
|
@ -397,12 +422,6 @@ kpxcFields.useCustomLoginFields = async function() {
|
|||
kpxcTOTPIcons.newIcon(totp, kpxc.databaseState);
|
||||
}
|
||||
|
||||
// If not all expected fields are identified, return an empty combination
|
||||
if ((creds.username && !username) || (creds.password && !password) || (creds.totp && !totp)
|
||||
|| (creds.fields.length !== stringFields.length)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const combinations = [];
|
||||
combinations.push({
|
||||
username: username,
|
||||
|
|
|
|||
|
|
@ -111,7 +111,8 @@ kpxcFill.fillFromPopup = async function(id, uuid) {
|
|||
combination = kpxc.combinations[1];
|
||||
}
|
||||
|
||||
kpxcFill.fillInCredentials(combination, selectedCredentials.login, uuid);
|
||||
const foundCombination = kpxcFields.getCombinationFromAllInputs();
|
||||
kpxcFill.fillInCredentials(foundCombination, selectedCredentials.login, uuid);
|
||||
kpxcUserAutocomplete.closeList();
|
||||
};
|
||||
|
||||
|
|
@ -222,7 +223,7 @@ kpxcFill.fillInCredentials = async function(combination, predefinedUsername, uui
|
|||
return;
|
||||
}
|
||||
|
||||
if (!combination || (!combination.username && !combination.password)) {
|
||||
if (!combination) {
|
||||
logDebug('Error: Empty login combination.');
|
||||
return;
|
||||
}
|
||||
|
|
@ -286,8 +287,12 @@ kpxcFill.fillInStringFields = function(fields, stringFields) {
|
|||
const filledInFields = [];
|
||||
if (fields && stringFields && fields.length > 0 && stringFields.length > 0) {
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
const currentField = fields[i];
|
||||
if (i >= stringFields.length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const stringFieldValue = Object.values(stringFields[i]);
|
||||
const currentField = fields[i];
|
||||
|
||||
if (currentField && stringFieldValue[0]) {
|
||||
kpxc.setValue(currentField, stringFieldValue[0]);
|
||||
|
|
|
|||
|
|
@ -255,11 +255,11 @@ kpxc.initAutocomplete = function() {
|
|||
|
||||
// Looks for any username & password combinations from the detected input fields
|
||||
kpxc.initCombinations = async function(inputs = []) {
|
||||
if (inputs.length === 0) {
|
||||
const isCustomLoginFieldsUsed = kpxcFields.isCustomLoginFieldsUsed();
|
||||
if (inputs.length === 0 && !isCustomLoginFieldsUsed) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const isCustomLoginFieldsUsed = kpxcFields.isCustomLoginFieldsUsed();
|
||||
const combinations = isCustomLoginFieldsUsed
|
||||
? await kpxcFields.useCustomLoginFields()
|
||||
: await kpxcFields.getAllCombinations(inputs);
|
||||
|
|
@ -285,6 +285,11 @@ kpxc.initCombinations = async function(inputs = []) {
|
|||
}
|
||||
}
|
||||
|
||||
// Update the fields in Custom Login Fields banner if it's open
|
||||
if (kpxcCustomLoginFieldsBanner.created) {
|
||||
kpxcCustomLoginFieldsBanner.updateFieldSelections();
|
||||
}
|
||||
|
||||
logDebug('Login field combinations identified:', combinations);
|
||||
return combinations;
|
||||
};
|
||||
|
|
@ -296,7 +301,7 @@ kpxc.initCredentialFields = async function() {
|
|||
|
||||
// Search all remaining inputs from the page, ignore the previous input fields
|
||||
const pageInputs = await kpxcFields.getAllPageInputs(formInputs);
|
||||
if (formInputs.length === 0 && pageInputs.length === 0) {
|
||||
if (formInputs.length === 0 && pageInputs.length === 0 && !kpxcFields.isCustomLoginFieldsUsed()) {
|
||||
// Run 'redetect_credentials' manually if no fields are found after a page load
|
||||
setTimeout(async function() {
|
||||
if (_called.automaticRedetectCompleted) {
|
||||
|
|
@ -821,7 +826,7 @@ browser.runtime.onMessage.addListener(async function(req, sender) {
|
|||
} else if (req.action === 'check_database_hash' && 'hash' in req) {
|
||||
kpxc.detectDatabaseChange(req);
|
||||
} else if (req.action === 'choose_credential_fields') {
|
||||
kpxcDefine.init();
|
||||
kpxcCustomLoginFieldsBanner.create();
|
||||
} else if (req.action === 'clear_credentials') {
|
||||
kpxc.clearAllFromPage();
|
||||
} else if (req.action === 'fill_user_pass_with_specific_login') {
|
||||
|
|
|
|||
|
|
@ -7,15 +7,15 @@ const MIN_INPUT_FIELD_OFFSET_WIDTH = 60;
|
|||
const MIN_OPACITY = 0.7;
|
||||
const MAX_OPACITY = 1;
|
||||
|
||||
let notificationWrapper;
|
||||
let notificationTimeout;
|
||||
|
||||
const DatabaseState = {
|
||||
DISCONNECTED: 0,
|
||||
LOCKED: 1,
|
||||
UNLOCKED: 2
|
||||
};
|
||||
|
||||
let notificationWrapper;
|
||||
let notificationTimeout;
|
||||
|
||||
// jQuery style wrapper for querySelector()
|
||||
const $ = function(elem) {
|
||||
return document.querySelector(elem);
|
||||
|
|
@ -122,8 +122,8 @@ kpxcUI.setIconPosition = function(icon, field, rtl = false, segmented = false) {
|
|||
const rect = field.getBoundingClientRect();
|
||||
const size = Number(icon.getAttribute('size'));
|
||||
const offset = kpxcUI.calculateIconOffset(field, size);
|
||||
let left = kpxcUI.bodyStyle.position.toLowerCase() === 'relative' ? rect.left - kpxcUI.bodyRect.left : rect.left;
|
||||
let top = kpxcUI.bodyStyle.position.toLowerCase() === 'relative' ? rect.top - kpxcUI.bodyRect.top : rect.top;
|
||||
let left = kpxcUI.getRelativeLeftPosition(rect);
|
||||
let top = kpxcUI.getRelativeTopPosition(rect);
|
||||
|
||||
// Add more space for the icon to show it at the right side of the field if TOTP fields are segmented
|
||||
if (segmented) {
|
||||
|
|
@ -145,6 +145,14 @@ kpxcUI.setIconPosition = function(icon, field, rtl = false, segmented = false) {
|
|||
: Pixels(left + scrollLeft + field.offsetWidth - size - offset);
|
||||
};
|
||||
|
||||
kpxcUI.getRelativeLeftPosition = function(rect) {
|
||||
return kpxcUI.bodyStyle.position.toLowerCase() === 'relative' ? rect.left - kpxcUI.bodyRect.left : rect.left;
|
||||
};
|
||||
|
||||
kpxcUI.getRelativeTopPosition = function(rect) {
|
||||
return kpxcUI.bodyStyle.position.toLowerCase() === 'relative' ? rect.top - kpxcUI.bodyRect.top : rect.top;
|
||||
};
|
||||
|
||||
kpxcUI.deleteHiddenIcons = function(iconList, attr) {
|
||||
const deletedIcons = [];
|
||||
for (const icon of iconList) {
|
||||
|
|
@ -260,6 +268,12 @@ kpxcUI.createNotification = function(type, message) {
|
|||
}, 5000);
|
||||
};
|
||||
|
||||
kpxcUI.createButton = function(color, textContent, callback) {
|
||||
const button = kpxcUI.createElement('button', color, {}, textContent);
|
||||
button.addEventListener('click', callback);
|
||||
return button;
|
||||
};
|
||||
|
||||
const DOMRectToArray = function(domRect) {
|
||||
return [ domRect.bottom, domRect.height, domRect.left, domRect.right, domRect.top, domRect.width, domRect.x, domRect.y ];
|
||||
};
|
||||
|
|
@ -267,8 +281,11 @@ const DOMRectToArray = function(domRect) {
|
|||
const initColorTheme = function(elem) {
|
||||
const colorTheme = kpxc.settings['colorTheme'];
|
||||
|
||||
if (colorTheme === undefined || colorTheme === 'system') {
|
||||
if (colorTheme === undefined) {
|
||||
elem.removeAttribute('data-color-theme');
|
||||
} else if (colorTheme === 'system') {
|
||||
const theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
elem.setAttribute('data-color-theme', theme);
|
||||
} else {
|
||||
elem.setAttribute('data-color-theme', colorTheme);
|
||||
}
|
||||
|
|
@ -302,16 +319,6 @@ document.addEventListener('mousemove', function(e) {
|
|||
kpxcPasswordDialog.dialog.style.top = Pixels(yPos);
|
||||
}
|
||||
}
|
||||
|
||||
if (kpxcDefine.selected === kpxcDefine.dialog) {
|
||||
const xPos = e.clientX - kpxcDefine.diffX;
|
||||
const yPos = e.clientY - kpxcDefine.diffY;
|
||||
|
||||
if (kpxcDefine.selected && kpxcDefine.dialog) {
|
||||
kpxcDefine.dialog.style.left = Pixels(xPos);
|
||||
kpxcDefine.dialog.style.top = Pixels(yPos);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('mousedown', function(e) {
|
||||
|
|
@ -328,7 +335,6 @@ document.addEventListener('mouseup', function(e) {
|
|||
}
|
||||
|
||||
kpxcPasswordDialog.selected = null;
|
||||
kpxcDefine.selected = null;
|
||||
kpxcUI.mouseDown = false;
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ kpxcUsernameIcons.isValid = function(field) {
|
|||
|| field.offsetWidth < MIN_INPUT_FIELD_OFFSET_WIDTH
|
||||
|| field.readOnly
|
||||
|| kpxcIcons.hasIcon(field)
|
||||
|| !kpxcFields.isVisible(field)) {
|
||||
|| (!kpxcFields.isCustomLoginFieldsUsed() && !kpxcFields.isVisible(field))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -117,7 +117,7 @@ UsernameFieldIcon.prototype.createIcon = function(field) {
|
|||
};
|
||||
|
||||
const iconClicked = async function(field, icon) {
|
||||
if (!kpxcFields.isVisible(field)) {
|
||||
if (!kpxcFields.isCustomLoginFieldsUsed() && !kpxcFields.isVisible(field)) {
|
||||
icon.parentNode.removeChild(icon);
|
||||
field.removeAttribute('kpxc-username-field');
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -64,12 +64,33 @@ div.kpxc-banner .kpxc-banner-icon-moz {
|
|||
background-size: contain;
|
||||
}
|
||||
|
||||
div.kpxc-banner .kpxc-help-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
overflow: hidden;
|
||||
background: url('chrome-extension://__MSG_@@extension_id__/icons/help.svg') right no-repeat;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
div.kpxc-banner .kpxc-help-icon-moz {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
overflow: hidden;
|
||||
background: url('moz-extension://__MSG_@@extension_id__/icons/help.svg') right no-repeat;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.kpxc-separator {
|
||||
border-left: 1px solid #ccc;
|
||||
height: 100% !important;
|
||||
margin: 10px !important;
|
||||
}
|
||||
|
||||
.kpxc-pick-info-text {
|
||||
margin-left: 8px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
div.kpxc-banner .kpxc-checkbox {
|
||||
margin: 2px !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,10 +46,12 @@
|
|||
transition: all .15s;
|
||||
}
|
||||
|
||||
.kpxc-button:disabled {
|
||||
border-color: #ccc !important;
|
||||
.kpxc-button:disabled, .kpxc-gray-button {
|
||||
background-color: #777777 !important;
|
||||
border-color: #444444 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.kpxc-button:disabled:hover {
|
||||
background-color: #fff !important;
|
||||
background-color: #777777 !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,117 +1,61 @@
|
|||
.kpxcDefine-modal-backdrop {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: 2147483645;
|
||||
}
|
||||
|
||||
.kpxcDefine-modal-backdrop:after {
|
||||
background-color: #000000;
|
||||
bottom: 0;
|
||||
content: '';
|
||||
filter: alpha(opacity=50);
|
||||
left: 0;
|
||||
opacity: 0.5;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
#kpxcDefine-fields {
|
||||
z-index: 2147483646;
|
||||
}
|
||||
|
||||
#kpxcDefine-description {
|
||||
background-color: rgba(0,0,0,0.8);
|
||||
border: 2px solid #555555;
|
||||
color: #efefef;
|
||||
cursor: pointer;
|
||||
font-size: 15px;
|
||||
height: auto !important;
|
||||
left: 150px;
|
||||
padding: 7px 5px;
|
||||
position: absolute;
|
||||
text-align: left;
|
||||
top: 100px;
|
||||
user-select: none;
|
||||
z-index: 2147483646;
|
||||
}
|
||||
|
||||
#kpxcDefine-description div:first-of-type {
|
||||
color: #efefef;
|
||||
font-weight: bold;
|
||||
font-size: 120%;
|
||||
margin-top: 0;
|
||||
padding-bottom: 8px;
|
||||
padding-top: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#kpxcDefine-description p {
|
||||
border-top: 2px solid #666666;
|
||||
color: #efefef;
|
||||
line-height: 110%;
|
||||
margin-top: 10px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
#kpxcDefine-help {
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.kpxcDefine-keyboardHelp {
|
||||
font-size: 0.75em;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.kpxcDefine-keyboardHelp kbd {
|
||||
background-color: #eee;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #b4b4b4;
|
||||
color: #333;
|
||||
display: inline-block;
|
||||
height: auto !important;
|
||||
line-height: 1;
|
||||
padding: 2px 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.kpxcDefine-chooser-help {
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.kpxcDefine-fixed-field {
|
||||
align-items: center;
|
||||
background-color: rgba(239,239,239,0.4);
|
||||
border: 2px solid #efefef;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
font-weight: bold;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
z-index: 2147483646;
|
||||
}
|
||||
|
||||
.kpxcDefine-fixed-field-dark {
|
||||
background-color: rgba(45, 45, 45, 0.4);
|
||||
border: 2px solid #0f0f0f;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.kpxcDefine-fixed-hover-field {
|
||||
background-color: rgba(255, 165, 238, 0.631);
|
||||
border: 2px solid orange;
|
||||
background-color: rgba(255,165,239,0.4);
|
||||
}
|
||||
|
||||
.kpxcDefine-fixed-hover-field-dark {
|
||||
background-color: rgba(255, 165, 238, 0.599);
|
||||
border: 2px solid orange;
|
||||
color: #505050;
|
||||
}
|
||||
|
||||
.kpxcDefine-fixed-password-field {
|
||||
border: 2px solid red;
|
||||
background-color: rgba(255,0,0,0.4);
|
||||
border: 2px solid red;
|
||||
color: #efefef;
|
||||
}
|
||||
|
||||
.kpxcDefine-fixed-username-field {
|
||||
border: 2px solid limegreen;
|
||||
background-color: rgba(50,205,50,0.4);
|
||||
background-color: rgba(232, 252, 3, 0.4);
|
||||
border: 2px solid yellow;
|
||||
color: #efefef;
|
||||
}
|
||||
|
||||
.kpxcDefine-fixed-string-field, .kpxcDefine-fixed-totp-field {
|
||||
border: 2px solid deepskyblue;
|
||||
background-color: rgba(30,144,255,0.4);
|
||||
.kpxcDefine-fixed-totp-field {
|
||||
background-color: rgba(50,205,50,0.4);
|
||||
border: 2px solid limegreen;
|
||||
color: #efefef;
|
||||
}
|
||||
|
||||
.kpxcDefine-fixed-string-field {
|
||||
background-color: rgba(30,144,255,0.4);
|
||||
border: 2px solid deepskyblue;
|
||||
color: #efefef;
|
||||
}
|
||||
|
||||
.kpxcDefine-dark-text {
|
||||
color: #505050;
|
||||
}
|
||||
|
|
|
|||
1
keepassxc-browser/icons/help.svg
Normal file
1
keepassxc-browser/icons/help.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2"><g transform="matrix(0.983029,0,0,0.983029,-2.15274,-4.69129)"><circle cx="26.604" cy="29.187" r="20.345" style="fill:#0090ff"/></g><g transform="matrix(31.8224,0,0,31.8224,16.3008,35.374)"><path d="M.169-.218C.169-.263.175-.3.186-.327.197-.354.217-.38.247-.407.276-.433.296-.454.306-.471.315-.487.32-.505.32-.523.32-.578.295-.605.244-.605.22-.605.201-.598.186-.583.172-.568.164-.548.164-.522H.022C.023-.584.043-.633.082-.668.122-.703.176-.721.244-.721.313-.721.367-.704.405-.671.443-.637.462-.59.462-.529.462-.501.456-.475.443-.451.431-.426.409-.399.378-.369L.339-.331C.314-.307.3-.28.296-.248l-.002.03H.169zm-.014.15C.155-.09.163-.108.177-.122.192-.136.211-.143.234-.143S.276-.136.291-.122c.015.014.022.032.022.054C.313-.047.306-.029.292-.015.277-.001.258.006.234.006.211.006.191-.001.177-.015.163-.029.155-.047.155-.068z" style="fill:#fff;fill-rule:nonzero"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
|
@ -58,7 +58,7 @@
|
|||
"content/banner.js",
|
||||
"content/autocomplete.js",
|
||||
"content/credential-autocomplete.js",
|
||||
"content/define.js",
|
||||
"content/custom-fields-banner.js",
|
||||
"content/fields.js",
|
||||
"content/fill.js",
|
||||
"content/form.js",
|
||||
|
|
@ -124,6 +124,7 @@
|
|||
},
|
||||
"web_accessible_resources": [
|
||||
"icons/disconnected.svg",
|
||||
"icons/help.svg",
|
||||
"icons/keepassxc.svg",
|
||||
"icons/key.svg",
|
||||
"icons/locked.svg",
|
||||
|
|
|
|||
Loading…
Reference in a new issue