Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/keepassxreboot/keepassxc-browser.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSami Vänttinen <sami.vanttinen@protonmail.com>2022-06-22 07:21:05 +0300
committerGitHub <noreply@github.com>2022-06-22 07:21:05 +0300
commitba5a7141fba456ee409446ef32da2b19c416cee3 (patch)
tree5f88938f91fec875a3d1daf76bf9b6c57faec45f
parent32ee5105650ed3917b3d03f25c9a7fc333132b71 (diff)
Custom Login Fields banner selector (#1390)
New Custom Login Fields banner selector.
-rw-r--r--.eslintrc2
-rw-r--r--keepassxc-browser/_locales/en/messages.json44
-rw-r--r--keepassxc-browser/content/banner.js1
-rw-r--r--keepassxc-browser/content/custom-fields-banner.js767
-rw-r--r--keepassxc-browser/content/define.js496
-rw-r--r--keepassxc-browser/content/fields.js31
-rw-r--r--keepassxc-browser/content/fill.js11
-rwxr-xr-xkeepassxc-browser/content/keepassxc-browser.js13
-rw-r--r--keepassxc-browser/content/ui.js40
-rw-r--r--keepassxc-browser/content/username-field.js4
-rw-r--r--keepassxc-browser/css/banner.css21
-rw-r--r--keepassxc-browser/css/button.css8
-rw-r--r--keepassxc-browser/css/define.css116
-rw-r--r--keepassxc-browser/icons/help.svg1
-rwxr-xr-xkeepassxc-browser/manifest.json3
15 files changed, 922 insertions, 636 deletions
diff --git a/.eslintrc b/.eslintrc
index 034b127..fc054c3 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -99,7 +99,7 @@
"kpxc": true,
"kpActions": true,
"kpxcBanner": true,
- "kpxcDefine": true,
+ "kpxcCustomLoginFieldsBanner": true,
"kpxcFields": true,
"kpxcFill": true,
"kpxcForm": true,
diff --git a/keepassxc-browser/_locales/en/messages.json b/keepassxc-browser/_locales/en/messages.json
index e4d4214..a38d888 100644
--- a/keepassxc-browser/_locales/en/messages.json
+++ b/keepassxc-browser/_locales/en/messages.json
@@ -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."
diff --git a/keepassxc-browser/content/banner.js b/keepassxc-browser/content/banner.js
index e98e664..cfb1f66 100644
--- a/keepassxc-browser/content/banner.js
+++ b/keepassxc-browser/content/banner.js
@@ -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);
diff --git a/keepassxc-browser/content/custom-fields-banner.js b/keepassxc-browser/content/custom-fields-banner.js
new file mode 100644
index 0000000..f51fd20
--- /dev/null
+++ b/keepassxc-browser/content/custom-fields-banner.js
@@ -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 }, '*');
+ }
+}
diff --git a/keepassxc-browser/content/define.js b/keepassxc-browser/content/define.js
deleted file mode 100644
index 20cd1ea..0000000
--- a/keepassxc-browser/content/define.js
+++ /dev/null
@@ -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();
- }
-};
diff --git a/keepassxc-browser/content/fields.js b/keepassxc-browser/content/fields.js
index d51b6d7..7e75afc 100644
--- a/keepassxc-browser/content/fields.js
+++ b/keepassxc-browser/content/fields.js
@@ -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,
diff --git a/keepassxc-browser/content/fill.js b/keepassxc-browser/content/fill.js
index 198f80c..345a4f5 100644
--- a/keepassxc-browser/content/fill.js
+++ b/keepassxc-browser/content/fill.js
@@ -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]);
diff --git a/keepassxc-browser/content/keepassxc-browser.js b/keepassxc-browser/content/keepassxc-browser.js
index eb1889c..a13598e 100755
--- a/keepassxc-browser/content/keepassxc-browser.js
+++ b/keepassxc-browser/content/keepassxc-browser.js
@@ -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') {
diff --git a/keepassxc-browser/content/ui.js b/keepassxc-browser/content/ui.js
index 508a160..b4d7d12 100644
--- a/keepassxc-browser/content/ui.js
+++ b/keepassxc-browser/content/ui.js
@@ -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;
});
diff --git a/keepassxc-browser/content/username-field.js b/keepassxc-browser/content/username-field.js
index dc69427..0a17c44 100644
--- a/keepassxc-browser/content/username-field.js
+++ b/keepassxc-browser/content/username-field.js
@@ -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;
diff --git a/keepassxc-browser/css/banner.css b/keepassxc-browser/css/banner.css
index ea3e3d7..47c082d 100644
--- a/keepassxc-browser/css/banner.css
+++ b/keepassxc-browser/css/banner.css
@@ -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;
}
diff --git a/keepassxc-browser/css/button.css b/keepassxc-browser/css/button.css
index 79df313..e3b75de 100644
--- a/keepassxc-browser/css/button.css
+++ b/keepassxc-browser/css/button.css
@@ -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;
}
diff --git a/keepassxc-browser/css/define.css b/keepassxc-browser/css/define.css
index a5b1a9f..23921fd 100644
--- a/keepassxc-browser/css/define.css
+++ b/keepassxc-browser/css/define.css
@@ -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(232, 252, 3, 0.4);
+ border: 2px solid yellow;
+ color: #efefef;
+}
+
+.kpxcDefine-fixed-totp-field {
background-color: rgba(50,205,50,0.4);
+ border: 2px solid limegreen;
color: #efefef;
}
-.kpxcDefine-fixed-string-field, .kpxcDefine-fixed-totp-field {
- border: 2px solid deepskyblue;
+.kpxcDefine-fixed-string-field {
background-color: rgba(30,144,255,0.4);
+ border: 2px solid deepskyblue;
color: #efefef;
}
+
+.kpxcDefine-dark-text {
+ color: #505050;
+}
diff --git a/keepassxc-browser/icons/help.svg b/keepassxc-browser/icons/help.svg
new file mode 100644
index 0000000..6a7bf81
--- /dev/null
+++ b/keepassxc-browser/icons/help.svg
@@ -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> \ No newline at end of file
diff --git a/keepassxc-browser/manifest.json b/keepassxc-browser/manifest.json
index cb74d70..8591052 100755
--- a/keepassxc-browser/manifest.json
+++ b/keepassxc-browser/manifest.json
@@ -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",