Skip to content

Commit 8bc00cf

Browse files
committed
Add the new selector for submit button
1 parent b1b6581 commit 8bc00cf

4 files changed

Lines changed: 114 additions & 35 deletions

File tree

keepassxc-browser/_locales/en/messages.json

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -319,9 +319,13 @@
319319
"description": "Clear save data button text when choosing Custom Login Fields."
320320
},
321321
"defineStringField": {
322-
"message": "String field",
322+
"message": "String Field",
323323
"description": "Text for string field."
324324
},
325+
"defineSubmitButton": {
326+
"message": "Submit Button",
327+
"description": "Text for Submit Button."
328+
},
325329
"defineChooseUsername": {
326330
"message": "Choose a username field",
327331
"description": "Choosing a username field text when choosing Custom Login Fields."
@@ -338,9 +342,13 @@
338342
"message": "Choose String Fields",
339343
"description": "Choose String Fields a selection text when choosing Custom Login Fields."
340344
},
345+
"defineChooseSubmitButton": {
346+
"message": "Choose submit button",
347+
"description": "Choose submit button selection text when choosing Custom Login Fields."
348+
},
341349
"defineHelpText": {
342-
"message": "Please confirm your selection or choose more fields as String fields.",
343-
"description": "Confirm a selection text when choosing Custom Login Fields which contains string fields."
350+
"message": "Please confirm your selection or choose more fields as String Fields.",
351+
"description": "Confirm a selection text when choosing Custom Login Fields which contains String Fields."
344352
},
345353
"defineKeyboardText": {
346354
"message": "You can also use the numbers to choose the input fields from keyboard.",
@@ -362,6 +370,10 @@
362370
"message": "String Fields",
363371
"description": "General text for a String Fields."
364372
},
373+
"submitButton": {
374+
"message": "Submit Button",
375+
"description": "Custom Submit Button text."
376+
},
365377
"credentialsNoUsername": {
366378
"message": "- no username -",
367379
"description": "Shown when no username is set in the credentials."

keepassxc-browser/content/custom-fields-banner.js

Lines changed: 78 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const STEP_SELECT_USERNAME = 1;
55
const STEP_SELECT_PASSWORD = 2;
66
const STEP_SELECT_TOTP = 3;
77
const STEP_SELECT_STRING_FIELDS = 4;
8+
const STEP_SELECT_SUBMIT_BUTTON = 5;
89

910
const CHECKBOX_OVERLAY_SIZE = 20;
1011

@@ -18,9 +19,10 @@ const PASSWORD_FIELD_CLASS = 'kpxcDefine-fixed-password-field';
1819
const TOTP_FIELD_CLASS = 'kpxcDefine-fixed-totp-field';
1920
const STRING_FIELD_CLASS = 'kpxcDefine-fixed-string-field';
2021

21-
const inputQueryPatternStart = 'input';
22-
const inputQueryPatternNotCheckbox = ':not([type=checkbox])';
23-
const inputQueryPattern = ':not([disabled]):not([type=button]):not([type=radio]):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';
22+
const INPUT_BUTTON_QUERY_PATTERN = 'button, input[type=submit]';
23+
const INPUT_QUERY_PATTERNS_START = 'input';
24+
const INPUT_QUERY_PATTERN_NOT_CHECKBOX = ':not([type=checkbox])';
25+
const INPUT_QUERY_PATTERN = ':not([disabled]):not([type=button]):not([type=radio]):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';
2426

2527
const kpxcCustomLoginFieldsBanner = {};
2628
kpxcCustomLoginFieldsBanner.banner = undefined;
@@ -29,8 +31,9 @@ kpxcCustomLoginFieldsBanner.created = false;
2931
kpxcCustomLoginFieldsBanner.dataStep = STEP_NONE;
3032
kpxcCustomLoginFieldsBanner.infoText = undefined;
3133
kpxcCustomLoginFieldsBanner.wrapper = undefined;
32-
kpxcCustomLoginFieldsBanner.inputQueryPatternNormal = inputQueryPatternStart + inputQueryPatternNotCheckbox + inputQueryPattern;
33-
kpxcCustomLoginFieldsBanner.inputQueryPatternStringFields = inputQueryPatternStart + inputQueryPattern;
34+
kpxcCustomLoginFieldsBanner.inputQueryPatternNormal =
35+
INPUT_QUERY_PATTERNS_START + INPUT_QUERY_PATTERN_NOT_CHECKBOX + INPUT_QUERY_PATTERN;
36+
kpxcCustomLoginFieldsBanner.inputQueryPatternStringFields = INPUT_QUERY_PATTERNS_START + INPUT_QUERY_PATTERN;
3437
kpxcCustomLoginFieldsBanner.markedFields = [];
3538
kpxcCustomLoginFieldsBanner.nonSelectedElementsPattern = `div.${FIXED_FIELD_CLASS}:not(.${USERNAME_FIELD_CLASS}):not(.${PASSWORD_FIELD_CLASS}):not(.${TOTP_FIELD_CLASS}):not(.${STRING_FIELD_CLASS})`;
3639

@@ -43,6 +46,7 @@ kpxcCustomLoginFieldsBanner.selection = {
4346
totpElement: undefined,
4447
fields: [],
4548
fieldElements: [],
49+
submitButton: undefined,
4650
};
4751

4852
kpxcCustomLoginFieldsBanner.buttons = {
@@ -100,6 +104,7 @@ kpxcCustomLoginFieldsBanner.create = async function() {
100104
const passwordButton = kpxcUI.createButton(RED_BUTTON, tr('password'), kpxcCustomLoginFieldsBanner.passwordButtonClicked);
101105
const totpButton = kpxcUI.createButton(GREEN_BUTTON, 'TOTP', kpxcCustomLoginFieldsBanner.totpButtonClicked);
102106
const stringFieldsButton = kpxcUI.createButton(BLUE_BUTTON, tr('stringFields'), kpxcCustomLoginFieldsBanner.stringFieldsButtonClicked);
107+
const submitButton = kpxcUI.createButton(BLUE_BUTTON, tr('submitButton'), kpxcCustomLoginFieldsBanner.submitButtonClicked);
103108
const clearDataButton = kpxcUI.createButton(RED_BUTTON, tr('defineClearData'), kpxcCustomLoginFieldsBanner.clearData);
104109
const confirmButton = kpxcUI.createButton(GREEN_BUTTON, tr('defineConfirm'), kpxcCustomLoginFieldsBanner.confirm);
105110
const closeButton = kpxcUI.createButton(RED_BUTTON, tr('defineClose'), kpxcCustomLoginFieldsBanner.closeButtonClicked);
@@ -116,10 +121,11 @@ kpxcCustomLoginFieldsBanner.create = async function() {
116121
kpxcCustomLoginFieldsBanner.buttons.password = passwordButton;
117122
kpxcCustomLoginFieldsBanner.buttons.totp = totpButton;
118123
kpxcCustomLoginFieldsBanner.buttons.stringFields = stringFieldsButton;
124+
kpxcCustomLoginFieldsBanner.buttons.submitButton = submitButton;
119125

120126
bannerInfo.appendMultiple(icon, infoText);
121-
bannerButtons.appendMultiple(resetButton, separator, usernameButton,
122-
passwordButton, totpButton, stringFieldsButton, secondSeparator, clearDataButton, confirmButton, closeButton);
127+
bannerButtons.appendMultiple(resetButton, separator, usernameButton, passwordButton, totpButton,
128+
stringFieldsButton, submitButton, secondSeparator, clearDataButton, confirmButton, closeButton);
123129
banner.appendMultiple(bannerInfo, bannerButtons);
124130
kpxcUI.makeBannerDraggable(banner);
125131

@@ -189,7 +195,8 @@ kpxcCustomLoginFieldsBanner.usernameButtonClicked = function(e) {
189195

190196
// Reset username field selection if already set
191197
if (kpxcCustomLoginFieldsBanner.selection.username) {
192-
kpxcCustomLoginFieldsBanner.removeSelection(kpxcCustomLoginFieldsBanner.selection.username, `div.${USERNAME_FIELD_CLASS}`);
198+
kpxcCustomLoginFieldsBanner.removeSelection(kpxcCustomLoginFieldsBanner.selection.username,
199+
`div.${USERNAME_FIELD_CLASS}`);
193200
kpxcCustomLoginFieldsBanner.selection.username = undefined;
194201
}
195202

@@ -207,7 +214,8 @@ kpxcCustomLoginFieldsBanner.passwordButtonClicked = function(e) {
207214

208215
// Reset password field selection if already set
209216
if (kpxcCustomLoginFieldsBanner.selection.password) {
210-
kpxcCustomLoginFieldsBanner.removeSelection(kpxcCustomLoginFieldsBanner.selection.password, `div.${PASSWORD_FIELD_CLASS}`);
217+
kpxcCustomLoginFieldsBanner.removeSelection(kpxcCustomLoginFieldsBanner.selection.password,
218+
`div.${PASSWORD_FIELD_CLASS}`);
211219
kpxcCustomLoginFieldsBanner.selection.password = undefined;
212220
}
213221

@@ -225,7 +233,8 @@ kpxcCustomLoginFieldsBanner.totpButtonClicked = function(e) {
225233

226234
// Reset TOTP field selection if already set
227235
if (kpxcCustomLoginFieldsBanner.selection.totp) {
228-
kpxcCustomLoginFieldsBanner.removeSelection(kpxcCustomLoginFieldsBanner.selection.totp, `div.${TOTP_FIELD_CLASS}`);
236+
kpxcCustomLoginFieldsBanner.removeSelection(kpxcCustomLoginFieldsBanner.selection.totp,
237+
`div.${TOTP_FIELD_CLASS}`);
229238
kpxcCustomLoginFieldsBanner.selection.totp = undefined;
230239
}
231240

@@ -256,6 +265,25 @@ kpxcCustomLoginFieldsBanner.stringFieldsButtonClicked = function(e) {
256265
sendMessageToFrames(e, 'string_field_button_clicked');
257266
};
258267

268+
kpxcCustomLoginFieldsBanner.submitButtonClicked = function(e) {
269+
if (!e.isTrusted || kpxcCustomLoginFieldsBanner.dataStep === STEP_SELECT_SUBMIT_BUTTON) {
270+
kpxcCustomLoginFieldsBanner.backToStart();
271+
return;
272+
}
273+
274+
// Reset TOTP field selection if already set
275+
if (kpxcCustomLoginFieldsBanner.selection.submitButton) {
276+
kpxcCustomLoginFieldsBanner.removeSelection(kpxcCustomLoginFieldsBanner.selection.submitButton,
277+
`div.${STRING_FIELD_CLASS}`);
278+
kpxcCustomLoginFieldsBanner.selection.submitButton = undefined;
279+
}
280+
281+
kpxcCustomLoginFieldsBanner.prepareSubmitButtonSelection();
282+
kpxcCustomLoginFieldsBanner.buttons.confirm.disabled = true;
283+
284+
sendMessageToFrames(e, 'submit_button_clicked');
285+
};
286+
259287
kpxcCustomLoginFieldsBanner.closeButtonClicked = function(e) {
260288
if (!e.isTrusted) {
261289
return;
@@ -282,6 +310,8 @@ kpxcCustomLoginFieldsBanner.updateFieldSelections = function() {
282310
kpxcCustomLoginFieldsBanner.prepareTOTPSelection();
283311
} else if (kpxcCustomLoginFieldsBanner.dataStep === STEP_SELECT_STRING_FIELDS) {
284312
kpxcCustomLoginFieldsBanner.prepareStringFieldSelection();
313+
} else if (kpxcCustomLoginFieldsBanner.dataStep === STEP_SELECT_SUBMIT_BUTTON) {
314+
kpxcCustomLoginFieldsBanner.prepareSubmitButtonSelection();
285315
}
286316
};
287317

@@ -314,17 +344,20 @@ kpxcCustomLoginFieldsBanner.confirm = async function(e) {
314344
kpxc.settings[DEFINED_CUSTOM_FIELDS][location].password = undefined;
315345
} else if (currentSite.totp?.[0] === path[0]) {
316346
kpxc.settings[DEFINED_CUSTOM_FIELDS][location].totp = undefined;
347+
} else if (currentSite.submitButton?.[0] === path[0]) {
348+
kpxc.settings[DEFINED_CUSTOM_FIELDS][location].submitButton = undefined;
317349
}
318350
};
319351

320352
const usernamePath = kpxcCustomLoginFieldsBanner.selection.username;
321353
const passwordPath = kpxcCustomLoginFieldsBanner.selection.password;
322354
const totpPath = kpxcCustomLoginFieldsBanner.selection.totp;
323355
const stringFieldsPaths = kpxcCustomLoginFieldsBanner.selection.fields;
356+
const submitButtonPath = kpxcCustomLoginFieldsBanner.selection.submitButton;
324357
const location = kpxc.getDocumentLocation();
325358
const currentSettings = kpxc.settings[DEFINED_CUSTOM_FIELDS][location];
326359

327-
if (usernamePath || passwordPath || totpPath || stringFieldsPaths.length > 0) {
360+
if (usernamePath || passwordPath || totpPath || stringFieldsPaths.length > 0 || submitButtonPath) {
328361
if (currentSettings) {
329362
// Update the single selection to current settings
330363
if (usernamePath) {
@@ -345,13 +378,19 @@ kpxcCustomLoginFieldsBanner.confirm = async function(e) {
345378
if (stringFieldsPaths.length > 0) {
346379
kpxc.settings[DEFINED_CUSTOM_FIELDS][location].fields = stringFieldsPaths;
347380
}
381+
382+
if (submitButtonPath) {
383+
clearIdenticalField(submitButtonPath, location);
384+
kpxc.settings[DEFINED_CUSTOM_FIELDS][location].submitButton = submitButtonPath;
385+
}
348386
} else {
349387
// Override all fields (default, because there's no currentSettings available)
350388
kpxc.settings[DEFINED_CUSTOM_FIELDS][location] = {
351389
username: usernamePath,
352390
password: passwordPath,
353391
totp: totpPath,
354-
fields: stringFieldsPaths
392+
fields: stringFieldsPaths,
393+
submitButton: submitButtonPath,
355394
};
356395
}
357396

@@ -381,7 +420,8 @@ kpxcCustomLoginFieldsBanner.resetSelection = function() {
381420
username: undefined,
382421
password: undefined,
383422
totp: undefined,
384-
fields: []
423+
fields: [],
424+
submitButton: undefined,
385425
};
386426

387427
kpxcCustomLoginFieldsBanner.removeMarkedFields();
@@ -423,15 +463,24 @@ kpxcCustomLoginFieldsBanner.prepareStringFieldSelection = function() {
423463
kpxcCustomLoginFieldsBanner.selectStringFields();
424464
};
425465

466+
kpxcCustomLoginFieldsBanner.prepareSubmitButtonSelection = function() {
467+
kpxcCustomLoginFieldsBanner.infoText.textContent = tr('defineChooseSubmitButton');
468+
kpxcCustomLoginFieldsBanner.dataStep = STEP_SELECT_SUBMIT_BUTTON;
469+
kpxcCustomLoginFieldsBanner.buttons.submitButton.classList.remove(GRAY_BUTTON_CLASS);
470+
kpxcCustomLoginFieldsBanner.selectField('submitButton');
471+
};
472+
426473
kpxcCustomLoginFieldsBanner.isFieldSelected = function(field) {
427474
const currentFieldId = kpxcFields.setId(field);
475+
const selection = kpxcCustomLoginFieldsBanner.selection;
428476

429477
if (kpxcCustomLoginFieldsBanner.markedFields.some(f => f === field)) {
430478
return (
431-
(kpxcCustomLoginFieldsBanner.selection.username && kpxcCustomLoginFieldsBanner.selection.usernameElement === field)
432-
|| (kpxcCustomLoginFieldsBanner.selection.password && kpxcCustomLoginFieldsBanner.selection.passwordElement === field)
433-
|| (kpxcCustomLoginFieldsBanner.selection.totp && kpxcCustomLoginFieldsBanner.selection.totpElement === field)
434-
|| kpxcCustomLoginFieldsBanner.selection.fields.some(f => f[0] === currentFieldId[0])
479+
(selection.username && selection.usernameElement === field)
480+
|| (selection.password && selection.passwordElement === field)
481+
|| (selection.totp && selection.totpElement === field)
482+
|| (selection.submitButton && selection.submitButton === field)
483+
|| selection.fields.some(f => f[0] === currentFieldId[0])
435484
);
436485
}
437486

@@ -460,7 +509,7 @@ kpxcCustomLoginFieldsBanner.setSelectedField = function(elem) {
460509
kpxcCustomLoginFieldsBanner.buttons.close.textContent = tr('optionsButtonCancel');
461510
};
462511

463-
// Expects 'username', 'password' or 'totp'
512+
// Expects 'username', 'password', 'totp' or 'submitButton'
464513
kpxcCustomLoginFieldsBanner.selectField = function(fieldType) {
465514
kpxcCustomLoginFieldsBanner.eventFieldClick = function(e) {
466515
const field = kpxcCustomLoginFieldsBanner.getSelectedField(e);
@@ -516,9 +565,10 @@ kpxcCustomLoginFieldsBanner.selectStringFields = function() {
516565
kpxcCustomLoginFieldsBanner.markFields = function() {
517566
let firstInput;
518567
const inputs = document.querySelectorAll(
519-
kpxcCustomLoginFieldsBanner.dataStep === STEP_SELECT_STRING_FIELDS
520-
? kpxcCustomLoginFieldsBanner.inputQueryPatternStringFields
521-
: kpxcCustomLoginFieldsBanner.inputQueryPatternNormal);
568+
kpxcCustomLoginFieldsBanner.dataStep === STEP_SELECT_SUBMIT_BUTTON ? INPUT_BUTTON_QUERY_PATTERN :
569+
(STEP_SELECT_STRING_FIELDS
570+
? kpxcCustomLoginFieldsBanner.inputQueryPatternStringFields
571+
: kpxcCustomLoginFieldsBanner.inputQueryPatternNormal));
522572
const zoom = kpxcUI.bodyStyle.zoom || 1;
523573

524574
for (const i of inputs) {
@@ -676,6 +726,9 @@ kpxcCustomLoginFieldsBanner.handleTopWindowMessage = function(args) {
676726
} else if (message === 'string_field_selected') {
677727
kpxcCustomLoginFieldsBanner.selection.fields = selection;
678728
kpxcCustomLoginFieldsBanner.setSelectedField();
729+
} else if (message === 'submitButton_selected') {
730+
kpxcCustomLoginFieldsBanner.selection.submitButton = selection;
731+
kpxcCustomLoginFieldsBanner.setSelectedField();
679732
} else if (message === 'enable_clear_data_button') {
680733
kpxcCustomLoginFieldsBanner.buttons.clearData.style.display = 'inline-block';
681734
}
@@ -699,6 +752,8 @@ kpxcCustomLoginFieldsBanner.handleParentWindowMessage = function(args) {
699752
kpxcCustomLoginFieldsBanner.totpButtonClicked(e);
700753
} else if (message === 'string_field_button_clicked') {
701754
kpxcCustomLoginFieldsBanner.stringFieldsButtonClicked(e);
755+
} else if (message === 'submit_button_clicked') {
756+
kpxcCustomLoginFieldsBanner.submitButtonClicked(e);
702757
} else if (message === 'reset_button_clicked') {
703758
kpxcCustomLoginFieldsBanner.reset();
704759
} else if (message === 'close_button_clicked') {
@@ -766,5 +821,7 @@ const dataStepToString = function() {
766821
return tr('totp');
767822
} else if (kpxcCustomLoginFieldsBanner.dataStep === STEP_SELECT_STRING_FIELDS) {
768823
return tr('defineStringField');
824+
} else if (kpxcCustomLoginFieldsBanner.dataStep === STEP_SELECT_SUBMIT_BUTTON) {
825+
return tr('defineSubmitButton');
769826
}
770827
};

keepassxc-browser/content/fields.js

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -588,14 +588,14 @@ kpxcFields.traverseParents = function(element, predicate, resultFn = () => true,
588588
kpxcFields.useCustomLoginFields = async function() {
589589
const location = kpxc.getDocumentLocation();
590590
const creds = kpxc.settings['defined-custom-fields'][location];
591-
if (!creds.username && !creds.password && !creds.totp && creds.fields.length === 0) {
591+
if (!creds.username && !creds.password && !creds.totp && creds.fields.length === 0 && !creds.submitButton) {
592592
return;
593593
}
594594

595-
// Finds the input field based on the stored ID
596-
const findInputField = async function(inputFields, idArray) {
595+
// Finds the element based on the stored ID
596+
const findElement = async function(fields, idArray) {
597597
if (idArray) {
598-
const input = inputFields.find(e => e === kpxcFields.getId(idArray, e));
598+
const input = fields.find(e => e === kpxcFields.getId(idArray, e));
599599
if (input) {
600600
return input;
601601
}
@@ -612,16 +612,24 @@ kpxcFields.useCustomLoginFields = async function() {
612612
}
613613
});
614614

615-
const [ username, password, totp ] = await Promise.all([
616-
await findInputField(inputFields, creds.username),
617-
await findInputField(inputFields, creds.password),
618-
await findInputField(inputFields, creds.totp)
615+
const buttons = [];
616+
document.body.querySelectorAll('button, input[type=submit]').forEach(e => {
617+
if (e.type !== 'hidden' && !e.disabled) {
618+
buttons.push(e);
619+
}
620+
});
621+
622+
const [ username, password, totp, submitButton ] = await Promise.all([
623+
await findElement(inputFields, creds.username),
624+
await findElement(inputFields, creds.password),
625+
await findElement(inputFields, creds.totp),
626+
await findElement(buttons, creds.submitButton),
619627
]);
620628

621629
// Handle StringFields
622630
const stringFields = [];
623631
for (const sf of creds.fields) {
624-
const field = await findInputField(inputFields, sf);
632+
const field = await findElement(inputFields, sf);
625633
if (field) {
626634
stringFields.push(field);
627635
}
@@ -639,7 +647,8 @@ kpxcFields.useCustomLoginFields = async function() {
639647
password: password,
640648
passwordInputs: [ password ],
641649
totp: totp,
642-
fields: stringFields
650+
fields: stringFields,
651+
submitButton: submitButton
643652
});
644653

645654
return combinations;

keepassxc-browser/content/fill.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,8 @@ kpxcFill.performAutoSubmit = async function(combination, skipAutoSubmit) {
340340
if (!skipAutoSubmit && !autoSubmitIgnoredForSite) {
341341
await sendMessage('page_set_autosubmit_performed');
342342

343-
const submitButton = kpxcForm.getFormSubmitButton(combination.form);
343+
// Use submit button from Custom Login Fields or detect it from the form
344+
const submitButton = combination?.submitButton ?? kpxcForm.getFormSubmitButton(combination.form);
344345
if (submitButton !== undefined) {
345346
submitButton.click();
346347
} else if (combination.form) {

0 commit comments

Comments
 (0)