Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
"AuthenticatorAssertionResponse": "readonly",
"AuthenticatorAttestationResponse": "readonly",
"Autocomplete": "readonly",
"BannerPosition": "readonly",
"BLUE_BUTTON": "readonly",
"bootstrap": "readonly",
"browser": "readonly",
Expand Down
2 changes: 2 additions & 0 deletions keepassxc-browser/background/event.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,8 @@ kpxcEvent.sendBackToTabs = async function(tab, args = []) {
kpxcEvent.messageHandlers = {
'add_credentials': keepass.addCredentials,
'associate': keepass.associate,
'banner_get_position': page.getBannerPosition,
'banner_set_position': page.setBannerPosition,
'check_database_hash': keepass.checkDatabaseHash,
'check_update_keepassxc': kpxcEvent.onCheckUpdateKeePassXC,
'compare_version': kpxcEvent.compareVersion,
Expand Down
10 changes: 10 additions & 0 deletions keepassxc-browser/background/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const defaultSettings = {
autoReconnect: false,
autoRetrieveCredentials: true,
autoSubmit: false,
bannerPosition: BannerPosition.TOP,
checkUpdateKeePassXC: CHECK_UPDATE_NEVER,
clearCredentialsTimeout: 10,
colorTheme: 'system',
Expand Down Expand Up @@ -253,6 +254,15 @@ page.setManualFill = async function(tab, manualFill) {
page.manualFill = manualFill;
};

page.getBannerPosition = async function(tab) {
return page.settings.bannerPosition;
};

page.setBannerPosition = async function(tab, position) {
page.settings.bannerPosition = position;
await browser.storage.local.set({ 'settings': page.settings });
};

page.getSubmitted = async function(tab) {
// Do not return any credentials if the tab ID does not match.
if (tab.id !== page.submittedCredentials.tabId) {
Expand Down
5 changes: 5 additions & 0 deletions keepassxc-browser/common/global.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ const AssociatedAction = {
CANCELED: 3
};

const BannerPosition = {
BOTTOM: 0,
TOP: 1
};

const ManualFill = {
NONE: 0,
PASSWORD: 1,
Expand Down
6 changes: 3 additions & 3 deletions keepassxc-browser/content/autocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ class Autocomplete {
}
}

updatePosition() {
updatePosition() {
if (!this.container || !this.input) {
return;
}
Expand All @@ -336,7 +336,7 @@ class Autocomplete {
this.list.style.maxWidth = `max(${Pixels(this.input.offsetWidth)}, 600px)`;

// Get body zoom radio
const zoom = kpxcUI.bodyStyle.zoom || 1;
const zoom = kpxcUI.bodyStyle.zoom || 1;

// Calculate Y offset if menu does not fit to the bottom of the screen -> show it at the top of the input field
const menuRect = this.container.getBoundingClientRect();
Expand All @@ -352,7 +352,7 @@ class Autocomplete {
);
this.container.style.left = Pixels((rect.left - kpxcUI.bodyRect.left) / zoom + scrollLeft);
} else {
this.container.style.top = Pixels(rect.top / zoom + scrollTop + this.input.offsetHeight - menuOffset)
this.container.style.top = Pixels(rect.top / zoom + scrollTop + this.input.offsetHeight - menuOffset);
this.container.style.left = Pixels(rect.left / zoom + scrollLeft);
}
}
Expand Down
38 changes: 28 additions & 10 deletions keepassxc-browser/content/banner.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ kpxcBanner.create = async function(credentials = {}) {
credentials.username = credentials.username.trim();
kpxcBanner.credentials = credentials;

const banner = kpxcUI.createElement('div', 'kpxc-banner', { 'id': 'kpxc-banner-container' });
const bannerPosition = await sendMessage('banner_get_position');
const bannerClass =
bannerPosition === BannerPosition.TOP ? 'kpxc-banner kpxc-banner-on-top' : 'kpxc-banner kpxc-banner-on-bottom';
const banner = kpxcUI.createElement('div', bannerClass, { 'id': 'kpxc-banner-container' });
initColorTheme(banner);
banner.style.zIndex = '2147483646';

Expand Down Expand Up @@ -114,6 +117,8 @@ kpxcBanner.create = async function(credentials = {}) {
dismissButton.textContent = tr('popupButtonBack');
});

kpxcUI.makeBannerDraggable(banner);

dismissButton.addEventListener('click', function(e) {
if (!e.isTrusted) {
return;
Expand Down Expand Up @@ -411,7 +416,7 @@ kpxcBanner.createCredentialDialog = async function() {
const connectedDatabase = await sendMessage('get_connected_database');
const databaseName = connectedDatabase.count > 0 ? connectedDatabase.identifier : '';

const dialog = kpxcUI.createElement('div', 'kpxc-banner-dialog');
const dialog = kpxcUI.createElement('div', 'kpxc-banner-dialog kpxc-banner-dialog-top');
const databaseText = kpxcUI.createElement('p');
const spanDatabaseText = kpxcUI.createElement('span', '', {}, tr('rememberSaving'));
const usernameNew = kpxcUI.createElement('p', 'username-new');
Expand All @@ -421,11 +426,9 @@ kpxcBanner.createCredentialDialog = async function() {
const spanNewUsername = kpxcUI.createElement('span', '', {}, tr('rememberNewUsername'));
const spanUsernameExists = kpxcUI.createElement('span', '', {}, tr('rememberUsernameExists'));

// Set dialog position
dialog.style.top = Pixels(kpxcBanner.banner.offsetHeight);
dialog.style.right = '0';

setDialogPosition(dialog);
databaseText.appendChild(spanDatabaseText);

const spanName = kpxcUI.createElement('span', 'strong', {}, databaseName);
databaseText.append(spanName);

Expand All @@ -447,10 +450,25 @@ kpxcBanner.createGroupDialog = function() {
const chooseGroup = kpxcUI.createElement('p', '', {}, tr('rememberChooseGroup'));
const list = kpxcUI.createElement('ul', 'list-group', { 'id': 'list' });

// Set dialog position
dialog.style.top = Pixels(kpxcBanner.banner.offsetHeight);
dialog.style.right = Pixels(0);

setDialogPosition(dialog);
dialog.appendMultiple(chooseGroup, list);
kpxcBanner.banner.appendChild(dialog);
};

const setDialogPosition = function(dialog) {
if (!dialog) {
return;
}

if (kpxcBanner.banner.classList.contains('kpxc-banner-on-bottom')) {
dialog.style.bottom = Pixels(kpxcBanner.banner.offsetHeight);
dialog.classList.remove('kpxc-banner-dialog-top');
dialog.classList.add('kpxc-banner-dialog-bottom');
} else {
dialog.style.top = Pixels(kpxcBanner.banner.offsetHeight);
dialog.classList.remove('kpxc-banner-dialog-bottom');
dialog.classList.add('kpxc-banner-dialog-top');
}

dialog.style.right = Pixels(0);
};
6 changes: 5 additions & 1 deletion keepassxc-browser/content/custom-fields-banner.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,10 @@ kpxcCustomLoginFieldsBanner.create = async function() {
return;
}

const banner = kpxcUI.createElement('div', 'kpxc-banner', { 'id': 'container' });
const bannerPosition = await sendMessage('banner_get_position');
const bannerClass =
bannerPosition === BannerPosition.TOP ? 'kpxc-banner kpxc-banner-on-top' : 'kpxc-banner kpxc-banner-on-bottom';
const banner = kpxcUI.createElement('div', bannerClass, { 'id': 'container' });
banner.style.zIndex = '2147483646';
kpxcCustomLoginFieldsBanner.chooser = kpxcUI.createElement('div', '', { 'id': 'kpxcDefine-fields' });

Expand Down Expand Up @@ -111,6 +114,7 @@ kpxcCustomLoginFieldsBanner.create = async function() {
bannerButtons.appendMultiple(resetButton, separator, usernameButton,
passwordButton, totpButton, stringFieldsButton, secondSeparator, clearDataButton, confirmButton, closeButton);
banner.appendMultiple(bannerInfo, bannerButtons);
kpxcUI.makeBannerDraggable(banner);

const location = kpxc.getDocumentLocation();
kpxcCustomLoginFieldsBanner.buttons.clearData.style.display
Expand Down
2 changes: 1 addition & 1 deletion keepassxc-browser/content/passkeys.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const createAttestationResponse = function(publicKey) {
getPublicKeyAlgorithm: () => publicKey.response?.publicKeyAlgorithm,
getTransports: () => [ 'internal' ]
};
return Object.setPrototypeOf(response, AuthenticatorAttestationResponse.prototype);
return Object.setPrototypeOf(response, AuthenticatorAttestationResponse.prototype);
};

// Wraps response to AuthenticatorAssertionResponse object
Expand Down
57 changes: 57 additions & 0 deletions keepassxc-browser/content/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,59 @@ kpxcUI.isRTL = function(field) {
f => ({ 'ltr': false, 'rtl': true })[f.getLowerCaseAttribute('dir')]);
};

kpxcUI.makeBannerDraggable = function(banner) {
if (!banner) {
return;
}

banner.draggable = true;

banner.addEventListener('dragstart', (e) => {
if (!e.isTrusted) {
return;
}

e.dataTransfer.effectAllowed = 'copyMove';
document.addEventListener('dragover', preventDefaultDragEnd);
});

banner.addEventListener('dragend', async (e) => {
if (!e.isTrusted || !e.target) {
return;
}

// If dragged to last third of the screen, move banner to bottom.
// If dragged to first third of the screen, move banner to top.
// If credential/group dialog is open, move it as well.
const bannerDialog = e.target.querySelector('.kpxc-banner-dialog');
if (e.y > e.view.innerHeight * (2 / 3) && e.target.classList.contains('kpxc-banner-on-top')) {
e.target.classList.remove('kpxc-banner-on-top');
e.target.classList.add('kpxc-banner-on-bottom');

if (bannerDialog) {
bannerDialog.style.top = '';
bannerDialog.style.bottom = Pixels(e.target.offsetHeight);
bannerDialog.classList.remove('kpxc-banner-dialog-top');
bannerDialog.classList.add('kpxc-banner-dialog-bottom');
}
await sendMessage('banner_set_position', BannerPosition.BOTTOM);
} else if (e.y < e.view.innerHeight * (1 / 3) && e.target.classList.contains('kpxc-banner-on-bottom')) {
e.target.classList.remove('kpxc-banner-on-bottom');
e.target.classList.add('kpxc-banner-on-top');

if (bannerDialog) {
bannerDialog.style.bottom = '';
bannerDialog.style.top = Pixels(e.target.offsetHeight);
bannerDialog.classList.remove('kpxc-banner-dialog-bottom');
bannerDialog.classList.add('kpxc-banner-dialog-top');
}
await sendMessage('banner_set_position', BannerPosition.TOP);
}

document.removeEventListener('dragover', preventDefaultDragEnd);
});
};

/**
* Detects if the input field appears or disappears -> show/hide the icon
* - boundingClientRect with slightly (< -10) negative values -> hidden
Expand Down Expand Up @@ -360,6 +413,10 @@ const createStylesheet = function(file) {
return stylesheet;
};

const preventDefaultDragEnd = function(e) {
e?.preventDefault();
};

const logDebug = function(message, extra) {
if (kpxc.settings.debugLogging) {
debugLogMessage(message, extra);
Expand Down
35 changes: 28 additions & 7 deletions keepassxc-browser/css/banner.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ div.kpxc-banner {
animation-duration: 0.2s;
animation-name: kpxc-slidein;
background-color: var(--kpxc-background-color);
border-bottom: 1px solid #38383d !important;
box-shadow: 0 4px 6px 0 hsla(0, 0%, 0%, 0.2) !important;
box-sizing: border-box !important;
color: var(--kpxc-text-color) !important;
display: flex !important;
font-size: 1em !important;
height: auto !important;
justify-content: space-between !important;
left: 0px !important;
line-height: 1 !important;
margin: 0 auto !important;
Expand All @@ -16,10 +16,23 @@ div.kpxc-banner {
overflow-x: hidden !important;
padding: 4px !important;
position: fixed !important;
top: 0px !important;
width: 100% !important;
display: flex !important;
justify-content: space-between !important;
}

div.kpxc-banner:hover {
cursor: grab;
}

.kpxc-banner-on-bottom {
border-bottom: 1px solid #38383d !important;
bottom: 0px !important;
box-shadow: 0 -4px 6px 0 hsla(0, 0%, 0%, 0.2) !important;
}

.kpxc-banner-on-top {
border-top: 1px solid #38383d !important;
box-shadow: 0 4px 6px 0 hsla(0, 0%, 0%, 0.2) !important;
top: 0px !important;
}

@keyframes kpxc-slidein {
Expand Down Expand Up @@ -105,8 +118,6 @@ div.kpxc-banner label {
div.kpxc-banner-dialog {
background-color: var(--kpxc-background-color);
border: var(--kpxc-container-border);
border-radius: 0 0 4px 4px;
box-shadow: 0 4px 6px 0 hsla(0, 0%, 0%, 0.2);
color: var(--kpxc-text-color);
display: block !important;
margin: 0 auto;
Expand All @@ -120,6 +131,16 @@ div.kpxc-banner-dialog {
z-index: 10000000;
}

div.kpxc-banner-dialog-bottom {
border-radius: 4px 4px 0 0;
box-shadow: 0 -4px 6px 0 hsla(0, 0%, 0%, 0.2);
}

div.kpxc-banner-dialog-top {
border-radius: 0 0 4px 4px;
box-shadow: 0 4px 6px 0 hsla(0, 0%, 0%, 0.2);
}

div.kpxc-banner-dialog > ul {
list-style-type: disc !important;
margin-bottom: 0 !important;
Expand Down