Merge pull request #351 from keepassxreboot/ux_improvements

UX improvements
This commit is contained in:
Sami Vänttinen 2019-09-22 18:57:30 +03:00 committed by GitHub
commit 2977a75c0f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 2851 additions and 2178 deletions

View file

@ -21,16 +21,12 @@
},
"contextMenuSaveCredentials": {
"message": "Save credentials",
"description": "Save credentials using the extension popup icon."
"description": "Save credentials using the credential banner."
},
"contextMenuShowPasswordGenerator": {
"message": "Show password generator",
"message": "Show Password Generator",
"description": "Show password generator text."
},
"rememberCredentialsPopup": {
"message": "Create or modify the credentials by clicking on the extension icon.",
"description": "Notification text when saving credentials from the context menu."
},
"multipleCredentialsDetected": {
"message": "HTTP authentication with multiple credentials detected. Click on the extension icon to choose the correct one.",
"description": "Notification when HTTP Authentication has multiple credentials detected."
@ -99,6 +95,10 @@
"message": "No logins found.",
"description": "No logins found."
},
"errorNotConnected": {
"message": "Not connected to KeePassXC.",
"description": "Error notification shown when not connected to KeePassXC"
},
"passwordGeneratorIcon": {
"message": "Password generator icon",
"description": "Alt attribute text of the password generator icon."
@ -111,10 +111,6 @@
"message": "Also fill in the next password-field",
"description": "Checkbox text below the password generator input field."
},
"passwordGeneratorBits": {
"message": "$1 Bits",
"description": "Bits of the generated password."
},
"passwordGeneratorTitle": {
"message": "Password Generator",
"description": "Password generator dialog title."
@ -131,9 +127,9 @@
"message": "Copy",
"description": "Copy button text in password generator."
},
"passwordGeneratorFillAndCopy": {
"message": "Fill and copy",
"description": "Fill and copy button text in password generator."
"passwordGeneratorFill": {
"message": "Fill password",
"description": "Fill a password in password generator."
},
"passwordGeneratorErrorTooLong": {
"message": "Error: The generated password is longer than the allowed length!",
@ -159,6 +155,18 @@
"message": "Is KeePassXC running and connected?",
"description": "Password generator error text when KeePassXC is closed."
},
"usernameFieldText": {
"message": "Fill credentials from KeePassXC",
"description": "Username field icon hover text."
},
"usernameLockedFieldText": {
"message": "Database is locked",
"description": "Username field icon hover text when database is locked."
},
"usernameFieldIcon": {
"message": "Username field icon",
"description": "Username field icon text."
},
"defineDismiss": {
"message": "Dismiss",
"description": "Dismiss button text when choosing custom login fields."
@ -224,11 +232,11 @@
"description": "Shown when no username is set in the credentials."
},
"credentialsNoLoginsFound": {
"message": "Error: No logins found.",
"message": "No logins found.",
"description": "Shown when no credentials are found for the current page."
},
"credentialsMultipleFound": {
"message": "Error: More than one login was found in KeePassXC!",
"message": "More than one login was found in KeePassXC!",
"description": "Alert message when trying to fill username and/or password when multiple credentials are found."
},
"credentialsMultipleFoundSecondLine": {
@ -236,7 +244,7 @@
"description": "Alert message when trying to fill username and/or password when multiple credentials are found."
},
"credentialsNoUsernameFound": {
"message": "Error: No credentials for the given username found.",
"message": "No credentials for the given username found.",
"description": "Alert message when no credentials are found for the given username."
},
"credentialExpired": {
@ -287,6 +295,10 @@
"message": "Reopen database",
"description": "Popup reopen database button text."
},
"popupUsernameButton": {
"message": "Add Username-only option for the site",
"description": "Button text for adding Username-only option."
},
"popupErrorEncountered": {
"message": "KeePassXC-Browser has encountered an error:",
"description": "A text shown above error message in the popup."
@ -323,46 +335,78 @@
"message": "KeePassXC-Browser is connected to KeePassXC and has been configured using the following identifier: $1",
"description": "A popuo message when a connection to KeePassXC is succesful."
},
"popupRememberInfoText": {
"popupUsernameFieldDetected": {
"message": "Only a single username field was detected. Add the URL to Site Preferences with Username-only option enabled?",
"description": "Text shown when page can be added to Site Preferences with Username-only option enabled."
},
"rememberInfoText": {
"message": "Username or password changed! Save it?",
"description": "Popup message when username or password has changed."
"description": "Message when username or password has changed."
},
"popupRememberSaving": {
"rememberSaving": {
"message": "Credentials will be saved in connected database with identifier $1",
"description": "A popup message when saving or updating credentials."
"description": "A message when saving or updating credentials."
},
"popupRememberNewUsername": {
"rememberNewUsername": {
"message": "The following username is currently not saved: $1",
"description": "A popup message shown when a new username is detected when saving credentials."
"description": "A message shown when a new username is detected when saving credentials."
},
"popupRememberUsernameExists": {
"rememberUsernameExists": {
"message": "The credentials with the used username are marked bold: $1",
"description": "A popup message shown when a existing username is detected when saving credentials."
"description": "A message shown when a existing username is detected when saving credentials."
},
"popupRememberChooseCredentials": {
"rememberChooseCredentials": {
"message": "Please choose the credentials you want to update.",
"description": "A popup message shown choosing what credentials user wants to update."
"description": "A message shown choosing what credentials user wants to update."
},
"popupRememberChooseGroup": {
"rememberChooseGroup": {
"message": "Please choose the group you want to add the new credentials.",
"description": "A popup message shown choosing what group user wants to use for new credentials."
"description": "A message shown choosing what group user wants to use for new credentials."
},
"popupRememberInfoDefaultGroupNotFound": {
"message": "Info: Default group created in connected database.",
"rememberInfoDefaultGroupNotFound": {
"message": "Default group created in connected database.",
"description": "Info message shown when default group set cannot be found."
},
"popupRememberErrorCreatingNewGroup": {
"message": "Error: New group(s) cannot be created.",
"rememberErrorCreatingNewGroup": {
"message": "New group(s) cannot be created.",
"description": "Error message shown when new groups cannot be created."
},
"popupRememberErrorPasswordNotChanged": {
"message": "Error: Credentials not updated. The password has not been changed.",
"rememberErrorPasswordNotChanged": {
"message": "Credentials not updated. The password has not been changed.",
"description": "Error message shown when credential password is not changed."
},
"popupRememberErrorCannotSaveCredentials": {
"message": "Error: Credentials cannot be saved or updated.",
"rememberErrorCannotSaveCredentials": {
"message": "Credentials cannot be saved or updated.",
"description": "Error message shown when credentials cannot be saved or updated."
},
"rememberErrorDatabaseClosed": {
"message": "Credentials cannot be saved or updated to a closed database.",
"description": "Error message shown when credentials cannot be saved or updated and the database is closed."
},
"rememberCredentialsNotSaved": {
"message": "Operation cancelled. Credentials are not updated.",
"description": "Message shown when saving operation was cancelled."
},
"rememberCredentialsSaved": {
"message": "New credentials with username $1 saved.",
"description": "Message shown when new credentials are saved."
},
"rememberCredentialsUpdated": {
"message": "Credentials with username $1 updated.",
"description": "Message shown when new credentials are succesfully updated."
},
"rememberCredentialsExists": {
"message": "Identical credentials already exists. Nothing to update.",
"description": "Message shown when new credentials already found in database."
},
"rememberEmptyUsername": {
"message": "(empty)",
"description": "Means an empty username."
},
"rememberNoPassword": {
"message": "Please fill the password field before saving credentials.",
"description": "Message shown when no password is set when saving credentials."
},
"popupLoginText": {
"message": "Select the login information you would like to get entered into the page.",
"description": "A popup message shown when one or multiple credentials are present."
@ -400,7 +444,7 @@
"description": "Dismiss button text in popup when updating credentials."
},
"popupButtonIgnore": {
"message": "Never ask for this page",
"message": "Don't ask again for this site",
"description": "Ignore button text in popup or in notification when updating credentials."
},
"popupButtonClose": {
@ -491,18 +535,6 @@
"message": "Reset",
"description": "Reset button text for keyboard shortcuts."
},
"optionsLabelBlinkTime": {
"message": "Blink Time:",
"description": "Blink Time option text."
},
"optionsLabelRedirectOffset": {
"message": "Redirect Offset:",
"description": "Redirect Offset option text."
},
"optionsLabelRedirectAllowance": {
"message": "Maximum (Number of) Redirects:",
"description": "RMaximum (Number of) Redirects options text."
},
"optionsLabelDefaultGroup": {
"message": "Default group for saving new passwords:",
"description": "Default group options text."
@ -520,8 +552,8 @@
"description": "Default checkbox explanation help text."
},
"optionsCheckboxUsePasswordGenerator": {
"message": "Activate password generator.",
"description": "Activate password generator checkbox text."
"message": "Activate password generator icons.",
"description": "Activate password generator icons checkbox text."
},
"optionsCheckboxAutoRetrieveCredentials": {
"message": "Automatically retrieve credentials.",
@ -560,7 +592,7 @@
"description": "Save domain only checkbox text."
},
"optionsCheckboxShowLoginNotifications": {
"message": "Show a notification when new credentials can be saved to the database.",
"message": "Show a banner on the page when new credentials can be saved to the database.",
"description": "Show login notifications checkbox text."
},
"optionsCheckboxAutoFillAndSend": {
@ -591,24 +623,8 @@
"message": "Keyboard shortcuts",
"description": "Keyboard shortcut header text."
},
"optionsBlinkTimeHelpText": {
"message": "Maximum time (ms) the icon should blink after detecting new credentials",
"description": "Blink Time option help text."
},
"optionsRedirectOffsetHelpText": {
"message": "Minimum time (ms) the icon should blink before deactivating due to page redirects.",
"description": "Redirect Offset option help text."
},
"optionsRedirectOffsetHelpTextSecond": {
"message": "-1 to only use blink time ignoring Maximum Redirects (old behavior)",
"description": "Redirect Offset option help text, second part."
},
"optionsRedirectAllowanceHelpText": {
"message": "How many pages should the tab cycle through after the redirect offset before deactivating the icon",
"description": "Redirect Allowance option help text."
},
"optionsUsePasswordGeneratorHelpText": {
"message": "Adds a button to password fields for generating a new password.",
"message": "Adds an icon to password fields for generating a new password.",
"description": "Password Generator option help text."
},
"optionsUsePasswordGeneratorHelpTextSecond": {
@ -804,7 +820,7 @@
"description": "Site preferences info text, first part."
},
"optionsSitePreferencesTabHelpTextSecond": {
"message": "To ignore new/modified credentials on a specific site, add them below or click the blinking KeePassXC-Browser icon and select",
"message": "To ignore new/modified credentials on a specific site, add them below or when using the credential banner on the page select",
"description": "Site preferences info text, second part."
},
"optionsSitePreferencesTabHelpTextThird": {

View file

@ -2,18 +2,10 @@
const browserAction = {};
const BLINK_TIMEOUT_DEFAULT = 7500;
const BLINK_TIMEOUT_REDIRECT_THRESHOLD_TIME_DEFAULT = -1;
const BLINK_TIMEOUT_REDIRECT_COUNT_DEFAULT = 1;
// Milliseconds for intervall (e.g. to update browserAction)
const _interval = 250;
let _loop = null;
browserAction.show = function(callback, tab) {
browserAction.show = function(tab) {
let data = {};
if (!page.tabs[tab.id] || page.tabs[tab.id].stack.length === 0) {
browserAction.showDefault(callback, tab);
browserAction.showDefault(tab);
return;
} else {
data = page.tabs[tab.id].stack[page.tabs[tab.id].stack.length - 1];
@ -32,106 +24,31 @@ browserAction.show = function(callback, tab) {
}
};
browserAction.update = function(interval) {
if (!page.tabs[page.currentTabId] || page.tabs[page.currentTabId].stack.length === 0) {
return;
}
const data = page.tabs[page.currentTabId].stack[page.tabs[page.currentTabId].stack.length - 1];
if (data.visibleForMilliSeconds !== undefined && data.visibleForMilliSeconds !== -1) {
if (data.visibleForMilliSeconds <= 0) {
browserAction.stackPop(page.currentTabId);
browserAction.disableLoop();
browserAction.show(null, { 'id': page.currentTabId });
page.clearCredentials(page.currentTabId);
return;
}
data.visibleForMilliSeconds -= interval;
}
if (data.intervalIcon) {
data.intervalIcon.counter += 1;
if (data.intervalIcon.counter < data.intervalIcon.max) {
return;
}
data.intervalIcon.counter = 0;
data.intervalIcon.index += 1;
if (data.intervalIcon.index > data.intervalIcon.icons.length - 1) {
data.intervalIcon.index = 0;
}
browser.browserAction.setIcon({
tabId: page.currentTabId,
path: '/icons/toolbar/' + browserAction.generateIconName(null, data.intervalIcon.icons[data.intervalIcon.index])
});
}
};
browserAction.showDefault = function(callback, tab) {
browserAction.showDefault = async function(tab) {
const stackData = {
level: 1,
iconType: 'normal',
popup: 'popup.html'
};
keepass.isConfigured().then((response) => {
if (!response || keepass.isDatabaseClosed || !keepass.isKeePassXCAvailable || page.tabs[tab.id].errorMessage) {
stackData.iconType = 'cross';
}
if (page.tabs[tab.id].loginList.length > 0) {
stackData.iconType = 'questionmark';
stackData.popup = 'popup_login.html';
}
browserAction.stackUnshift(stackData, tab.id);
browserAction.disableLoop();
browserAction.show(null, tab);
const response = await keepass.isConfigured().catch((err) => {
console.log('Error: Cannot show default popup: ' + err);
});
if (!response || keepass.isDatabaseClosed || !keepass.isKeePassXCAvailable) {
stackData.iconType = 'cross';
}
if (page.tabs[tab.id] && page.tabs[tab.id].loginList.length > 0) {
stackData.iconType = 'questionmark';
stackData.popup = 'popup_login.html';
}
browserAction.stackUnshift(stackData, tab.id);
browserAction.show(tab);
};
browserAction.stackAdd = function(callback, tab, icon, popup, level, push, visibleForMilliSeconds, visibleForPageUpdates, redirectOffset, dontShow) {
const id = tab.id || page.currentTabId;
if (!level) {
level = 1;
}
const stackData = {
level: level,
icon: icon
};
if (popup) {
stackData.popup = popup;
}
if (visibleForMilliSeconds !== undefined) {
stackData.visibleForMilliSeconds = visibleForMilliSeconds;
}
if (visibleForPageUpdates !== undefined) {
stackData.visibleForPageUpdates = visibleForPageUpdates;
}
if (redirectOffset !== undefined) {
stackData.redirectOffset = redirectOffset;
}
if (push) {
browserAction.stackPush(stackData, id);
} else {
browserAction.stackUnshift(stackData, id);
}
if (!dontShow) {
browserAction.show(null, { 'id': id });
}
};
browserAction.removeLevelFromStack = function(callback, tab, level, type, dontShow) {
browserAction.removeLevelFromStack = function(tab, level, type, dontShow) {
if (!page.tabs[tab.id]) {
return;
}
@ -156,7 +73,7 @@ browserAction.removeLevelFromStack = function(callback, tab, level, type, dontSh
page.tabs[tab.id].stack = newStack;
if (!dontShow) {
browserAction.show(callback, tab);
browserAction.show(tab);
}
};
@ -167,132 +84,16 @@ browserAction.stackPop = function(tabId) {
browserAction.stackPush = function(data, tabId) {
const id = tabId || page.currentTabId;
browserAction.removeLevelFromStack(null, { 'id': id }, data.level, '<=', true);
browserAction.removeLevelFromStack({ 'id': id }, data.level, '<=', true);
page.tabs[id].stack.push(data);
};
browserAction.stackUnshift = function(data, tabId) {
const id = tabId || page.currentTabId;
browserAction.removeLevelFromStack(null, { 'id': id }, data.level, '<=', true);
browserAction.removeLevelFromStack({ 'id': id }, data.level, '<=', true);
page.tabs[id].stack.unshift(data);
};
browserAction.removeRememberPopup = function(callback, tab, removeImmediately) {
if (!page.tabs[tab.id]) {
return;
}
if (page.tabs[tab.id].stack.length === 0) {
page.clearCredentials(tab.id);
return;
}
const data = page.tabs[tab.id].stack[page.tabs[tab.id].stack.length - 1];
if (removeImmediately || !isNaN(data.visibleForPageUpdates)) {
const currentMS = Date.now();
if (removeImmediately || (data.visibleForPageUpdates <= 0 && data.redirectOffset > 0)) {
browserAction.stackPop(tab.id);
browserAction.show(null, { 'id': tab.id });
page.clearCredentials(tab.id);
} else if (!isNaN(data.visibleForPageUpdates) && data.redirectOffset > 0 && currentMS >= data.redirectOffset) {
data.visibleForPageUpdates -= 1;
}
}
};
browserAction.setRememberPopup = function(tabId, username, password, url, usernameExists, credentialsList) {
browser.storage.local.get({ 'settings': {} }).then(function(item) {
const settings = item.settings;
// Don't show anything if the site is in the ignore
if (settings.sitePreferences !== undefined) {
for (const site of settings.sitePreferences) {
if (site.ignore === IGNORE_NORMAL && (site.url === url || siteMatch(site.url, url))) {
return;
}
}
}
const id = tabId || page.currentTabId;
let timeoutMinMillis = Number(getValueOrDefault(settings, 'blinkMinTimeout', BLINK_TIMEOUT_REDIRECT_THRESHOLD_TIME_DEFAULT, -1));
if (timeoutMinMillis > 0) {
timeoutMinMillis += Date.now();
}
const blinkTimeout = getValueOrDefault(settings, 'blinkTimeout', BLINK_TIMEOUT_DEFAULT, -1);
const pageUpdateAllowance = getValueOrDefault(settings, 'allowedRedirect', BLINK_TIMEOUT_REDIRECT_COUNT_DEFAULT, 0);
const stackData = {
visibleForMilliSeconds: blinkTimeout,
visibleForPageUpdates: pageUpdateAllowance,
redirectOffset: timeoutMinMillis,
level: 10,
intervalIcon: {
index: 0,
counter: 0,
max: 2,
icons: [ 'icon_remember_red_background.png', 'icon_remember_red_lock.png' ]
},
icon: 'icon_remember_red_background.png',
popup: 'popup_remember.html'
};
browserAction.activateLoop();
browserAction.stackPush(stackData, id);
page.tabs[id].credentials = {
username: username,
password: password,
url: url,
usernameExists: usernameExists,
list: credentialsList
};
browserAction.show(null, { 'id': id });
if (page.settings.showLoginNotifications) {
const message = tr('rememberCredentialsPopup');
const buttons = [
{
'title': tr('popupButtonClose')
},
{
'title': tr('popupButtonIgnore')
}
];
browser.notifications.create({
'type': 'basic',
'iconUrl': browser.extension.getURL('icons/keepassxc_64x64.png'),
'title': 'KeePassXC-Browser',
'message': message,
'buttons': buttons
});
browser.notifications.onButtonClicked.addListener((notificationId, index) => {
browser.notifications.clear(notificationId);
if (index === 1) {
browserAction.ignoreSite(url);
}
});
}
});
};
function getValueOrDefault(settings, key, defaultVal, min) {
try {
let val = settings[key];
if (isNaN(val) || val < min) {
val = defaultVal;
}
return val;
} catch (e) {
return defaultVal;
}
}
browserAction.generateIconName = function(iconType, icon) {
if (icon) {
return icon;
@ -306,33 +107,17 @@ browserAction.generateIconName = function(iconType, icon) {
return name;
};
browserAction.ignoreSite = function(url) {
browser.windows.getCurrent().then(() => {
// Get current active window
browser.tabs.query({ 'active': true, 'currentWindow': true }).then((tabs) => {
const tab = tabs[0];
browserAction.ignoreSite = async function(url) {
await browser.windows.getCurrent();
// Send the message to the current tab's content script
browser.runtime.getBackgroundPage().then(() => {
browser.tabs.sendMessage(tab.id, {
action: 'ignore_site',
args: [ url ]
});
});
});
// Get current active window
const tabs = await browser.tabs.query({ 'active': true, 'currentWindow': true });
const tab = tabs[0];
// Send the message to the current tab's content script
await browser.runtime.getBackgroundPage();
browser.tabs.sendMessage(tab.id, {
action: 'ignore_site',
args: [ url ]
});
};
// Interval which updates the browserAction (e.g. blinking icon)
browserAction.activateLoop = function() {
if (_loop === null) {
_loop = setInterval(function() {
browserAction.update(_interval);
}, _interval);
}
};
browserAction.disableLoop = function() {
clearInterval(_loop);
_loop = null;
};

View file

@ -2,302 +2,263 @@
const kpxcEvent = {};
kpxcEvent.onMessage = function(request, sender, callback) {
kpxcEvent.onMessage = async function(request, sender) {
if (request.action in kpxcEvent.messageHandlers) {
//console.log('onMessage(' + request.action + ') for #' + sender.tab.id);
if (!sender.hasOwnProperty('tab') || sender.tab.id < 1) {
sender.tab = {};
sender.tab.id = page.currentTabId;
}
kpxcEvent.invoke(kpxcEvent.messageHandlers[request.action], callback, sender.tab.id, request.args);
// onMessage closes channel for callback automatically
// if this method does not return true
if (callback !== undefined) {
return true;
}
return await kpxcEvent.messageHandlers[request.action](sender.tab, request.args);
}
};
/**
* Get interesting information about the given tab.
* Function adapted from AdBlock-Plus.
*
* @param {function} handler to call after invoke
* @param {function} callback to call after handler or null
* @param {integer} senderTabId
* @param {array} args
* @param {bool} secondTime
* @returns null (asynchronous)
*/
kpxcEvent.invoke = function(handler, callback, senderTabId, args, secondTime) {
if (senderTabId < 1) {
return;
}
if (!page.tabs[senderTabId]) {
page.createTabEntry(senderTabId);
}
// Remove information from no longer existing tabs
page.removePageInformationFromNotExistingTabs();
browser.tabs.get(senderTabId).then((tab) => {
if (!tab) {
return;
}
if (!tab.url) {
// Issue 6877: tab URL is not set directly after you opened a window
// using window.open()
if (!secondTime) {
window.setTimeout(function() {
kpxcEvent.invoke(handler, callback, senderTabId, args, true);
}, 250);
}
return;
}
if (!page.tabs[tab.id]) {
page.createTabEntry(tab.id);
}
args = args || [];
args.unshift(tab);
args.unshift(callback);
if (handler) {
handler.apply(this, args);
} else {
console.log('undefined handler for tab ' + tab.id);
}
}).catch((e) => {
console.log(e);
});
};
kpxcEvent.onShowNotification = function(callback, tab, message) {
if (page.settings.showNotifications) {
showNotification(message);
}
};
kpxcEvent.showStatus = function(configured, tab, callback) {
kpxcEvent.showStatus = async function(tab, configured) {
let keyId = null;
if (configured && keepass.databaseHash !== '') {
keyId = keepass.keyRing[keepass.databaseHash].id;
}
browserAction.showDefault(null, tab);
browserAction.showDefault(tab);
const errorMessage = page.tabs[tab.id].errorMessage;
callback({
return {
identifier: keyId,
configured: configured,
databaseClosed: keepass.isDatabaseClosed,
keePassXCAvailable: keepass.isKeePassXCAvailable,
encryptionKeyUnrecognized: keepass.isEncryptionKeyUnrecognized,
associated: keepass.isAssociated(),
error: errorMessage || null
error: errorMessage || null,
usernameFieldDetected: page.usernameFieldDetected
};
};
kpxcEvent.onLoadSettings = async function() {
return await page.initSettings().catch((err) => {
console.log('onLoadSettings error: ' + err);
return Promise.reject();
});
};
kpxcEvent.onLoadSettings = function(callback, tab) {
page.initSettings().then((settings) => {
callback(settings);
}, (err) => {
console.log('error loading settings: ' + err);
kpxcEvent.onLoadKeyRing = async function() {
const item = await browser.storage.local.get({ 'keyRing': {} }).catch((err) => {
console.log('kpxcEvent.onLoadKeyRing error: ' + err);
return Promise.reject();
});
keepass.keyRing = item.keyRing;
if (keepass.isAssociated() && !keepass.keyRing[keepass.associated.hash]) {
keepass.associated = {
'value': false,
'hash': null
};
}
return item.keyRing;
};
kpxcEvent.onLoadKeyRing = function(callback, tab) {
browser.storage.local.get({ 'keyRing': {} }).then(function(item) {
keepass.keyRing = item.keyRing;
if (keepass.isAssociated() && !keepass.keyRing[keepass.associated.hash]) {
keepass.associated = {
'value': false,
'hash': null
};
}
callback(item.keyRing);
}, (err) => {
console.log('error loading keyRing: ' + err);
});
kpxcEvent.onSaveSettings = async function(tab, args = []) {
const [ settings ] = args;
browser.storage.local.set({ 'settings': settings });
kpxcEvent.onLoadSettings(tab);
return Promise.resolve();
};
kpxcEvent.onSaveSettings = function(callback, tab, settings) {
browser.storage.local.set({ 'settings': settings }).then(function() {
kpxcEvent.onLoadSettings(callback, tab);
});
};
kpxcEvent.onGetStatus = function(callback, tab, internalPoll = false, triggerUnlock = false) {
kpxcEvent.onGetStatus = async function(tab, args = []) {
// When internalPoll is true the event is triggered from content script in intervals -> don't poll KeePassXC
if (!internalPoll) {
keepass.testAssociation((response) => {
try {
const [ internalPoll = false, triggerUnlock = false ] = args;
if (!internalPoll) {
const response = await keepass.testAssociation(tab, [ true, triggerUnlock ]);
if (!response) {
kpxcEvent.showStatus(false, tab, callback);
return;
return kpxcEvent.showStatus(tab, false);
}
}
keepass.isConfigured().then((configured) => {
kpxcEvent.showStatus(configured, tab, callback);
});
}, tab, true, triggerUnlock);
} else {
keepass.isConfigured().then((configured) => {
kpxcEvent.showStatus(configured, tab, callback);
});
const configured = await keepass.isConfigured();
return kpxcEvent.showStatus(tab, configured);
} catch (err) {
console.log('Error: No status shown: ' + err);
return Promise.reject();
}
};
kpxcEvent.onReconnect = async function(callback, tab) {
const configured = await keepass.reconnect(callback, tab);
kpxcEvent.onReconnect = async function(tab) {
const configured = await keepass.reconnect(tab);
if (configured) {
browser.tabs.sendMessage(tab.id, {
action: 'redetect_fields'
}).catch((err) => {
console.log(err);
return;
});
}
kpxcEvent.showStatus(configured, tab, callback);
return kpxcEvent.showStatus(tab, configured);
};
kpxcEvent.lockDatabase = function(callback, tab) {
keepass.lockDatabase(tab).then(() => {
kpxcEvent.showStatus(true, tab, callback);
});
kpxcEvent.lockDatabase = async function(tab) {
try {
await keepass.lockDatabase(tab);
return kpxcEvent.showStatus(tab, false);
} catch (err) {
console.log('kpxcEvent.lockDatabase error: ' + err);
return false;
}
};
kpxcEvent.onPopStack = function(callback, tab) {
kpxcEvent.onPopStack = function(tab) {
browserAction.stackPop(tab.id);
browserAction.show(null, tab);
browserAction.show(tab);
return Promise.resolve();
};
kpxcEvent.onGetTabInformation = function(callback, tab) {
kpxcEvent.onGetTabInformation = function(tab) {
const id = tab.id || page.currentTabId;
callback(page.tabs[id]);
return Promise.resolve(page.tabs[id]);
};
kpxcEvent.onGetConnectedDatabase = function(callback, tab) {
callback({
kpxcEvent.onGetConnectedDatabase = function() {
return Promise.resolve({
count: Object.keys(keepass.keyRing).length,
identifier: (keepass.keyRing[keepass.associated.hash]) ? keepass.keyRing[keepass.associated.hash].id : null
});
};
kpxcEvent.onGetKeePassXCVersions = function(callback, tab) {
kpxcEvent.onGetKeePassXCVersions = async function(tab) {
if (keepass.currentKeePassXC === '') {
keepass.getDatabaseHash((res) => {
callback({ 'current': keepass.currentKeePassXC, 'latest': keepass.latestKeePassXC.version });
}, tab);
} else {
callback({ 'current': keepass.currentKeePassXC, 'latest': keepass.latestKeePassXC.version });
await keepass.getDatabaseHash(tab);
return { 'current': keepass.currentKeePassXC, 'latest': keepass.latestKeePassXC.version };
}
return Promise.resolve({ 'current': keepass.currentKeePassXC, 'latest': keepass.latestKeePassXC.version });
};
kpxcEvent.onCheckUpdateKeePassXC = function(callback, tab) {
kpxcEvent.onCheckUpdateKeePassXC = async function() {
keepass.checkForNewKeePassXCVersion();
callback({ current: keepass.currentKeePassXC.version, latest: keepass.latestKeePassXC.version });
return { current: keepass.currentKeePassXC.version, latest: keepass.latestKeePassXC.version };
};
kpxcEvent.onUpdateAvailableKeePassXC = function(callback, tab) {
callback(page.settings.checkUpdateKeePassXC > 0 ? keepass.keePassXCUpdateAvailable() : false);
kpxcEvent.onUpdateAvailableKeePassXC = async function() {
return (page.settings.checkUpdateKeePassXC > 0) ? keepass.keePassXCUpdateAvailable() : false;
};
kpxcEvent.onRemoveCredentialsFromTabInformation = function(callback, tab) {
kpxcEvent.onRemoveCredentialsFromTabInformation = function(tab) {
const id = tab.id || page.currentTabId;
page.clearCredentials(id);
page.clearSubmittedCredentials();
return Promise.resolve();
};
kpxcEvent.onSetRememberPopup = function(callback, tab, username, password, url, usernameExists, credentialsList) {
keepass.testAssociation((response) => {
if (response) {
keepass.isConfigured().then((configured) => {
if (configured) {
browserAction.setRememberPopup(tab.id, username, password, url, usernameExists, credentialsList);
}
}).catch((e) => {
console.log(e);
});
}
}, tab);
};
kpxcEvent.onLoginPopup = function(callback, tab, logins) {
kpxcEvent.onLoginPopup = function(tab, logins) {
const stackData = {
level: 1,
iconType: 'questionmark',
popup: 'popup_login.html'
};
browserAction.stackUnshift(stackData, tab.id);
page.tabs[tab.id].loginList = logins;
browserAction.show(null, tab);
if (logins.length > 0) {
page.tabs[tab.id].loginList = logins[0];
}
browserAction.show(tab);
return Promise.resolve();
};
kpxcEvent.initHttpAuth = function(callback) {
kpxcEvent.initHttpAuth = function() {
httpAuth.init();
callback();
};
return Promise.resolve();
}
kpxcEvent.onHTTPAuthPopup = function(callback, tab, data) {
kpxcEvent.onHTTPAuthPopup = function(tab, data) {
const stackData = {
level: 1,
iconType: 'questionmark',
popup: 'popup_httpauth.html'
};
browserAction.stackUnshift(stackData, tab.id);
page.tabs[tab.id].loginList = data;
browserAction.show(null, tab);
browserAction.show(tab);
return Promise.resolve();
};
kpxcEvent.onMultipleFieldsPopup = function(callback, tab) {
kpxcEvent.onMultipleFieldsPopup = function(tab) {
const stackData = {
level: 1,
iconType: 'normal',
popup: 'popup_multiple-fields.html'
};
browserAction.stackUnshift(stackData, tab.id);
browserAction.show(null, tab);
browserAction.show(tab);
return Promise.resolve();
};
kpxcEvent.pageClearLogins = function(callback, tab, alreadyCalled) {
kpxcEvent.pageClearLogins = function(tab, alreadyCalled) {
if (!alreadyCalled) {
page.clearLogins(tab.id);
}
callback();
return Promise.resolve();
};
kpxcEvent.pageGetLoginId = function(callback, tab) {
callback(page.loginId);
kpxcEvent.pageGetLoginId = function() {
return Promise.resolve(page.loginId);
};
kpxcEvent.pageSetLoginId = function(callback, tab, loginId) {
kpxcEvent.pageSetLoginId = function(tab, loginId) {
page.loginId = loginId;
return Promise.resolve();
};
kpxcEvent.pageClearSubmitted = function() {
page.clearSubmittedCredentials();
return Promise.resolve();
}
kpxcEvent.pageGetSubmitted = function() {
return Promise.resolve(page.submittedCredentials);
};
kpxcEvent.pageSetSubmitted = function(tab, args = []) {
const [ submitted, username, password, url, oldCredentials ] = args;
page.setSubmittedCredentials(submitted, username, password, url, oldCredentials);
return Promise.resolve();
};
kpxcEvent.onUsernameFieldDetected = function(tab, args = []) {
page.usernameFieldDetected = args[0];
};
// All methods named in this object have to be declared BEFORE this!
kpxcEvent.messageHandlers = {
'add_credentials': keepass.addCredentials,
'associate': keepass.associate,
'check_database_hash': keepass.checkDatabaseHash,
'check_update_keepassxc': kpxcEvent.onCheckUpdateKeePassXC,
'create_new_group': keepass.createNewGroup,
'enable_automatic_reconnect': keepass.enableAutomaticReconnect,
'disable_automatic_reconnect': keepass.disableAutomaticReconnect,
'generate_password': keepass.generatePassword,
'get_connected_database': kpxcEvent.onGetConnectedDatabase,
'get_database_hash': keepass.getDatabaseHash,
'get_database_groups': keepass.getDatabaseGroups,
'get_keepassxc_versions': kpxcEvent.onGetKeePassXCVersions,
'get_status': kpxcEvent.onGetStatus,
'get_tab_information': kpxcEvent.onGetTabInformation,
'init_http_auth': kpxcEvent.initHttpAuth,
'is_connected': keepass.getIsKeePassXCAvailable,
'load_keyring': kpxcEvent.onLoadKeyRing,
'load_settings': kpxcEvent.onLoadSettings,
'lock-database': kpxcEvent.lockDatabase,
'page_clear_logins': kpxcEvent.pageClearLogins,
'page_clear_submitted': kpxcEvent.pageClearSubmitted,
'page_get_login_id': kpxcEvent.pageGetLoginId,
'page_get_submitted': kpxcEvent.pageGetSubmitted,
'page_set_login_id': kpxcEvent.pageSetLoginId,
'page_set_submitted': kpxcEvent.pageSetSubmitted,
'pop_stack': kpxcEvent.onPopStack,
'popup_login': kpxcEvent.onLoginPopup,
'popup_multiple-fields': kpxcEvent.onMultipleFieldsPopup,
@ -306,9 +267,7 @@ kpxcEvent.messageHandlers = {
'retrieve_credentials': keepass.retrieveCredentials,
'show_default_browseraction': browserAction.showDefault,
'update_credentials': keepass.updateCredentials,
'username_field_detected': kpxcEvent.onUsernameFieldDetected,
'save_settings': kpxcEvent.onSaveSettings,
'set_remember_credentials': kpxcEvent.onSetRememberPopup,
'show_notification': kpxcEvent.onShowNotification,
'stack_add': browserAction.stackAdd,
'update_available_keepassxc': kpxcEvent.onUpdateAvailableKeePassXC
};

View file

@ -47,11 +47,10 @@ httpAuth.handleRequestCallback = function(details, callback) {
httpAuth.processPendingCallbacks(details, callback, callback);
};
httpAuth.retrieveCredentials = function(tabId, url, submitUrl, forceCallback) {
return new Promise((resolve, reject) => {
keepass.retrieveCredentials((logins) => {
resolve(logins);
}, tabId, url, submitUrl, forceCallback, false, true);
httpAuth.retrieveCredentials = async function(tabId, url, submitUrl) {
return await keepass.retrieveCredentials(tabId, [ url, submitUrl, false, true ]).catch((err) => {
console.log('httpAuth.retrieveCredentials error: ' + err);
return Promise.reject();
});
};
@ -69,7 +68,7 @@ httpAuth.processPendingCallbacks = async function(details, resolve, reject) {
details.searchUrl = (details.isProxy && details.proxyUrl) ? details.proxyUrl : details.url;
const logins = await httpAuth.retrieveCredentials({ 'id': details.tabId }, details.searchUrl, details.searchUrl, true);
const logins = await httpAuth.retrieveCredentials({ 'id': details.tabId }, details.searchUrl, details.searchUrl);
httpAuth.loginOrShowCredentials(logins, details, resolve, reject);
};
@ -87,7 +86,7 @@ httpAuth.loginOrShowCredentials = function(logins, details, resolve, reject) {
if (page.settings.showNotifications) {
showNotification(tr('multipleCredentialsDetected'));
}
kpxcEvent.onHTTPAuthPopup(null, { 'id': details.tabId }, { 'logins': logins, 'url': details.searchUrl, 'resolve': resolve });
kpxcEvent.onHTTPAuthPopup({ 'id': details.tabId }, { 'logins': logins, 'url': details.searchUrl, 'resolve': resolve });
}
} else {
reject({ cancel: false }); // No logins found

View file

@ -1,15 +1,17 @@
'use strict';
keepass.migrateKeyRing().then(() => {
page.initSettings().then(() => {
page.initOpenedTabs().then(() => {
httpAuth.init();
keepass.reconnect().then(() => {
keepass.enableAutomaticReconnect();
});
});
});
});
(async () => {
try {
await keepass.migrateKeyRing();
await page.initSettings();
await page.initOpenedTabs();
await httpAuth.init();
await keepass.reconnect();
await keepass.enableAutomaticReconnect();
} catch (e) {
console.log('init.js failed');
}
})();
/**
* Generate information structure for created tab and invoke all needed
@ -18,10 +20,12 @@ keepass.migrateKeyRing().then(() => {
*/
browser.tabs.onCreated.addListener((tab) => {
if (tab.id > 0) {
//console.log('browser.tabs.onCreated(' + tab.id+ ')');
if (tab.selected) {
page.currentTabId = tab.id;
kpxcEvent.invoke(page.switchTab, null, tab.id, []);
if (!page.tabs[tab.id]) {
page.createTabEntry(tab.id);
}
page.switchTab(tab);
}
}
});
@ -43,20 +47,24 @@ browser.tabs.onRemoved.addListener((tabId, removeInfo) => {
* Invoke functions to retrieve credentials for focused tab
* @param {object} activeInfo
*/
browser.tabs.onActivated.addListener((activeInfo) => {
browser.tabs.onActivated.addListener(async function(activeInfo) {
// Remove possible credentials from old tab information
page.clearCredentials(page.currentTabId, true);
browserAction.removeRememberPopup(null, { 'id': page.currentTabId }, true);
browser.tabs.get(activeInfo.tabId).then((info) => {
try {
const info = await browser.tabs.get(activeInfo.tabId);
if (info && info.id) {
page.currentTabId = info.id;
if (info.status === 'complete') {
//console.log('kpxcEvent.invoke(page.switchTab, null, '+info.id + ', []);');
kpxcEvent.invoke(page.switchTab, null, info.id, []);
if (!page.tabs[info.id]) {
page.createTabEntry(info.id);
}
page.switchTab(info);
}
}
});
} catch (err) {
console.log('Error: ' + err);
}
});
/**
@ -71,8 +79,10 @@ browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
}
if (changeInfo.status === 'complete') {
browserAction.showDefault(null, tab);
kpxcEvent.invoke(browserAction.removeRememberPopup, null, tabId, []);
browserAction.showDefault(tab);
if (!page.tabs[tab.id]) {
page.createTabEntry(tab.id);
}
}
});
@ -82,7 +92,6 @@ const contextMenuItems = [
{ title: tr('contextMenuFillUsernameAndPassword'), action: 'fill_username_password' },
{ title: tr('contextMenuFillPassword'), action: 'fill_password' },
{ title: tr('contextMenuFillTOTP'), action: 'fill_totp' },
{ title: tr('contextMenuShowPasswordGeneratorIcons'), action: 'activate_password_generator' },
{ title: tr('contextMenuShowPasswordGenerator'), action: 'show_password_generator' },
{ title: tr('contextMenuSaveCredentials'), action: 'remember_credentials' }
];
@ -109,12 +118,11 @@ for (const item of contextMenuItems) {
}
// Listen for keyboard shortcuts specified by user
browser.commands.onCommand.addListener((command) => {
browser.commands.onCommand.addListener(async (command) => {
if (contextMenuItems.some(e => e.action === command)) {
browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => {
if (tabs.length) {
browser.tabs.sendMessage(tabs[0].id, { action: command });
}
});
const tabs = await browser.tabs.query({ active: true, currentWindow: true });
if (tabs.length) {
browser.tabs.sendMessage(tabs[0].id, { action: command });
}
}
});

File diff suppressed because it is too large Load diff

View file

@ -4,7 +4,7 @@ const defaultSettings = {
checkUpdateKeePassXC: 3,
autoCompleteUsernames: true,
autoFillAndSend: false,
usePasswordGenerator: true,
usePasswordGeneratorIcons: false,
autoFillSingleEntry: false,
autoSubmit: false,
autoRetrieveCredentials: true,
@ -17,79 +17,86 @@ const defaultSettings = {
};
var page = {};
page.tabs = [];
page.currentTabId = -1;
page.blockedTabs = [];
page.currentTabId = -1;
page.loginId = -1;
page.submitted = false;
page.submittedCredentials = {};
page.tabs = [];
page.usernameFieldDetected = false;
page.initSettings = function() {
return new Promise((resolve, reject) => {
browser.storage.local.get({ 'settings': {} }).then((item) => {
page.settings = item.settings;
if (!('checkUpdateKeePassXC' in page.settings)) {
page.settings.checkUpdateKeePassXC = defaultSettings.checkUpdateKeePassXC;
}
if (!('autoCompleteUsernames' in page.settings)) {
page.settings.autoCompleteUsernames = defaultSettings.autoCompleteUsernames;
}
if (!('autoFillAndSend' in page.settings)) {
page.settings.autoFillAndSend = defaultSettings.autoFillAndSend;
}
if (!('usePasswordGenerator' in page.settings)) {
page.settings.usePasswordGenerator = defaultSettings.usePasswordGenerator;
}
if (!('autoFillSingleEntry' in page.settings)) {
page.settings.autoFillSingleEntry = defaultSettings.autoFillSingleEntry;
}
if (!('autoSubmit' in page.settings)) {
page.settings.autoSubmit = defaultSettings.autoSubmit;
}
if (!('autoRetrieveCredentials' in page.settings)) {
page.settings.autoRetrieveCredentials = defaultSettings.autoRetrieveCredentials;
}
if (!('showNotifications' in page.settings)) {
page.settings.showNotifications = defaultSettings.showNotifications;
}
if (!('showLoginNotifications' in page.settings)) {
page.settings.showLoginNotifications = defaultSettings.showLoginNotifications;
}
if (!('saveDomainOnly' in page.settings)) {
page.settings.saveDomainOnly = defaultSettings.saveDomainOnly;
}
if (!('autoReconnect' in page.settings)) {
page.settings.autoReconnect = defaultSettings.autoReconnect;
}
if (!('defaultGroup' in page.settings)) {
page.settings.defaultGroup = defaultSettings.defaultGroup;
}
if (!('defaultGroupAlwaysAsk' in page.settings)) {
page.settings.defaultGroupAlwaysAsk = defaultSettings.defaultGroupAlwaysAsk;
}
browser.storage.local.set({ 'settings': page.settings });
resolve(page.settings);
});
});
page.initSettings = async function() {
try {
const item = await browser.storage.local.get({ 'settings': {} });
page.settings = item.settings;
if (!('checkUpdateKeePassXC' in page.settings)) {
page.settings.checkUpdateKeePassXC = defaultSettings.checkUpdateKeePassXC;
}
if (!('autoCompleteUsernames' in page.settings)) {
page.settings.autoCompleteUsernames = defaultSettings.autoCompleteUsernames;
}
if (!('autoFillAndSend' in page.settings)) {
page.settings.autoFillAndSend = defaultSettings.autoFillAndSend;
}
if (!('usePasswordGeneratorIcons' in page.settings)) {
page.settings.usePasswordGeneratorIcons = defaultSettings.usePasswordGeneratorIcons;
}
if (!('autoFillSingleEntry' in page.settings)) {
page.settings.autoFillSingleEntry = defaultSettings.autoFillSingleEntry;
}
if (!('autoSubmit' in page.settings)) {
page.settings.autoSubmit = defaultSettings.autoSubmit;
}
if (!('autoRetrieveCredentials' in page.settings)) {
page.settings.autoRetrieveCredentials = defaultSettings.autoRetrieveCredentials;
}
if (!('showNotifications' in page.settings)) {
page.settings.showNotifications = defaultSettings.showNotifications;
}
if (!('showLoginNotifications' in page.settings)) {
page.settings.showLoginNotifications = defaultSettings.showLoginNotifications;
}
if (!('saveDomainOnly' in page.settings)) {
page.settings.saveDomainOnly = defaultSettings.saveDomainOnly;
}
if (!('autoReconnect' in page.settings)) {
page.settings.autoReconnect = defaultSettings.autoReconnect;
}
if (!('defaultGroup' in page.settings)) {
page.settings.defaultGroup = defaultSettings.defaultGroup;
}
if (!('defaultGroupAlwaysAsk' in page.settings)) {
page.settings.defaultGroupAlwaysAsk = defaultSettings.defaultGroupAlwaysAsk;
}
await browser.storage.local.set({ 'settings': page.settings });
return page.settings;
} catch (err) {
console.log('page.initSettings error: ' + err);
return Promise.reject();
}
};
page.initOpenedTabs = function() {
return new Promise((resolve, reject) => {
browser.tabs.query({}).then((tabs) => {
for (const i of tabs) {
page.createTabEntry(i.id);
}
page.initOpenedTabs = async function() {
try {
const tabs = await browser.tabs.query({});
for (const i of tabs) {
page.createTabEntry(i.id);
}
// Set initial tab-ID
browser.tabs.query({ 'active': true, 'currentWindow': true }).then((currentTabs) => {
if (currentTabs.length === 0) {
resolve();
return; // For example: only the background devtools or a popup are opened
}
page.currentTabId = currentTabs[0].id;
browserAction.show(null, currentTabs[0]);
resolve();
});
});
});
// Set initial tab-ID
const currentTabs = await browser.tabs.query({ active: true, currentWindow: true });
if (currentTabs.length === 0) {
return Promise.resolve();
}
page.currentTabId = currentTabs[0].id;
browserAction.show(currentTabs[0]);
return Promise.resolve();
} catch (err) {
console.log('page.initOpenedTabs error: ' + err);
return Promise.reject();
}
};
page.isValidProtocol = function(url) {
@ -98,8 +105,8 @@ page.isValidProtocol = function(url) {
return !(url.indexOf('.') === -1 || (protocol !== 'http' && protocol !== 'https' && protocol !== 'ftp' && protocol !== 'sftp'));
};
page.switchTab = function(callback, tab) {
browserAction.showDefault(null, tab);
page.switchTab = function(tab) {
browserAction.showDefault(tab);
browser.tabs.sendMessage(tab.id, { action: 'activated_tab' }).catch((e) => {});
};
@ -108,7 +115,8 @@ page.clearCredentials = function(tabId, complete) {
return;
}
page.tabs[tabId].credentials = {};
page.usernameFieldDetected = false;
page.tabs[tabId].credentials = [];
delete page.tabs[tabId].credentials;
if (complete) {
@ -128,30 +136,43 @@ page.clearLogins = function(tabId) {
page.tabs[tabId].loginList = [];
};
page.setSubmittedCredentials = function(submitted, username, password, url, oldCredentials) {
page.submittedCredentials.submitted = submitted;
page.submittedCredentials.username = username;
page.submittedCredentials.password = password;
page.submittedCredentials.url = url;
page.submittedCredentials.oldCredentials = oldCredentials;
};
page.clearSubmittedCredentials = function() {
page.submitted = false;
page.submittedCredentials = {};
};
page.createTabEntry = function(tabId) {
page.tabs[tabId] = {
'stack': [],
'errorMessage': null,
'loginList': []
};
page.clearSubmittedCredentials();
};
page.removePageInformationFromNotExistingTabs = function() {
page.removePageInformationFromNotExistingTabs = async function() {
const rand = Math.floor(Math.random() * 1001);
if (rand === 28) {
browser.tabs.query({}).then(function(tabs) {
const tabIds = [];
const infoIds = Object.keys(page.tabs);
const tabs = await browser.tabs.query({});
const tabIds = [];
const infoIds = Object.keys(page.tabs);
for (const t of tabs) {
tabIds[t.id] = true;
}
for (const t of tabs) {
tabIds[t.id] = true;
}
for (const i of infoIds) {
if (!(i in tabIds)) {
delete page.tabs[i];
}
for (const i of infoIds) {
if (!(i in tabIds)) {
delete page.tabs[i];
}
});
}
}
};

View file

@ -34,13 +34,9 @@ kpxcAutocomplete.create = function(input, showListInstantly = false, autoSubmit
kpxcAutocomplete.showList = function(inputField) {
kpxcAutocomplete.closeList();
kpxcAutocomplete.input = inputField;
const div = kpxcUI.createElement('div', 'kpxcAutocomplete-items', { 'id': 'kpxcAutocomplete-list' });
// Element position
const rect = inputField.getBoundingClientRect();
div.style.top = String((rect.top + document.body.scrollTop) + inputField.offsetHeight) + 'px';
div.style.left = String((rect.left + document.body.scrollLeft)) + 'px';
div.style.minWidth = String(inputField.offsetWidth) + 'px';
const div = kpxcUI.createElement('div', 'kpxcAutocomplete-items', { 'id': 'kpxcAutocomplete-list' });
kpxcAutocomplete.updatePosition(inputField, div);
div.style.zIndex = '2147483646';
document.body.append(div);
@ -56,6 +52,10 @@ kpxcAutocomplete.showList = function(inputField) {
// Save index for combination.loginId
const index = Array.prototype.indexOf.call(e.currentTarget.parentElement.childNodes, e.currentTarget);
browser.runtime.sendMessage({
action: 'page_set_login_id', args: [ index ]
});
inputField.value = this.getElementsByTagName('input')[0].value;
kpxcAutocomplete.fillPassword(inputField.value, index);
kpxcAutocomplete.closeList();
@ -188,6 +188,7 @@ kpxcAutocomplete.keyPress = function(e) {
kpxcAutocomplete.fillPassword = function(value, index) {
const fieldId = kpxcAutocomplete.input.getAttribute('data-kpxc-id');
kpxcFields.prepareId(fieldId);
const givenType = kpxcAutocomplete.input.type === 'password' ? 'password' : 'username';
const combination = kpxcFields.getCombination(givenType, fieldId);
combination.loginId = index;
@ -196,6 +197,18 @@ kpxcAutocomplete.fillPassword = function(value, index) {
kpxcAutocomplete.input.setAttribute('fetched', true);
};
kpxcAutocomplete.updatePosition = function(inputField, elem) {
const div = elem || $('.kpxcAutocomplete-items');
if (!div) {
return;
}
const rect = inputField.getBoundingClientRect();
div.style.top = Pixels((rect.top + document.scrollingElement.scrollTop) + inputField.offsetHeight);
div.style.left = Pixels((rect.left + document.scrollingElement.scrollLeft));
div.style.minWidth = Pixels(inputField.offsetWidth);
};
// Detect click outside autocomplete
document.addEventListener('click', function(e) {
if (!e.isTrusted) {
@ -207,7 +220,31 @@ document.addEventListener('click', function(e) {
return;
}
if (e.target !== kpxcAutocomplete.input && (e.target.nodeName !== kpxcAutocomplete.input.nodeName)) {
if (e.target !== kpxcAutocomplete.input &&
!e.target.classList.contains('kpxc-username-icon') &&
e.target.nodeName !== kpxcAutocomplete.input.nodeName) {
kpxcAutocomplete.closeList(e.target);
}
});
// Handle autocomplete position on window resize
window.addEventListener('resize', function() {
if (!kpxc.settings.autoCompleteUsernames) {
return;
}
if (kpxcAutocomplete.input) {
kpxcAutocomplete.updatePosition(kpxcAutocomplete.input);
}
});
// Handle autocomplete position on scroll
window.addEventListener('scroll', function() {
if (!kpxc.settings.autoCompleteUsernames) {
return;
}
if (kpxcAutocomplete.input) {
kpxcAutocomplete.updatePosition(kpxcAutocomplete.input);
}
});

View file

@ -0,0 +1,390 @@
'use strict';
const DEFAULT_BROWSER_GROUP = 'KeePassXC-Browser Passwords';
var kpxcBanner = {};
kpxcBanner.banner = undefined;
kpxcBanner.created = false;
kpxcBanner.credentials = {};
kpxcBanner.destroy = function() {
kpxcBanner.created = false;
kpxcBanner.credentials = {};
const dialog = $('.kpxc-banner-dialog');
if (dialog) {
document.body.removeChild(dialog);
}
browser.runtime.sendMessage({
action: 'remove_credentials_from_tab_information'
});
const banners = document.querySelectorAll('.kpxc-banner');
if (banners.length > 0) {
for (const b of banners) {
document.body.removeChild(b);
}
return;
}
document.body.removeChild(kpxcBanner.banner);
};
kpxcBanner.create = async function(credentials = {}) {
if (!kpxc.settings.showLoginNotifications || kpxcBanner.created) {
return;
}
// Check if database is closed
const state = await browser.runtime.sendMessage({ action: 'check_database_hash' });
if (state === '') {
//kpxcUI.createNotification('error', tr('rememberErrorDatabaseClosed'));
return;
}
// Don't show anything if the site is in the ignore
if (kpxc.settings.sitePreferences !== undefined) {
for (const site of kpxc.settings.sitePreferences) {
if (site.ignore !== IGNORE_NOTHING && (site.url === credentials.url || siteMatch(site.url, credentials.url))) {
return;
}
}
}
kpxcBanner.created = true;
kpxcBanner.credentials = credentials;
const banner = kpxcUI.createElement('div', 'kpxc-banner', { 'id': 'container' });
banner.style.zIndex = '2147483646';
const bannerInfo = kpxcUI.createElement('div', 'banner-info');
const bannerButtons = kpxcUI.createElement('div', 'banner-buttons');
const className = (isFirefox() ? 'kpxc-banner-icon-moz' : 'kpxc-banner-icon');
const icon = kpxcUI.createElement('span', className, { 'alt': 'logo' });
const infoText = kpxcUI.createElement('span', '', {}, tr('rememberInfoText'));
const usernameText = kpxcUI.createElement('span', 'small', {}, tr('popupUsername') + ' ');
const usernameSpan = kpxcUI.createElement('span', 'small info information-username', {}, credentials.username);
const newButton = kpxcUI.createElement('button', 'kpxc-button kpxc-green-button', { 'id': 'kpxc-banner-btn-new' }, tr('popupButtonNew'));
const updateButton = kpxcUI.createElement('button', 'kpxc-button kpxc-orange-button', { 'id': 'kpxc-banner-btn-update' }, tr('popupButtonUpdate'));
const dismissButton = kpxcUI.createElement('button', 'kpxc-button kpxc-red-button', { 'id': 'kpxc-banner-btn-dismiss' }, tr('popupButtonDismiss'));
const separator = kpxcUI.createElement('div', 'separator');
const ignoreCheckbox = kpxcUI.createElement('input', 'kpxc-checkbox', { type: 'checkbox', name: 'ignoreCheckbox' });
const checkboxLabel = kpxcUI.createElement('label', 'kpxc-checkbox-label', { for: 'ignoreCheckbox' }, tr('popupButtonIgnore'));
// No existing credentials to update --> disable Update button
if (credentials.list.length === 0) {
updateButton.classList.remove('kpxc-orange-button');
updateButton.disabled = true;
}
newButton.addEventListener('click', function(e) {
if (!e.isTrusted) {
return;
}
kpxcBanner.saveNewCredentials(credentials);
});
updateButton.addEventListener('click', function(e) {
if (!e.isTrusted) {
return;
}
kpxcBanner.updateCredentials(credentials);
});
dismissButton.addEventListener('click', function(e) {
if (!e.isTrusted) {
return;
}
// If a banner dialog is shown, display the main banner
const dialog = $('.kpxc-banner-dialog');
if (dialog) {
$('#kpxc-banner-btn-new').hidden = false;
$('#kpxc-banner-btn-update').hidden = false;
$('.kpxc-checkbox').disabled = false;
document.body.removeChild(dialog);
} else {
if (ignoreCheckbox.checked) {
kpxc.ignoreSite([ window.top.location.href ]);
}
kpxcBanner.destroy();
}
});
kpxcBanner.banner = banner;
bannerInfo.appendMultiple(icon, infoText, usernameText, usernameSpan);
bannerButtons.appendMultiple(newButton, updateButton, separator, ignoreCheckbox, checkboxLabel, dismissButton);
banner.appendMultiple(bannerInfo, bannerButtons);
document.body.appendChild(banner);
};
kpxcBanner.saveNewCredentials = async function(credentials = {}) {
const result = await browser.runtime.sendMessage({
action: 'get_database_groups'
});
// Only the Root group and no KeePassXC-Browser passwords -> save to default
// Or when default group is not set and defaultGroupAskAlways is disabled -> save to default
if ((result.groups === undefined || (result.groups.length > 0 && result.groups[0].children.length === 0)) ||
(!result.defaultGroupAlwaysAsk && (result.defaultGroup === '' || result.defaultGroup === DEFAULT_BROWSER_GROUP))) {
const res = await browser.runtime.sendMessage({
action: 'add_credentials',
args: [ credentials.username, credentials.password, credentials.url ]
});
kpxcBanner.verifyResult(res);
return;
} else if (!result.defaultGroupAlwaysAsk && (result.defaultGroup !== '' || result.defaultGroup !== DEFAULT_BROWSER_GROUP)) {
// Another group name has been specified
const [ gname, guuid ] = kpxcBanner.getDefaultGroup(result.groups[0].children, result.defaultGroup);
if (gname === '' && guuid === '') {
// Root group is used -> use the root path
if (result.defaultGroup.toLowerCase() === 'root') {
result.defaultGroup = '/';
}
// Create a new group
const newGroup = await browser.runtime.sendMessage({
action: 'create_new_group',
args: [ result.defaultGroup ]
});
if (newGroup.name && newGroup.uuid) {
const res = await browser.runtime.sendMessage({
action: 'add_credentials',
args: [ credentials.username, credentials.password, credentials.url, newGroup.name, newGroup.uuid ]
});
kpxcBanner.verifyResult(res);
} else {
kpxcUI.createNotification('error', tr('rememberErrorCreatingNewGroup'));
}
return;
}
const res = await browser.runtime.sendMessage({
action: 'add_credentials',
args: [ credentials.username, credentials.password, credentials.url, gname, guuid ]
});
kpxcBanner.verifyResult(res);
return;
}
const addChildren = function(group, parentElement, depth) {
++depth;
const padding = depth * 20;
for (const child of group.children) {
const a = createLink(child.name, child.uuid, child.children.length > 0);
a.setAttribute('id', 'child');
a.style.paddingLeft = Pixels(padding);
if (parentElement.getAttribute('id') === 'root') {
a.setAttribute('id', 'root-child');
}
$('ul#list').appendChild(a);
addChildren(child, a, depth);
}
};
const createLink = function(group, groupUuid, hasChildren) {
const a = kpxcUI.createElement('a', 'list-group-item', { 'href': '#' }, group);
a.addEventListener('click', async function(e) {
e.preventDefault();
if (!e.isTrusted) {
return;
}
const res = await browser.runtime.sendMessage({
action: 'add_credentials',
args: [ credentials.username, credentials.password, credentials.url, group, groupUuid ]
});
kpxcBanner.verifyResult(res);
});
if (hasChildren) {
a.textContent = '\u25BE ' + group;
}
return a;
};
kpxcBanner.createGroupDialog();
// Create the link list for group selection
let depth = 0;
for (const g of result.groups) {
const a = createLink(g.name, g.uuid, g.children.length > 0);
a.setAttribute('id', 'root');
$('ul#list').appendChild(a);
addChildren(g, a, depth);
}
$('.kpxc-banner-dialog').style.display = 'block';
};
kpxcBanner.updateCredentials = async function(credentials = {}) {
// Only one entry which could be updated
if (credentials.list.length === 1) {
// Use the current username if it's empty
if (!credentials.username) {
credentials.username = credentials.list[0].login;
}
const res = await browser.runtime.sendMessage({
action: 'update_credentials',
args: [ credentials.list[0].uuid, credentials.username, credentials.password, credentials.url ]
});
kpxcBanner.verifyResult(res);
} else {
await kpxcBanner.createCredentialDialog();
$('.kpxc-banner-dialog .username-new .strong').textContent = credentials.username;
$('.kpxc-banner-dialog .username-exists .strong').textContent = credentials.username;
if (credentials.usernameExists) {
$('.kpxc-banner-dialog .username-new').style.display = 'none';
$('.kpxc-banner-dialog .username-exists').style.display = 'block';
} else {
$('.kpxc-banner-dialog .username-new').style.display = 'block';
$('.kpxc-banner-dialog .username-exists').style.display = 'none';
}
for (let i = 0; i < credentials.list.length; i++) {
const a = kpxcUI.createElement('a', 'list-group-item', { 'href': '#', 'entryId': i }, `${credentials.list[i].login} (${credentials.list[i].name})`);
a.addEventListener('click', function(e) {
e.preventDefault();
if (!e.isTrusted) {
return;
}
const entryId = e.target.getAttribute('entryId');
// Use the current username if it's empty
if (!credentials.username) {
credentials.username = credentials.list[entryId].login;
}
let url = credentials.url;
url = (url.length > 50) ? url.substring(0, 50) + '...' : url;
// Check if the password has changed for the updated credentials
browser.runtime.sendMessage({
action: 'retrieve_credentials',
args: [ url, '', true ] // Sets triggerUnlock to true
}).then(async (creds) => {
if (!creds || creds.length !== credentials.list.length) {
kpxcBanner.verifyResult('error');
return;
}
const res = await browser.runtime.sendMessage({
action: 'update_credentials',
args: [ credentials.list[entryId].uuid, credentials.username, credentials.password, credentials.url ]
});
kpxcBanner.verifyResult(res);
});
});
if (credentials.usernameExists && credentials.username === credentials.list[i].login) {
a.style.fontWeight = 'bold';
}
$('ul#list').appendChild(a);
}
$('.kpxc-banner-dialog').style.display = 'block';
}
};
kpxcBanner.verifyResult = function(code) {
if (code === 'error') {
kpxcUI.createNotification('error', tr('rememberErrorCannotSaveCredentials'));
} else if (code === 'created') {
kpxcUI.createNotification('success', tr('rememberCredentialsSaved', kpxcBanner.credentials.username || tr('rememberEmptyUsername')));
} else if (code === 'updated') {
kpxcUI.createNotification('success', tr('rememberCredentialsUpdated', kpxcBanner.credentials.username || tr('rememberEmptyUsername')));
} else if (code === 'canceled') {
kpxcUI.createNotification('warning', tr('rememberCredentialsNotSaved'));
} else {
kpxcUI.createNotification('error', tr('rememberErrorDatabaseClosed'));
}
kpxcBanner.destroy();
};
// Traverse the groups and ensure all paths are found
kpxcBanner.getDefaultGroup = function(groups, defaultGroup) {
const getGroup = function(group, splitted, depth) {
++depth;
for (const g of group) {
if (g.name === splitted[depth]) {
if (splitted.length === (depth + 1)) {
return [ g.name, g.uuid ];
}
return getGroup(g.children, splitted, depth);
}
}
return [ '', '' ];
};
let depth = -1;
const splitted = defaultGroup.split('/');
return getGroup(groups, splitted, depth);
};
kpxcBanner.createCredentialDialog = async function() {
$('#kpxc-banner-btn-new').hidden = true;
$('#kpxc-banner-btn-update').hidden = true;
$('.kpxc-checkbox').disabled = true;
const connectedDatabase = await browser.runtime.sendMessage({
action: 'get_connected_database'
});
const databaseName = connectedDatabase.count > 0 ? connectedDatabase.identifier : '';
const dialog = kpxcUI.createElement('div', 'kpxc-banner-dialog');
const databaseText = kpxcUI.createElement('p');
const spanDatabaseText = kpxcUI.createElement('span', '', {}, tr('rememberSaving'));
const usernameNew = kpxcUI.createElement('p', 'username-new');
const usernameExists = kpxcUI.createElement('p', 'username-exists');
const chooseCreds = kpxcUI.createElement('p', '', {}, tr('rememberChooseCredentials'));
const list = kpxcUI.createElement('ul', 'list-group', { 'id': 'list' });
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';
databaseText.appendChild(spanDatabaseText);
const spanName = kpxcUI.createElement('span', 'strong', {}, databaseName);
databaseText.append(spanName);
usernameNew.appendChild(spanNewUsername);
usernameExists.appendChild(spanUsernameExists);
usernameNew.append(kpxcUI.createElement('span', 'strong'));
usernameExists.append(kpxcUI.createElement('span', 'strong'));
dialog.appendMultiple(databaseText, usernameNew, usernameExists, chooseCreds, list);
document.body.appendChild(dialog);
};
kpxcBanner.createGroupDialog = function() {
$('#kpxc-banner-btn-new').hidden = true;
$('#kpxc-banner-btn-update').hidden = true;
$('.kpxc-checkbox').disabled = true;
const dialog = kpxcUI.createElement('div', 'kpxc-banner-dialog');
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);
dialog.appendMultiple(chooseGroup, list);
document.body.appendChild(dialog);
};

View file

@ -134,6 +134,10 @@ kpxcDefine.isFieldSelected = function(kpxcId) {
kpxcDefine.markAllUsernameFields = function(chooser) {
kpxcDefine.eventFieldClick = function(e, elem) {
if (!e.isTrusted) {
return;
}
const field = elem || e.currentTarget;
kpxcDefine.selection.username = field.getAttribute('data-kpxc-id');
field.classList.add('kpxcDefine-fixed-username-field');
@ -147,6 +151,10 @@ kpxcDefine.markAllUsernameFields = function(chooser) {
kpxcDefine.markAllPasswordFields = function(chooser, more = false) {
kpxcDefine.eventFieldClick = function(e, elem) {
if (!e.isTrusted) {
return;
}
const field = elem || e.currentTarget;
kpxcDefine.selection.password = field.getAttribute('data-kpxc-id');
field.classList.add('kpxcDefine-fixed-password-field');
@ -164,6 +172,10 @@ kpxcDefine.markAllPasswordFields = function(chooser, more = false) {
kpxcDefine.markAllStringFields = function(chooser) {
kpxcDefine.eventFieldClick = function(e, elem) {
if (!e.isTrusted) {
return;
}
const field = elem || e.currentTarget;
const value = field.getAttribute('data-kpxc-id');
kpxcDefine.selection.fields[value] = true;
@ -189,10 +201,10 @@ kpxcDefine.markFields = function(chooser, pattern) {
if (kpxcFields.isVisible(i)) {
const field = kpxcUI.createElement('div', 'kpxcDefine-fixed-field', { 'data-kpxc-id': i.getAttribute('data-kpxc-id') });
const rect = i.getBoundingClientRect();
field.style.top = rect.top + 'px';
field.style.left = rect.left + 'px';
field.style.width = rect.width + 'px';
field.style.height = rect.height + 'px';
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);
@ -262,7 +274,11 @@ kpxcDefine.prepareStep3 = function() {
$('#kpxcDefine-btn-confirm').style.display = 'inline-block';
};
kpxcDefine.skip = function() {
kpxcDefine.skip = function(e) {
if (!e.isTrusted) {
return;
}
if (kpxcDefine.dataStep === 1) {
kpxcDefine.selection.username = null;
kpxcDefine.prepareStep2();
@ -274,20 +290,32 @@ kpxcDefine.skip = function() {
}
};
kpxcDefine.again = function() {
kpxcDefine.again = function(e) {
if (!e.isTrusted) {
return;
}
kpxcDefine.resetSelection();
kpxcDefine.prepareStep1();
kpxcDefine.markAllUsernameFields('#kpxcDefine-fields');
};
kpxcDefine.more = function() {
kpxcDefine.more = function(e) {
if (!e.isTrusted) {
return;
}
if (kpxcDefine.dataStep === 2) {
kpxcDefine.prepareStep2();
kpxcDefine.markAllPasswordFields('#kpxcDefine-fields', true);
}
};
kpxcDefine.confirm = function() {
kpxcDefine.confirm = async function(e) {
if (!e.isTrusted) {
return;
}
if (kpxcDefine.dataStep !== 3) {
return;
}
@ -317,7 +345,7 @@ kpxcDefine.confirm = function() {
fields: fieldIds
};
browser.runtime.sendMessage({
await browser.runtime.sendMessage({
action: 'save_settings',
args: [ kpxc.settings ]
});
@ -325,7 +353,11 @@ kpxcDefine.confirm = function() {
kpxcDefine.close();
};
kpxcDefine.discard = function() {
kpxcDefine.discard = async function(e) {
if (!e.isTrusted) {
return;
}
if (!$('#kpxcDefine-btn-discard')) {
return;
}
@ -333,12 +365,12 @@ kpxcDefine.discard = function() {
const location = kpxc.getDocumentLocation();
delete kpxc.settings['defined-custom-fields'][location];
browser.runtime.sendMessage({
await browser.runtime.sendMessage({
action: 'save_settings',
args: [ kpxc.settings ]
});
browser.runtime.sendMessage({
await browser.runtime.sendMessage({
action: 'load_settings'
});
@ -347,6 +379,10 @@ kpxcDefine.discard = function() {
// Handle the keyboard events
kpxcDefine.keyDown = function(e) {
if (!e.isTrusted) {
return;
}
if (e.key === 'Escape') {
kpxcDefine.close();
} else if (e.key === 'Enter') {

File diff suppressed because it is too large Load diff

View file

@ -11,41 +11,18 @@ kpxcPassword.diffX = 0;
kpxcPassword.diffY = 0;
kpxcPassword.dialog = null;
kpxcPassword.titleBar = null;
kpxcPassword.useIcons = false;
const DOMRectToArray = function(domRect) {
return [ domRect.bottom, domRect.height, domRect.left, domRect.right, domRect.top, domRect.width, domRect.x, domRect.y ];
};
/**
* Detects if the input field appears or disappears -> show/hide the icon
* - boundingClientRect with slightly (< -10) negative values -> hidden
* - intersectionRatio === 0 -> hidden
* - isIntersecting === false -> hidden
* - intersectionRatio > 0 -> shown
* - isIntersecting === true -> shown
*/
try {
kpxcPassword.observer = new IntersectionObserver((entries) => {
for (const entry of entries) {
const rect = DOMRectToArray(entry.boundingClientRect);
if ((entry.intersectionRatio === 0 && !entry.isIntersecting) || (rect.some(x => x < -10))) {
kpxcPassword.icon.style.display = 'none';
} else if (entry.intersectionRatio > 0 && entry.isIntersecting) {
kpxcPassword.icon.style.display = 'block';
// Wait for possible DOM animations
setTimeout(() => {
kpxcPassword.setIconPosition(kpxcPassword.icon, entry.target);
}, 500);
}
}
kpxcUI.updateFromIntersectionObserver(kpxcPassword, entries);
});
} catch (err) {
console.log(err);
}
kpxcPassword.init = function() {
kpxcPassword.init = function(useIcons) {
kpxcPassword.useIcons = useIcons;
if ('initPasswordGenerator' in _called) {
return;
}
@ -58,24 +35,26 @@ kpxcPassword.initField = function(field, inputs, pos) {
return;
}
// Observer the visibility
if (kpxcPassword.observer) {
kpxcPassword.observer.observe(field);
}
if (field.getAttribute('kpxc-password-generator')) {
return;
}
field.setAttribute('kpxc-password-generator', true);
kpxcPassword.createIcon(field);
if (kpxcPassword.useIcons) {
// Observer the visibility
if (kpxcPassword.observer) {
kpxcPassword.observer.observe(field);
}
kpxcPassword.createIcon(field);
}
kpxcPassword.inputField = field;
let found = false;
if (inputs) {
for (let i = pos + 1; i < inputs.length; i++) {
if (inputs[i] && inputs[i].getAttribute('type') && inputs[i].getAttribute('type').toLowerCase() === 'password') {
if (inputs[i] && inputs[i].getLowerCaseAttribute('type') === 'password') {
field.setAttribute('kpxc-pwgen-next-field-id', inputs[i].getAttribute('data-kpxc-id'));
field.setAttribute('kpxc-pwgen-next-is-password-field', (i === 0));
found = true;
@ -101,27 +80,33 @@ kpxcPassword.createIcon = function(field) {
'offset': offset,
'kpxc-pwgen-field-id': field.getAttribute('data-kpxc-id')
});
icon.style.zIndex = '9999';
icon.style.width = String(size) + 'px';
icon.style.height = String(size) + 'px';
icon.style.zIndex = '10000000';
icon.style.width = Pixels(size);
icon.style.height = Pixels(size);
icon.addEventListener('click', function(e) {
if (!e.isTrusted) {
return;
}
e.preventDefault();
kpxcPassword.showDialog(field, icon);
});
kpxcPassword.setIconPosition(icon, field);
kpxcUI.setIconPosition(icon, field);
kpxcPassword.icon = icon;
document.body.appendChild(icon);
};
kpxcPassword.setIconPosition = function(icon, field) {
const rect = field.getBoundingClientRect();
const offset = Number(icon.getAttribute('offset'));
const size = Number(icon.getAttribute('size'));
icon.style.top = String((rect.top + document.scrollingElement.scrollTop) + offset + 1) + 'px';
icon.style.left = String((rect.left + document.scrollingElement.scrollLeft) + field.offsetWidth - size - offset) + 'px';
kpxcPassword.removeIcon = function(field) {
if (field.getAttribute('kpxc-password-generator')) {
const pwgenIcons = document.querySelectorAll('.kpxc-pwgen-icon');
for (const i of pwgenIcons) {
if (i.getAttribute('kpxc-pwgen-field-id') === field.getAttribute('data-kpxc-id')) {
document.body.removeChild(i);
}
}
}
};
kpxcPassword.createDialog = function() {
@ -140,45 +125,50 @@ kpxcPassword.createDialog = function() {
const titleBar = kpxcUI.createElement('div', 'kpxc-pwgen-titlebar', {}, tr('passwordGeneratorTitle'));
const closeButton = kpxcUI.createElement('div', 'kpxc-pwgen-close', {}, '×');
closeButton.addEventListener('click', function(e) {
if (!e.isTrusted) {
return;
}
kpxcPassword.openDialog();
});
titleBar.append(closeButton);
const passwordRow = kpxcUI.createElement('div', 'kpxc-pwgen-password-row');
const input = kpxcUI.createElement('input', 'kpxc-pwgen-input', { 'placeholder': tr('passwordGeneratorPlaceholder'), 'type': 'text', 'tabindex': '-1' });
const inputLabel = kpxcUI.createElement('label', 'kpxc-pwgen-bits', {}, tr('passwordGeneratorBits', '???'));
passwordRow.appendMultiple(input, inputLabel);
const nextFillRow = kpxcUI.createElement('div', 'kpxc-pwgen-nextfill-row');
const checkbox = kpxcUI.createElement('input', 'kpxc-pwgen-checkbox', { 'id': 'kpxc-pwgen-checkbox', 'type': 'checkbox' });
const checkboxLabel = kpxcUI.createElement('label', 'kpxc-pwgen-checkbox-label', { 'for': 'kpxc-pwgen-checkbox' }, tr('passwordGeneratorLabel'));
nextFillRow.appendMultiple(checkbox, checkboxLabel);
passwordRow.appendMultiple(input);
// Buttons
const buttonsRow = kpxcUI.createElement('div', 'kpxc-pwgen-buttons');
const generateButton = kpxcUI.createElement('button', 'kpxc-button kpxc-white-button', { 'id': 'kpxc-pwgen-btn-generate' }, tr('passwordGeneratorGenerate'));
const copyButton = kpxcUI.createElement('button', 'kpxc-button', { 'id': 'kpxc-pwgen-btn-copy' }, tr('passwordGeneratorCopy'));
const fillButton = kpxcUI.createElement('button', 'kpxc-button', { 'id': 'kpxc-pwgen-btn-fill' }, tr('passwordGeneratorFillAndCopy'));
const generateButton = kpxcUI.createElement('button', 'kpxc-button kpxc-orange-button', { 'id': 'kpxc-pwgen-btn-generate' }, tr('passwordGeneratorGenerate'));
const copyButton = kpxcUI.createElement('button', 'kpxc-button kpxc-orange-button', { 'id': 'kpxc-pwgen-btn-copy' }, tr('passwordGeneratorCopy'));
const fillButton = kpxcUI.createElement('button', 'kpxc-button kpxc-green-button', { 'id': 'kpxc-pwgen-btn-fill' }, tr('passwordGeneratorFill'));
generateButton.addEventListener('click', function(e) {
kpxcPassword.generate(e);
});
fillButton.addEventListener('click', function(e) {
kpxcPassword.fill(e);
kpxcPassword.openDialog();
});
copyButton.addEventListener('click', function(e) {
kpxcPassword.copy(e);
});
fillButton.addEventListener('click', function(e) {
kpxcPassword.fill(e);
});
buttonsRow.appendMultiple(generateButton, copyButton, fillButton);
dialog.appendMultiple(titleBar, passwordRow, nextFillRow, buttonsRow);
dialog.appendMultiple(titleBar, passwordRow, buttonsRow);
wrapper.append(dialog);
const icon = $('.kpxc-pwgen-icon');
dialog.style.top = String(icon.offsetTop + icon.offsetHeight) + 'px';
dialog.style.left = icon.style.left;
if (icon) {
dialog.style.top = Pixels(icon.offsetTop + icon.offsetHeight);
dialog.style.left = icon.style.left;
} else {
const rect = document.activeElement.getBoundingClientRect();
dialog.style.top = Pixels(rect.top + rect.height);
dialog.style.left = Pixels(rect.left);
}
document.body.append(wrapper);
@ -209,7 +199,7 @@ kpxcPassword.openDialog = function() {
};
kpxcPassword.trigger = function() {
kpxcPassword.showDialog(kpxcPassword.inputField, kpxcPassword.icon);
kpxcPassword.showDialog(kpxcPassword.inputField || document.activeElement, kpxcPassword.icon);
};
kpxcPassword.showDialog = function(field, icon) {
@ -224,8 +214,14 @@ kpxcPassword.showDialog = function(field, icon) {
// Adjust the dialog location
if (kpxcPassword.dialog) {
kpxcPassword.dialog.style.top = String(icon.offsetTop + icon.offsetHeight) + 'px';
kpxcPassword.dialog.style.left = icon.style.left;
if (icon) {
kpxcPassword.dialog.style.top = Pixels(icon.offsetTop + icon.offsetHeight);
kpxcPassword.dialog.style.left = icon.style.left;
} else {
const rect = document.activeElement.getBoundingClientRect();
kpxcPassword.dialog.style.top = Pixels(rect.top + rect.height);
kpxcPassword.dialog.style.left = Pixels(rect.left);
}
kpxcPassword.dialog.setAttribute('kpxc-pwgen-field-id', field.getAttribute('data-kpxc-id'));
kpxcPassword.dialog.setAttribute('kpxc-pwgen-next-field-id', field.getAttribute('kpxc-pwgen-next-field-id'));
@ -244,27 +240,34 @@ kpxcPassword.showDialog = function(field, icon) {
}
};
kpxcPassword.generate = function(e) {
kpxcPassword.generate = async function(e) {
// This function can be also called from non-events
if (e) {
if (!e.isTrusted) {
return;
}
e.preventDefault();
}
browser.runtime.sendMessage({
callbackGeneratedPassword(await browser.runtime.sendMessage({
action: 'generate_password'
}).then(kpxcPassword.callbackGeneratedPassword).catch((err) => {
console.log(err);
});
}));
};
kpxcPassword.copy = function(e) {
e.preventDefault();
if (kpxcPassword.copyPasswordToClipboard()) {
kpxcPassword.greenButton('#kpxc-pwgen-btn-copy');
kpxcPassword.whiteButton('#kpxc-pwgen-btn-fill');
if (!e.isTrusted) {
return;
}
e.preventDefault();
kpxcPassword.copyPasswordToClipboard();
};
kpxcPassword.fill = function(e) {
if (!e.isTrusted) {
return;
}
e.preventDefault();
// Use the active input field
@ -285,19 +288,10 @@ kpxcPassword.fill = function(e) {
}
field.value = password.value;
if ($('.kpxc-pwgen-checkbox').checked) {
if (field.getAttribute('kpxc-pwgen-next-field-id')) {
const nextFieldId = field.getAttribute('kpxc-pwgen-next-field-id');
const nextField = $('input[data-kpxc-id=\'' + nextFieldId + '\']');
if (nextField) {
nextField.value = password.value;
}
}
}
if (kpxcPassword.copyPasswordToClipboard()) {
kpxcPassword.greenButton('#kpxc-pwgen-btn-fill');
kpxcPassword.whiteButton('#kpxc-pwgen-btn-copy');
const nextFieldId = field.getAttribute('kpxc-pwgen-next-field-id');
const nextField = $('input[data-kpxc-id=\'' + nextFieldId + '\']');
if (nextField) {
nextField.value = password.value;
}
}
};
@ -312,33 +306,20 @@ kpxcPassword.copyPasswordToClipboard = function() {
return false;
};
kpxcPassword.callbackGeneratedPassword = function(entries) {
const callbackGeneratedPassword = function(entries) {
if (entries && entries.length >= 1) {
const errorMessage = $('#kpxc-pwgen-error');
if (errorMessage) {
kpxcPassword.enableButtons();
$('.kpxc-pwgen-checkbox').parentElement.style.display = 'block';
$('.kpxc-pwgen-bits').style.display = 'block';
enableButtons();
const input = $('.kpxc-pwgen-input');
input.style.display = 'block';
errorMessage.remove();
}
kpxcPassword.whiteButton('#kpxc-pwgen-btn-fill');
kpxcPassword.whiteButton('#kpxc-pwgen-btn-copy');
$('.kpxc-pwgen-input').value = entries[0].password;
if (entries[0].entropy) {
$('.kpxc-pwgen-bits').textContent = tr('passwordGeneratorBits', (Number.isNaN(entries[0].entropy) ? '???' : String(entries[0].entropy.toFixed(2))));
} else {
$('.kpxc-pwgen-bits').textContent = tr('passwordGeneratorBits', (isNaN(entries[0].login) ? '???' : entries[0].login));
}
} else {
if (document.querySelectorAll('div#kpxc-pwgen-error').length === 0) {
$('.kpxc-pwgen-checkbox').parentElement.style.display = 'none';
$('.kpxc-pwgen-bits').style.display = 'none';
const input = $('.kpxc-pwgen-input');
input.style.display = 'none';
@ -347,34 +328,18 @@ kpxcPassword.callbackGeneratedPassword = function(entries) {
errorMessage.style.whiteSpace = 'pre';
input.parentElement.append(errorMessage);
kpxcPassword.disableButtons();
disableButtons();
}
}
};
kpxcPassword.onRequestPassword = function() {
browser.runtime.sendMessage({
action: 'generate_password'
}).then(kpxcPassword.callbackGeneratedPassword);
};
kpxcPassword.greenButton = function(button) {
$(button).classList.remove('kpxc-white-button');
$(button).classList.add('kpxc-green-button');
};
kpxcPassword.whiteButton = function(button) {
$(button).classList.remove('kpxc-green-button');
$(button).classList.add('kpxc-white-button');
};
kpxcPassword.enableButtons = function() {
const enableButtons = function() {
$('#kpxc-pwgen-btn-generate').textContent = tr('passwordGeneratorGenerate');
$('#kpxc-pwgen-btn-copy').style.display = 'inline-block';
$('#kpxc-pwgen-btn-fill').style.display = 'inline-block';
};
kpxcPassword.disableButtons = function() {
const disableButtons = function() {
$('#kpxc-pwgen-btn-generate').textContent = tr('passwordGeneratorTryAgain');
$('#kpxc-pwgen-btn-copy').style.display = 'none';
$('#kpxc-pwgen-btn-fill').style.display = 'none';
@ -382,28 +347,10 @@ kpxcPassword.disableButtons = function() {
// Handle icon position on window resize
window.addEventListener('resize', function(e) {
if (kpxcPassword.inputField && kpxcPassword.icon) {
kpxcPassword.setIconPosition(kpxcPassword.icon, kpxcPassword.inputField);
}
kpxcUI.updateIconPosition(kpxcPassword);
});
// Handle icon position on scroll
window.addEventListener('scroll', function(e) {
if (kpxcPassword.inputField && kpxcPassword.icon) {
kpxcPassword.setIconPosition(kpxcPassword.icon, kpxcPassword.inputField);
}
});
// Closes the dialog when clicked outside of it)
document.addEventListener('click', function(e) {
if (kpxcPassword.dialog && kpxcPassword.dialog.style.display === 'block') {
const dialogEndX = kpxcPassword.dialog.offsetLeft + kpxcPassword.dialog.offsetWidth;
const dialogEndY = kpxcPassword.dialog.offsetTop + kpxcPassword.dialog.offsetHeight;
if ((e.clientX < kpxcPassword.dialog.offsetLeft || e.clientX > dialogEndX) ||
(e.clientY < kpxcPassword.dialog.offsetTop || e.clientY > dialogEndY) &&
!e.target.classList.contains('kpxc-pwgen-icon')) {
kpxcPassword.openDialog();
}
}
kpxcUI.updateIconPosition(kpxcPassword);
});

View file

@ -0,0 +1,35 @@
'use strict';
// List of sites that need special handling. 'rep' is the replacement URL.
const siteList = [
{
url: 'accounts.google.com',
rep: 'https://accounts.google.com/*'
}
];
var kpxcSites = {};
// Returns a predefined URL for certain sites to ensure compatibility with Site Preferences
kpxcSites.definedURL = function(url) {
for (const site of siteList) {
if (url.includes(site.url)) {
return site.rep;
}
}
return url;
};
// UNUSED: Adds all common sites with multi-page login to Site Preferences
kpxcSites.addAllCommonSites = function() {
kpxc.initializeSitePreferences();
for (const site of siteList) {
kpxc.settings['sitePreferences'].push({
url: site.rep,
ignore: IGNORE_NOTHING,
usernameOnly: true
});
}
};

View file

@ -5,6 +5,11 @@ var $ = function(elem) {
return document.querySelector(elem);
};
// Returns a string with 'px' for CSS styles
var Pixels = function(value) {
return String(value) + 'px';
};
var kpxcUI = {};
// Wrapper for creating elements
@ -31,6 +36,83 @@ kpxcUI.createElement = function(type, classes, attributes, textContent) {
return element;
};
kpxcUI.updateIconPosition = function(iconClass) {
if (iconClass.inputField && iconClass.icon) {
kpxcUI.setIconPosition(iconClass.icon, iconClass.inputField);
}
};
kpxcUI.setIconPosition = function(icon, field) {
const rect = field.getBoundingClientRect();
const offset = Number(icon.getAttribute('offset'));
const size = Number(icon.getAttribute('size'));
icon.style.top = Pixels((rect.top + document.scrollingElement.scrollTop) + offset + 1);
icon.style.left = Pixels((rect.left + document.scrollingElement.scrollLeft) + field.offsetWidth - size - offset);
};
/**
* Detects if the input field appears or disappears -> show/hide the icon
* - boundingClientRect with slightly (< -10) negative values -> hidden
* - intersectionRatio === 0 -> hidden
* - isIntersecting === false -> hidden
* - intersectionRatio > 0 -> shown
* - isIntersecting === true -> shown
*/
kpxcUI.updateFromIntersectionObserver = function(iconClass, entries) {
for (const entry of entries) {
const rect = DOMRectToArray(entry.boundingClientRect);
if ((entry.intersectionRatio === 0 && !entry.isIntersecting) || (rect.some(x => x < -10))) {
iconClass.icon.style.display = 'none';
} else if (entry.intersectionRatio > 0 && entry.isIntersecting) {
iconClass.icon.style.display = 'block';
// Wait for possible DOM animations
setTimeout(() => {
kpxcUI.setIconPosition(iconClass.icon, entry.target);
}, 500);
}
}
};
/**
* Creates a self-disappearing notification banner to DOM
* @param {string} type Notification type: (success, info, warning, error)
* @param {string} message The message shown
*/
kpxcUI.createNotification = function(type, message) {
if (!kpxc.settings.showNotifications || !type || !message) {
return;
}
const banner = kpxcUI.createElement('div', 'kpxc-notification kpxc-notification-' + type, {});
type = type.charAt(0).toUpperCase() + type.slice(1) + '!';
const className = (isFirefox() ? 'kpxc-banner-icon-moz' : 'kpxc-banner-icon');
const icon = kpxcUI.createElement('span', className, { 'alt': 'logo' });
const label = kpxcUI.createElement('span', 'kpxc-label', {}, type);
const msg = kpxcUI.createElement('span', '', {}, message);
banner.addEventListener('click', function() {
document.body.removeChild(banner);
});
banner.appendMultiple(icon, label, msg);
document.body.appendChild(banner);
// Destroy the banner after five seconds
setTimeout(() => {
if ($('.kpxc-notification')) {
document.body.removeChild(banner);
}
}, 5000);
};
const DOMRectToArray = function(domRect) {
return [ domRect.bottom, domRect.height, domRect.left, domRect.right, domRect.top, domRect.width, domRect.x, domRect.y ];
};
// Enables dragging
document.addEventListener('mousemove', function(e) {
if (kpxcPassword.selected === kpxcPassword.titleBar) {
@ -38,8 +120,8 @@ document.addEventListener('mousemove', function(e) {
const yPos = e.clientY - kpxcPassword.diffY;
if (kpxcPassword.selected !== null) {
kpxcPassword.dialog.style.left = xPos + 'px';
kpxcPassword.dialog.style.top = yPos + 'px';
kpxcPassword.dialog.style.left = Pixels(xPos);
kpxcPassword.dialog.style.top = Pixels(yPos);
}
}
@ -48,8 +130,8 @@ document.addEventListener('mousemove', function(e) {
const yPos = e.clientY - kpxcDefine.diffY;
if (kpxcDefine.selected !== null) {
kpxcDefine.dialog.style.left = xPos + 'px';
kpxcDefine.dialog.style.top = yPos + 'px';
kpxcDefine.dialog.style.left = Pixels(xPos);
kpxcDefine.dialog.style.top = Pixels(yPos);
}
}
});
@ -64,3 +146,7 @@ HTMLDivElement.prototype.appendMultiple = function(...args) {
this.append(a);
}
};
Element.prototype.getLowerCaseAttribute = function(attr) {
return this.getAttribute(attr) ? this.getAttribute(attr).toLowerCase() : undefined;
};

View file

@ -0,0 +1,137 @@
'use strict';
var kpxcUsernameField = {};
kpxcUsernameField.icon = null;
kpxcUsernameField.inputField = null;
try {
kpxcUsernameField.observer = new IntersectionObserver((entries) => {
kpxcUI.updateFromIntersectionObserver(kpxcUsernameField, entries);
});
} catch (err) {
console.log(err);
}
kpxcUsernameField.initField = function(field, databaseClosed = true) {
if (!field || field.getAttribute('kpxc-username-field') === 'true') {
return;
}
field.setAttribute('kpxc-username-field', 'true');
// Observer the visibility
if (kpxcUsernameField.observer) {
kpxcUsernameField.observer.observe(field);
}
createIcon(field, databaseClosed);
kpxcUsernameField.inputField = field;
};
kpxcUsernameField.switchIcon = function(locked) {
const icons = $('.kpxc-username-icon');
if (!icons || icons.length === 0) {
return;
}
const icon = icons;
if (locked) {
icon.classList.remove(getIconClassName());
icon.classList.add(getIconClassName(true));
icon.title = tr('usernameLockedFieldText');
} else {
icon.classList.remove(getIconClassName(true));
icon.classList.add(getIconClassName());
icon.title = tr('usernameFieldText');
}
};
const createIcon = function(target, databaseClosed) {
// Remove any existing password generator icons from the input field
if (target.getAttribute('kpxc-password-generator')) {
kpxcPassword.removeIcon(target);
}
const field = target;
const className = getIconClassName(databaseClosed);
const size = (field.offsetHeight > 28) ? 24 : 16;
let offset = Math.floor((field.offsetHeight - size) / 3);
offset = (offset < 0) ? 0 : offset;
const icon = kpxcUI.createElement('div', 'kpxc kpxc-username-icon ' + className,
{
'title': databaseClosed ? tr('usernameLockedFieldText') : tr('usernameFieldText'),
'alt': tr('usernameFieldIcon'),
'size': size,
'offset': offset,
'kpxc-pwgen-field-id': field.getAttribute('data-kpxc-id')
});
icon.style.zIndex = '10000000';
icon.style.width = Pixels(size);
icon.style.height = Pixels(size);
icon.addEventListener('click', async function(e) {
if (!e.isTrusted) {
return;
}
e.preventDefault();
if (!kpxcFields.isVisible(field)) {
document.body.removeChild(icon);
field.removeAttribute('kpxc-username-field');
return;
}
const connected = await browser.runtime.sendMessage({ action: 'is_connected' });
if (!connected) {
kpxcUI.createNotification('error', tr('errorNotConnected'));
return;
}
const databaseHash = await browser.runtime.sendMessage({ action: 'check_database_hash' });
if (databaseHash === '') {
// Triggers database unlock
_called.manualFillRequested = ManualFill.BOTH;
await browser.runtime.sendMessage({
action: 'get_database_hash',
args: [ false, true ] // Set triggerUnlock to true
});
}
if (icon.className.includes('unlock')) {
fillCredentials();
}
});
kpxcUI.setIconPosition(icon, field);
kpxcUsernameField.icon = icon;
document.body.appendChild(icon);
};
const getIconClassName = function(locked = false) {
if (locked) {
return (isFirefox() ? 'lock-moz' : 'lock');
}
return (isFirefox() ? 'unlock-moz' : 'unlock');
};
const fillCredentials = function() {
const fieldId = kpxcUsernameField.inputField.getAttribute('data-kpxc-id');
kpxcFields.prepareId(fieldId);
const givenType = kpxcUsernameField.inputField.type === 'password' ? 'password' : 'username';
const combination = kpxcFields.getCombination(givenType, fieldId);
kpxc.fillInCredentials(combination, givenType === 'password', false);
};
// Handle icon position on window resize
window.addEventListener('resize', function(e) {
kpxcUI.updateIconPosition(kpxcUsernameField);
});
// Handle icon position on scroll
window.addEventListener('scroll', function(e) {
kpxcUI.updateIconPosition(kpxcUsernameField);
});

View file

@ -0,0 +1,138 @@
div.kpxc-banner {
animation-duration: 0.2s;
animation-name: slidein;
background-color: #ffffff;
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: #000000 !important;
font-family: sans-serif !important;
font-size: 1em !important;
height: auto !important;
left: 0px !important;
line-height: 1 !important;
margin: 0 auto !important;
min-height: 2em !important;
max-height: 4em !important;
overflow-x: hidden !important;
padding: 4px !important;
position: fixed !important;
top: 0px !important;
width: 100% !important;
display: flex !important;
justify-content: space-between !important;
}
@keyframes slidein {
from {
max-height: 0em;
}
to {
max-height: 4em;
}
}
div.kpxc-banner .banner-info > span {
margin: 4px;
}
div.kpxc-banner div.banner-info, div.banner-buttons {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 8px;
}
div.kpxc-banner .small {
font-size: 80%;
color: #787878;
}
div.kpxc-banner .kpxc-banner-icon {
width: 24px;
height: 24px;
overflow: hidden;
background: url('chrome-extension://__MSG_@@extension_id__/icons/keepassxc.svg') right no-repeat;
background-size: contain;
}
div.kpxc-banner .kpxc-banner-icon-moz {
width: 24px;
height: 24px;
overflow: hidden;
background: url('moz-extension://__MSG_@@extension_id__/icons/keepassxc.svg') right no-repeat;
background-size: contain;
}
.separator {
border-left: 1px solid #ccc;
height: 100% !important;
margin: 10px !important;
}
div.kpxc-banner .kpxc-checkbox {
margin: 2px !important;
}
div.kpxc-banner label {
font-family: 'Lato', sans-serif !important;
font-size: 12px !important;
font-weight: normal !important;
margin: 4px !important;
}
div.kpxc-banner-dialog {
display: block !important;
position: fixed !important;
margin: 0 auto;
width: 680px;
max-height: 600px;
max-width: 460px;
background-color: white;
border: 1px solid #ccc;
border-radius: 0 0 4px 4px;
box-shadow: 0 4px 6px 0 hsla(0, 0%, 0%, 0.2);
padding: 8px;
z-index: 10000000;
}
div.kpxc-banner-dialog > ul {
list-style-type: disc !important;
margin-bottom: 0 !important;
margin-top: 0 !important;
padding-left: 0 !important;
}
div.kpxc-banner-dialog > p > span.strong {
font-weight: bold !important;
}
div.kpxc-banner-dialog .list-group {
font-size: .9em !important;
}
div.kpxc-banner-dialog .list-group-item {
border: 1px solid #ddd !important;
color: #000 !important;
display: block !important;
padding: 5px 10px;
position: relative !important;
text-decoration: none !important;
}
div.kpxc-banner-dialog .list-group-item:first-child {
border-top-left-radius: 4px !important;
border-top-right-radius: 4px !important;
}
div.kpxc-banner-dialog .list-group-item:last-child {
margin-bottom: 0 !important;
border-bottom-right-radius: 4px !important;
border-bottom-left-radius: 4px !important;
}
div.kpxc-banner-dialog .list-group-item:hover {
cursor: pointer !important;;
background-color: #eee !important;
}

View file

@ -1,48 +1,55 @@
.kpxc-button {
background-color: #fff;
background-image: none !important;
border: 1px solid #ccc !important;
border-radius: .25em !important;
font-size: 12px !important;
font-family: 'Lato', sans-serif !important;
height: 28px !important;
margin: .2em !important;
background-color: #fff;
border: 1px solid #888 !important;
border-radius: 4px !important;
padding: 1px 7px 2px !important;
}
.kpxc-white-button {
color: #333 !important;
background-color: #fff !important;
border-color: #ccc !important;
background-color: #f8f9fa !important;
border-color: #f8f9fa !important;
color: #212529 !important;
}
.kpxc-red-button {
background-color: #dc3545 !important;
border-color: #dc3545 !important;
color: #fff !important;
background-color: #d9534f !important;
border-color: #d43f3a !important;
}
.kpxc-orange-button {
color: #fff !important;
background-color: #f0ad4e !important;
border-color: #eea236 !important;
background-color: #ffc107 !important;
border-color: #ffc107 !important;
color: #212529 !important;
}
.kpxc-blue-button {
background-color: #007bff !important;
border-color: #007bff !important;
color: #fff !important;
background-color: #337ab7 !important;
border-color: #2e6da4 !important;
}
.kpxc-green-button {
background-color: #28a745 !important;
border-color: #28a745 !important;
color: #fff !important;
background-color: #5cb85c !important;
border-color: #4cae4c !important;
}
.kpxc-button:hover {
background-color: #eee;
cursor: pointer;
cursor: pointer !important;
filter: brightness(92%) !important;
transition: all .15s;
}
.kpxc-button:active {
background-color: #ddd;
}
.kpxc-button:disabled {
border-color: #ccc !important;
}
.kpxc-button:disabled:hover {
background-color: #fff !important;
}

View file

@ -0,0 +1,69 @@
.kpxc-notification {
border: 1px solid transparent;
border-radius: 4px;
box-shadow: 0 4px 6px 0 hsla(0, 0%, 0%, 0.2);
cursor: pointer;
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
font-size: 14px;
margin: 0px auto;
margin-bottom: 20px;
padding: 15px;
padding-right: 35px;
position: fixed;
right: 0px;
top: 0px;
width: 520px;
z-index: 9999;
}
.kpxc-notification span {
padding: 2px;
}
.kpxc-notification .kpxc-banner-icon {
width: 24px;
height: 24px;
padding: 10px;
margin-right: 4px;
overflow: hidden;
background: url('chrome-extension://__MSG_@@extension_id__/icons/keepassxc.svg') right no-repeat;
background-size: contain;
}
.kpxc-notification .kpxc-banner-icon-moz {
width: 24px;
height: 24px;
padding: 10px;
margin-right: 4px;
overflow: hidden;
background: url('moz-extension://__MSG_@@extension_id__/icons/keepassxc.svg') right no-repeat;
background-size: contain;
}
.kpxc-notification .kpxc-label {
font-weight: bold;
}
.kpxc-notification-success {
color: #155724;
background-color: #d4edda;
border-color: #c3e6cb;
}
.kpxc-notification-warning {
color: #856404;
background-color: #fff3cd;
border-color: #ffeeba;
}
.kpxc-notification-info {
color: #004085;
background-color: #cce5ff;
border-color: #b8daff;
}
.kpxc-notification-error {
color: #721c24;
background-color: #f8d7da;
border-color: #f5c6cb;
}

View file

@ -1,67 +1,41 @@
.kpxc-pwgen-dialog {
font-family: 'Lato', sans-serif !important;
width: 350px;
min-height: 80px;
background-color: white;
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: 0 4px 6px 0 hsla(0, 0%, 0%, 0.2);
z-index: 2147483645;
font-family: 'Lato', sans-serif !important;
min-height: 80px;
padding: .1em .2em .1em .2em;
position: absolute !important;
width: 350px;
z-index: 10000000 !important;
}
.kpxc-pwgen-titlebar {
font-size: 14px !important;
background-color: #6cac4d;
color: #fff;
padding: .4em .2em .4em .8em;
margin: .2em 0 .2em 0;
position: relative;
cursor: move;
font-size: 12px !important;
margin: .2em 0 .2em 0;
padding: .4em .2em .4em .8em;
position: relative;
}
.kpxc-pwgen-password-row {
color: #fff;
padding: .4em 1em;
margin: .2em 0 .2em 0;
display: flex;
}
.kpxc-pwgen-nextfill-row {
font-size: 12px !important;
color: #000;
padding: .4em 1em;
margin: .2em 0 .2em 0;
padding: .4em 1em;
}
.kpxc-pwgen-buttons {
align-items: center;
border-top: 1px solid #ccc;
color: #fff;
display: flex;
justify-content: left;
padding: .4em 1em;
margin: .2em 0 .2em 0;
border-top: 1px solid #ccc;
display: flex;
align-items: center;
justify-content: center;
}
.kpxc-pwgen-bits {
font-size: 12px !important;
font-style: normal !important;
font-weight: normal !important;
text-align: center;
color: #000;
background-color: #eee;
border: 1px solid #ccc;
padding: .4em;
border-radius: 4px;
width: 30%;
min-width: 60px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-collapse: separate;
vertical-align: middle;
white-space: nowrap;
}
.kpxc-pwgen-close {
@ -80,9 +54,14 @@
}
.kpxc-pwgen-input {
width: 100%;
background: none !important;
border-radius: 4px !important;
color: #000 !important;
font-size: 12px !important;
font-weight: normal !important;
height: auto !important;
padding: 4px !important;
width: 100% !important;
}
.kpxc-pwgen-checkbox-label {
@ -100,8 +79,8 @@
}
.kpxc-pwgen-icon {
position: absolute;
cursor: pointer;
position: absolute;
}
.kpxc-pwgen-icon.key {

View file

@ -0,0 +1,24 @@
.kpxc-username-icon {
position: absolute;
cursor: pointer;
}
.kpxc-username-icon.lock {
background: url('chrome-extension://__MSG_@@extension_id__/icons/locked.svg') right no-repeat;
background-size: contain;
}
.kpxc-username-icon.lock-moz {
background: url('moz-extension://__MSG_@@extension_id__/icons/locked.svg') right no-repeat;
background-size: contain;
}
.kpxc-username-icon.unlock {
background: url('chrome-extension://__MSG_@@extension_id__/icons/keepassxc.svg') right no-repeat;
background-size: contain;
}
.kpxc-username-icon.unlock-moz {
background: url('moz-extension://__MSG_@@extension_id__/icons/keepassxc.svg') right no-repeat;
background-size: contain;
}

View file

@ -21,6 +21,13 @@ var showNotification = function(message) {
});
};
var AssociatedAction = {
NOT_ASSOCIATED: 0,
ASSOCIATED: 1,
NEW_ASSOCIATION: 2,
CANCELED: 3
};
/**
* Transforms a valid match pattern into a regular expression
* which matches all URLs included by that pattern.
@ -71,6 +78,8 @@ var matchPatternToRegExp = function(pattern) {
}
if (path) {
path = trimURL(path);
if (path === '*') {
regex += '(/.*)?';
} else if (path.charAt(0) !== '/') {
@ -97,3 +106,8 @@ var slashNeededForUrl = function(pattern) {
function tr(key, params) {
return browser.i18n.getMessage(key, params);
}
// Removes everything after '?' from URL
var trimURL = function(url) {
return url.indexOf('?') !== -1 ? url.split('?')[0] : url;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,208 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="100"
height="100"
viewBox="0 0 99.999997 100"
id="svg2"
version="1.1"
sodipodi:docname="locked.svg"
inkscape:version="0.92.2 5c3e80d, 2017-08-06">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1231"
inkscape:window-height="877"
id="namedview29"
showgrid="false"
inkscape:zoom="2.36"
inkscape:cx="137.9347"
inkscape:cy="-19.107205"
inkscape:window-x="867"
inkscape:window-y="158"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" />
<defs
id="defs4">
<linearGradient
id="linearGradient4257">
<stop
style="stop-color:#808080;stop-opacity:1"
offset="0"
id="stop4259" />
<stop
style="stop-color:#4d4d4d;stop-opacity:1"
offset="1"
id="stop4261" />
</linearGradient>
<linearGradient
id="linearGradient4316-3-6">
<stop
style="stop-color:#226e23;stop-opacity:1"
offset="0"
id="stop4173" />
<stop
style="stop-color:#63ab3a;stop-opacity:1"
offset="1"
id="stop4175" />
</linearGradient>
<linearGradient
id="linearGradient4316-3">
<stop
id="stop4167"
offset="0"
style="stop-color:#226e23;stop-opacity:1" />
<stop
id="stop4169"
offset="1"
style="stop-color:#63ab3a;stop-opacity:1" />
</linearGradient>
<linearGradient
id="linearGradient4316">
<stop
style="stop-color:#226e23;stop-opacity:1"
offset="0"
id="stop4318" />
<stop
style="stop-color:#63ab3a;stop-opacity:1"
offset="1"
id="stop4320" />
</linearGradient>
<linearGradient
id="linearGradient4153"
osb:paint="solid">
<stop
style="stop-color:#b3b3b3;stop-opacity:1;"
offset="0"
id="stop4155" />
</linearGradient>
<linearGradient
xlink:href="#linearGradient4316"
id="linearGradient4324"
x1="50.757614"
y1="964.83679"
x2="50.757614"
y2="1042.2632"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0561225,0,0,1.0561225,-2.8061215,-1008.6172)" />
<radialGradient
gradientUnits="userSpaceOnUse"
r="43.571938"
fy="41.189114"
fx="-82.91127"
cy="41.189114"
cx="-82.91127"
id="radialGradient5106"
xlink:href="#linearGradient4316" />
<linearGradient
xlink:href="#linearGradient4316"
id="linearGradient4324-3"
x1="50.757614"
y1="964.83679"
x2="50.757614"
y2="1042.2632"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0561225,0,0,1.0561225,118.96071,-1109.1994)" />
<linearGradient
xlink:href="#linearGradient4316"
id="linearGradient4324-3-6"
x1="50.757614"
y1="964.83679"
x2="50.757614"
y2="1042.2632"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0561225,0,0,1.0561225,-2.8061235,-1008.6171)" />
<linearGradient
gradientUnits="userSpaceOnUse"
y2="86.356995"
x2="53.238865"
y1="12.753036"
x1="53.238865"
id="linearGradient5199"
xlink:href="#linearGradient4316-3-6" />
<linearGradient
xlink:href="#linearGradient4257"
id="linearGradient4263"
x1="50.09866"
y1="86.831215"
x2="49.526104"
y2="8.6772995"
gradientUnits="userSpaceOnUse" />
</defs>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<circle
cy="50"
cx="50"
id="path5201"
style="opacity:0.87099998;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:3.13186812;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.78431373"
r="45.934067" />
<circle
r="42.32143"
cy="50.000023"
cx="50"
id="path3336"
style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="path4264"
d="M 50,6.3242189 A 43.676472,43.676472 0 0 0 6.3242189,50 43.676472,43.676472 0 0 0 50,93.675781 43.676472,43.676472 0 0 0 93.675781,50 43.676472,43.676472 0 0 0 50,6.3242189 Z m 0,6.7109371 c 2.26423,0.0083 4.523397,0.224904 6.748047,0.646485 0.10716,0.48368 0.167969,0.98402 0.167969,1.5 0,3.82265 -3.097269,6.918015 -6.917969,6.916015 -3.82069,0.0022 -6.916016,-3.091382 -6.916016,-6.914062 0,-0.5181 0.06204,-1.02034 0.169922,-1.50586 C 45.476743,13.257569 47.7359,13.042035 50,13.035156 Z m -18.994141,5.298828 c -0.24053,1.21916 -0.371333,2.474142 -0.371093,3.763672 0,7.71966 4.528393,14.367959 11.064453,17.480469 l 0,35.089844 8.298828,8.298828 8.300781,-8.300781 -0.476562,-7.34961 3.914062,-3.912109 -3.914062,-3.912109 5.871093,-5.867188 -5.871093,-5.871094 0.476562,-8.177734 c 6.53802,-3.11057 11.066406,-9.758856 11.066406,-17.478516 0,-1.28696 -0.133397,-2.540982 -0.373046,-3.757812 C 80.124647,25.007387 86.946634,37.023584 86.964844,50 86.964304,70.414588 70.41459,86.964292 50,86.964844 29.58541,86.964292 13.035701,70.414589 13.035156,50 13.050706,37.021755 19.872279,25.003208 31.005859,18.333984 Z m 13.458985,25.898438 2.767578,0 0,27.666016 -2.765625,0 -0.002,-27.664063 0,-0.002 z"
style="opacity:1;fill:#f9f9f9;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="path3336-3-2-7"
d="m 56.747561,13.681815 c 0.107159,0.48368 0.16838,0.98465 0.16838,1.50063 0,3.822651 -3.097351,6.917921 -6.91805,6.915921 -3.82069,0.0022 -6.91594,-3.09115 -6.91594,-6.913831 0,-0.5181 0.0626,-1.02142 0.17048,-1.50694 C 45.477221,13.25743 47.7359,13.042554 50,13.035675 c 2.26423,0.0083 4.52291,0.224559 6.74756,0.64614 z m -26.11261,8.416551 c 0,7.71966 4.528179,14.36677 11.064239,17.47928 l 0,35.091179 8.2987,8.2987 8.30082,-8.3008 -0.47566,-7.3495 3.91258,-3.9126 -3.91258,-3.9126 5.86992,-5.867799 -5.86992,-5.8699 0.47565,-8.17878 c 6.53802,-3.11057 11.06635,-9.75752 11.06635,-17.47718 0,-1.28696 -0.13288,-2.54209 -0.37253,-3.75892 11.132459,6.667543 17.953549,18.684164 17.97176,31.66058 C 86.96374,70.414615 70.414591,86.963775 50,86.964325 29.58541,86.963775 13.03626,70.414615 13.03572,50.000026 c 0.0155,-12.978245 6.83607,-24.997676 17.96965,-31.6669 -0.24053,1.21916 -0.37066,2.47571 -0.37042,3.76524 z m 13.829769,22.13472 2.76764,0 0,27.665839 -2.76554,0 -0.002,-27.663749 0,-0.002 z"
style="opacity:0.94;fill:url(#linearGradient4263);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.80000001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<rect
style="fill:#fe0101;fill-opacity:1;stroke:none;stroke-width:10.35855961;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.78431373;paint-order:fill markers stroke"
id="rect838"
width="30"
height="30"
x="69"
y="69.152542" />
<rect
y="65.152542"
x="65.099998"
height="34"
width="34"
id="rect840"
style="fill:#fe0101;fill-opacity:0.28240739;stroke:none;stroke-width:11.73970127;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.78431373;paint-order:fill markers stroke" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.78431373;paint-order:fill markers stroke"
id="rect865"
width="6.0801892"
height="29.562309"
x="115.6887"
y="-14.881159"
transform="matrix(0.70561918,0.70859126,-0.70561918,0.70859126,0,0)" />
<rect
transform="matrix(-0.70859126,0.70561918,-0.70859126,-0.70561918,0,0)"
y="-133.51057"
x="-2.6410608"
height="29.562309"
width="6.0801892"
id="rect867"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.78431373;paint-order:fill markers stroke" />
</svg>

After

Width:  |  Height:  |  Size: 9.1 KiB

View file

@ -54,18 +54,24 @@
],
"js": [
"browser-polyfill.min.js",
"content/banner.js",
"content/autocomplete.js",
"content/define.js",
"content/keepassxc-browser.js",
"content/pwgen.js",
"content/sites.js",
"content/ui.js",
"content/username-field.js",
"global.js"
],
"css": [
"css/autocomplete.css",
"css/banner.css",
"css/button.css",
"css/define.css",
"css/pwgen.css"
"css/notification.css",
"css/pwgen.css",
"css/username.css"
],
"run_at": "document_idle",
"all_frames": true
@ -102,7 +108,9 @@
}
},
"web_accessible_resources": [
"icons/key.svg"
"icons/keepassxc.svg",
"icons/key.svg",
"icons/locked.svg"
],
"permissions": [
"activeTab",

View file

@ -38,56 +38,6 @@
<span data-i18n="contextMenuShowPasswordGenerator"></span>: <span id="show_password_generator-shortcut">error</span>
</p>
<p id="chrome-only"><button class="btn btn-sm btn-primary" id="configureCommands" type="button" data-i18n="[title]openNewTab"><span class="glyphicon glyphicon-cog"></span><span data-i18n="optionsConfigureShortcuts"></span></button></p>
<p>
<div class="form-group">
<label for="blinkTimeout" data-i18n="optionsLabelBlinkTime"></label>
<div class="control-group">
<div class="input-append">
<input type="number" id="blinkTimeout" placeholder="7500" value="7500" min="-1"/>
<button class="btn btn-sm btn-primary" id="blinkTimeoutButton" type="button"><span class="glyphicon glyphicon-floppy-disk"></span> <span data-i18n="optionsButtonSave"></span></button>
</div>
</div>
<span class="help-inline">
<span data-i18n="optionsBlinkTimeHelpText"></span>
<br />
<span data-i18n="optionsDefault" i18n-placeholder="7500"></span>, <span data-i18n="optionsInfinite" i18n-placeholder="-1"></span>
</span>
</div>
</p>
<p>
<div class="form-group">
<label for="blinkMinTimeout" data-i18n="optionsLabelRedirectOffset"></label>
<div class="control-group">
<div class="input-append">
<input type="number" id="blinkMinTimeout" placeholder="2000" value="2000" min="-1"/>
<button class="btn btn-sm btn-primary" id="blinkMinTimeoutButton" type="button"><span class="glyphicon glyphicon-floppy-disk"></span> <span data-i18n="optionsButtonSave"></span></button>
</div>
</div>
<span class="help-inline">
<span data-i18n="optionsRedirectOffsetHelpText"></span>
<br />
<span data-i18n="optionsRedirectOffsetHelpTextSecond"></span>
<br />
<span data-i18n="optionsDefault" i18n-placeholder="-1"></span>, <span data-i18n="optionsRecommended" i18n-placeholder="2000"></span>
</span>
</div>
</p>
<p>
<div class="form-group">
<label for="allowedRedirect" data-i18n="optionsLabelRedirectAllowance"></label>
<div class="control-group">
<div class="input-append">
<input type="number" id="allowedRedirect" placeholder="1" value="1" min="1"/>
<button class="btn btn-sm btn-primary" id="allowedRedirectButton" type="button"><span class="glyphicon glyphicon-floppy-disk"></span> <span data-i18n="optionsButtonSave"></span></button>
</div>
</div>
<span class="help-inline">
<span data-i18n="optionsRedirectAllowanceHelpText"></span>
<br />
<span data-i18n="optionsDefault" i18n-placeholder="1"></span>
</span>
</div>
</p>
<hr />
<p>
<div class="form-group">
@ -116,7 +66,7 @@
<p>
<div class="checkbox">
<label class="checkbox">
<input type="checkbox" name="usePasswordGenerator" value="true"/><span data-i18n="optionsCheckboxUsePasswordGenerator"></span>
<input type="checkbox" name="usePasswordGeneratorIcons" value="true"/><span data-i18n="optionsCheckboxUsePasswordGenerator"></span>
</label>
<span class="help-block">
<span data-i18n="optionsUsePasswordGeneratorHelpText"></span>

View file

@ -4,28 +4,22 @@ if (jQuery) {
var $ = jQuery.noConflict(true);
}
const defaultSettings = {
blinkTimeout: 7500,
redirectOffset: -1,
redirectAllowance: 1
};
$(function() {
browser.runtime.sendMessage({ action: 'load_settings' }).then((settings) => {
$(async function() {
try {
const settings = await browser.runtime.sendMessage({ action: 'load_settings' });
options.settings = settings;
browser.runtime.sendMessage({ action: 'load_keyring' }).then((keyRing) => {
options.keyRing = keyRing;
options.initMenu();
options.initGeneralSettings();
options.initConnectedDatabases();
options.initCustomCredentialFields();
options.initSitePreferences();
options.initAbout();
});
});
// Set options page language to HTML element from locale
$('html').attr('lang', browser.i18n.getUILanguage());
const keyRing = await browser.runtime.sendMessage({ action: 'load_keyring' });
options.keyRing = keyRing;
options.initMenu();
options.initGeneralSettings();
options.initConnectedDatabases();
options.initCustomCredentialFields();
options.initSitePreferences();
options.initAbout();
} catch (err) {
console.log('Error loading options page: ' + err);
}
});
var options = options || {};
@ -54,7 +48,7 @@ options.saveSettingsPromise = function() {
});
};
options.saveSetting = function(name) {
options.saveSetting = async function(name) {
const id = '#' + name;
$(id).closest('.control-group').removeClass('error').addClass('success');
setTimeout(() => {
@ -62,21 +56,21 @@ options.saveSetting = function(name) {
}, 2500);
browser.storage.local.set({ 'settings': options.settings });
browser.runtime.sendMessage({
await browser.runtime.sendMessage({
action: 'load_settings'
});
};
options.saveSettings = function() {
options.saveSettings = async function() {
browser.storage.local.set({ 'settings': options.settings });
browser.runtime.sendMessage({
await browser.runtime.sendMessage({
action: 'load_settings'
});
};
options.saveKeyRing = function() {
options.saveKeyRing = async function() {
browser.storage.local.set({ 'keyRing': options.keyRing });
browser.runtime.sendMessage({
await browser.runtime.sendMessage({
action: 'load_keyring'
});
};
@ -123,7 +117,7 @@ options.initGeneralSettings = function() {
});
$('#tab-general-settings input[type=radio]').change(function() {
options.settings[$(this).attr('name')] = $(this).val();
options.settings[$(this).attr('name')] = Number($(this).val());
options.saveSettings();
});
@ -139,13 +133,9 @@ options.initGeneralSettings = function() {
}).then(options.showKeePassXCVersions);
});
$('#blinkTimeout').val(options.settings['blinkTimeout']);
$('#blinkMinTimeout').val(options.settings['blinkMinTimeout']);
$('#allowedRedirect').val(options.settings['allowedRedirect']);
browser.commands.getAll().then(function(commands) {
commands.forEach(function(command) {
var shortcut = document.getElementById(`${command.name}-shortcut`);
let shortcut = document.getElementById(`${command.name}-shortcut`);
if (!shortcut) {
return;
}
@ -159,35 +149,6 @@ options.initGeneralSettings = function() {
});
});
$('#blinkTimeoutButton').click(function() {
const input = document.querySelector('#blinkTimeout');
if (!input.validity.valid) {
options.createWarning(input, tr('optionsErrorInvalidValue'));
}
const blinkTimeout = input.value;
const blinkTimeoutval = blinkTimeout !== '' ? Number(blinkTimeout) : defaultSettings.blinkTimeout;
options.settings['blinkTimeout'] = blinkTimeoutval;
options.saveSetting('blinkTimeout');
});
$('#blinkMinTimeoutButton').click(function() {
const blinkMinTimeout = $.trim($('#blinkMinTimeout').val());
const blinkMinTimeoutval = blinkMinTimeout !== '' ? Number(blinkMinTimeout) : defaultSettings.redirectOffset;
options.settings['blinkMinTimeout'] = blinkMinTimeoutval;
options.saveSetting('blinkMinTimeout');
});
$('#allowedRedirectButton').click(function() {
const allowedRedirect = $.trim($('#allowedRedirect').val());
const allowedRedirectval = allowedRedirect !== '' ? Number(allowedRedirect) : defaultSettings.redirectAllowance;
options.settings['allowedRedirect'] = allowedRedirectval;
options.saveSetting('allowedRedirect');
});
$('#defaultGroupButton').click(function() {
const value = $('#defaultGroup').val();
if (value.length > 0) {
@ -253,7 +214,8 @@ options.initConnectedDatabases = function() {
const trClone = $('#tab-connected-databases table tr.clone:first').clone(true);
trClone.removeClass('clone');
for (const hash in options.keyRing) {
const addHashToTable = function(hash) {
const tr = trClone.clone(true);
tr.data('hash', hash);
tr.attr('id', 'tr-cd-' + hash);
@ -269,16 +231,28 @@ options.initConnectedDatabases = function() {
$('#tab-connected-databases table tbody:first').append(tr);
}
const hashList = options.keyRing;
for (const hash in hashList) {
addHashToTable(hash);
}
if ($('#tab-connected-databases table tbody:first tr').length > 2) {
$('#tab-connected-databases table tbody:first tr.empty:first').hide();
} else {
$('#tab-connected-databases table tbody:first tr.empty:first').show();
}
$('#connect-button').click(function() {
browser.runtime.sendMessage({
action: 'associate'
});
$('#connect-button').click(async function() {
const result = await browser.runtime.sendMessage({ action: 'associate' });
if (result === AssociatedAction.NEW_ASSOCIATION) {
// Update the connection list with the added hash
options.keyRing = await browser.runtime.sendMessage({ action: 'load_keyring' });
for (const hash in hashList) {
const newHash = Object.keys(options.keyRing).find(h => h !== hash);
addHashToTable(newHash);
}
}
});
};
@ -485,8 +459,8 @@ options.createWarning = function(elem, text) {
const banner = document.createElement('div');
banner.classList.add('alert', 'alert-dismissible', 'alert-danger', 'fade', 'in');
banner.style.position = 'absolute';
banner.style.left = String(elem.offsetLeft) + 'px';
banner.style.top = String(elem.offsetTop + elem.offsetHeight) + 'px';
banner.style.left = Pixels(elem.offsetLeft);
banner.style.top = Pixels(elem.offsetTop + elem.offsetHeight);
banner.style.padding = '0px';
banner.style.width = '300px';
banner.textContent = text;

View file

@ -69,25 +69,10 @@ body {
height: 30px;
}
#login-filter {
outline:none;
outline: none;
border-radius: 4px;
border: 1px solid #ccc;
margin-bottom: 5px;
padding: 2px 10px;
width: 100%;
}
.credentials .groups {
display: none;
border-top: 1px solid #333;
}
.connected-database {
display: none;
font-size: 85%;
color: #787878;
}
.credentials .username-new {
display: none;
}
.credentials .username-exists {
display: none;
}

View file

@ -79,6 +79,14 @@
<button id="reopen-database-button" class="btn btn-sm btn-primary"><span class="glyphicon glyphicon-lock"></span> <span data-i18n="popupReopenButton"/></button>
</div>
</div>
<div id="username-field-detected" style="display: none">
<hr />
<p data-i18n="popupUsernameFieldDetected"></p>
<div style="text-align: right">
<button id="username-only-button" class="btn btn-sm btn-primary"><span class="glyphicon glyphicon-plus"></span> <span data-i18n="popupUsernameButton"/></button>
</div>
</div>
</div>
</body>
</html>

View file

@ -30,57 +30,80 @@ function statusResponse(r) {
$('#configured-and-associated').show();
$('#associated-identifier').html(r.identifier);
$('#lock-database-button').show();
if (r.usernameFieldDetected) {
$('#username-field-detected').show();
}
}
}
$(function() {
$('#connect-button').click(function() {
browser.runtime.sendMessage({
const sendMessageToTab = async function(message) {
const tabs = await browser.tabs.query({ active: true, currentWindow: true });
if (tabs.length === 0) {
return false; // Only the background devtools or a popup are opened
}
await browser.tabs.sendMessage(tabs[0].id, {
action: message
});
return true;
};
$(async () => {
$('#connect-button').click(async () => {
await browser.runtime.sendMessage({
action: 'associate'
});
close();
});
$('#reconnect-button').click(function() {
browser.runtime.sendMessage({
$('#reconnect-button').click(async () => {
await browser.runtime.sendMessage({
action: 'associate'
});
close();
});
$('#reload-status-button').click(function() {
browser.runtime.sendMessage({
$('#reload-status-button').click(async () => {
statusResponse(await browser.runtime.sendMessage({
action: 'reconnect'
}).then(statusResponse);
}));
});
$('#reopen-database-button').click(function() {
browser.runtime.sendMessage({
$('#reopen-database-button').click(async () => {
statusResponse(await browser.runtime.sendMessage({
action: 'get_status',
args: [ false, true ] // Set forcePopup to true
}).then(statusResponse);
}));
});
$('#redetect-fields-button').click(function() {
browser.tabs.query({ 'active': true, 'currentWindow': true }).then(function(tabs) {
if (tabs.length === 0) {
return; // For example: only the background devtools or a popup are opened
}
const tab = tabs[0];
$('#redetect-fields-button').click(async () => {
const res = await sendMessageToTab('redetect_fields');
if (!res) {
return;
}
browser.tabs.sendMessage(tab.id, {
action: 'redetect_fields'
});
});
statusResponse(await browser.runtime.sendMessage({
action: 'get_status'
}));
});
$('#lock-database-button').click(function() {
browser.runtime.sendMessage({
$('#lock-database-button').click(async () => {
statusResponse(await browser.runtime.sendMessage({
action: 'lock-database'
}).then(statusResponse);
}));
});
browser.runtime.sendMessage({
$('#username-only-button').click(async () => {
await sendMessageToTab('add_username_only_option');
await sendMessageToTab('redetect_fields');
$('#username-field-detected').hide();
});
statusResponse(await browser.runtime.sendMessage({
action: 'get_status'
}).then(statusResponse);
}).catch((err) => {
console.log('Error: Could not get status: ' + err);
}));
});

View file

@ -8,31 +8,28 @@ function updateAvailableResponse(available) {
}
}
function initSettings() {
$('#settings #btn-options').click(function() {
async function initSettings() {
$('#settings #btn-options').click(() => {
browser.runtime.openOptionsPage().then(close());
});
$('#settings #btn-choose-credential-fields').click(function() {
browser.windows.getCurrent().then((win) => {
browser.tabs.query({ 'active': true, 'currentWindow': true }).then((tabs) => {
const tab = tabs[0];
browser.runtime.getBackgroundPage().then((global) => {
browser.tabs.sendMessage(tab.id, {
action: 'choose_credential_fields'
});
close();
});
});
$('#settings #btn-choose-credential-fields').click(async () => {
await browser.windows.getCurrent();
const tabs = await browser.tabs.query({ active: true, currentWindow: true });
const tab = tabs[0];
await browser.runtime.getBackgroundPage();
browser.tabs.sendMessage(tab.id, {
action: 'choose_credential_fields'
});
close();
});
}
$(function() {
initSettings();
browser.runtime.sendMessage({
$(async () => {
await initSettings();
updateAvailableResponse(await browser.runtime.sendMessage({
action: 'update_available_keepassxc'
}).then(updateAvailableResponse);
}));
});

View file

@ -1,38 +1,33 @@
'use strict';
const getLoginData = function() {
return new Promise((resolve, reject) => {
browser.runtime.getBackgroundPage().then((global) => {
browser.tabs.query({ 'active': true, 'currentWindow': true }).then((tabs) => {
resolve(global.page.tabs[tabs[0].id].loginList);
});
});
});
const getLoginData = async () => {
const global = await browser.runtime.getBackgroundPage();
const tabs = await browser.tabs.query({ active: true, currentWindow: true });
return global.page.tabs[tabs[0].id].loginList;
};
$(function() {
getLoginData().then((data) => {
const ll = document.getElementById('login-list');
for (let i = 0; i < data.logins.length; ++i) {
const a = document.createElement('a');
a.setAttribute('class', 'list-group-item');
a.textContent = data.logins[i].login + ' (' + data.logins[i].name + ')';
$(a).data('creds', data.logins[i]);
$(a).click(function() {
if (data.resolve) {
const creds = $(this).data('creds');
data.resolve({
authCredentials: {
username: creds.login,
password: creds.password
}
});
}
close();
});
ll.appendChild(a);
}
});
$(async () => {
const data = await getLoginData();
const ll = document.getElementById('login-list');
for (let i = 0; i < data.logins.length; ++i) {
const a = document.createElement('a');
a.setAttribute('class', 'list-group-item');
a.textContent = data.logins[i].login + ' (' + data.logins[i].name + ')';
$(a).data('creds', data.logins[i]);
$(a).click(function() {
if (data.resolve) {
const creds = $(this).data('creds');
data.resolve({
authCredentials: {
username: creds.login,
password: creds.password
}
});
}
close();
});
ll.appendChild(a);
}
$('#lock-database-button').click(function() {
browser.runtime.sendMessage({
@ -40,18 +35,17 @@ $(function() {
}).then(statusResponse);
});
$('#btn-dismiss').click(function() {
getLoginData().then((data) => {
// Using reject won't work with every browser. So return empty credentials instead.
if (data.resolve) {
data.resolve({
authCredentials: {
username: '',
password: ''
}
});
}
close();
});
$('#btn-dismiss').click(async () => {
const loginData = await getLoginData();
// Using reject won't work with every browser. So return empty credentials instead.
if (loginData.resolve) {
loginData.resolve({
authCredentials: {
username: '',
password: ''
}
});
}
close();
});
});

View file

@ -1,51 +1,57 @@
'use strict';
$(function() {
browser.runtime.getBackgroundPage().then((global) => {
browser.tabs.query({ 'active': true, 'currentWindow': true }).then((tabs) => {
if (tabs.length === 0) {
return; // For example: only the background devtools or a popup are opened
}
const tab = tabs[0];
$(async () => {
const global = await browser.runtime.getBackgroundPage();
const tabs = await browser.tabs.query({ active: true, currentWindow: true });
if (tabs.length === 0) {
return; // For example: only the background devtools or a popup are opened
}
const tab = tabs[0];
const logins = global.page.tabs[tab.id].loginList;
const ll = document.getElementById('login-list');
for (let i = 0; i < logins.length; i++) {
const a = document.createElement('a');
a.textContent = logins[i];
a.setAttribute('class', 'list-group-item');
a.setAttribute('id', '' + i);
a.addEventListener('click', (e) => {
const id = e.target.id;
browser.tabs.sendMessage(tab.id, {
action: 'fill_user_pass_with_specific_login',
id: Number(id)
});
close();
});
ll.appendChild(a);
const logins = global.page.tabs[tab.id].loginList;
const ll = document.getElementById('login-list');
for (let i = 0; i < logins.length; i++) {
const a = document.createElement('a');
a.textContent = logins[i];
a.setAttribute('class', 'list-group-item');
a.setAttribute('id', '' + i);
a.addEventListener('click', (e) => {
if (!e.isTrusted) {
return;
}
if (logins.length > 1) {
document.getElementById('filter-block').style = '';
const filter = document.getElementById('login-filter');
filter.addEventListener('keyup', (e) => {
const val = filter.value;
const re = new RegExp(val, 'i');
const links = ll.getElementsByTagName('a');
for (const i in links) {
if (links.hasOwnProperty(i)) {
const found = String(links[i].textContent).match(re) !== null;
links[i].style = found ? '' : 'display: none;';
}
}
});
filter.focus();
const id = e.target.id;
browser.tabs.sendMessage(tab.id, {
action: 'fill_user_pass_with_specific_login',
id: Number(id)
});
close();
});
ll.appendChild(a);
}
if (logins.length > 1) {
document.getElementById('filter-block').style = '';
const filter = document.getElementById('login-filter');
filter.addEventListener('keyup', (e) => {
if (!e.isTrusted) {
return;
}
const val = filter.value;
const re = new RegExp(val, 'i');
const links = ll.getElementsByTagName('a');
for (const i in links) {
if (links.hasOwnProperty(i)) {
const found = String(links[i].textContent).match(re) !== null;
links[i].style = found ? '' : 'display: none;';
}
}
});
});
filter.focus();
}
$('#lock-database-button').click(function() {
$('#lock-database-button').click((e) => {
browser.runtime.sendMessage({
action: 'lock-database'
});
@ -54,7 +60,7 @@ $(function() {
$('#database-error-message').html(tr('errorMessageDatabaseNotOpened'));
});
$('#reopen-database-button').click(function() {
$('#reopen-database-button').click((e) => {
browser.runtime.sendMessage({
action: 'get_status',
args: [ false, true ] // Set forcePopup to true

View file

@ -1,49 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title data-i18n="popupTitle"></title>
<meta charset="UTF-8">
<link rel="stylesheet" href="popup.css" />
<link rel="stylesheet" href="../options/bootstrap.min.css" />
<script src="../browser-polyfill.min.js"></script>
<script src="../global.js"></script>
<script src="../jquery-3.3.1.min.js"></script>
<script src="../options/bootstrap.min.js"></script>
<script src="popup_functions.js"></script>
<script src="popup_remember.js"></script>
<script defer src="../translate.js"></script>
</head>
<body>
<div class="buttons">
<p>
<span data-i18n="popupRememberInfoText"></span>
<br />
<span class="small" data-i18n="popupURL"></span><span class="small info information-url"></span>
<br />
<span class="small" data-i18n="popupUsername"></span><span class="small info information-username"></span>
</p>
<p>
<button id="btn-new" class="btn btn-sm btn-success"><span class="glyphicon glyphicon-pencil"></span> <span data-i18n="popupButtonNew"></span></button>
<button id="btn-update" class="btn btn-sm btn-warning"><span class="glyphicon glyphicon-refresh"></span> <span data-i18n="popupButtonUpdate"></span></button>
<button id="btn-dismiss" class="btn btn-sm btn-danger"><span class="glyphicon glyphicon-remove"></span> <span data-i18n="popupButtonDismiss"></span></button>
<button id="btn-ignore" class="btn btn-sm btn-default"><span class="glyphicon glyphicon-remove"></span> <span data-i18n="popupButtonIgnore"></span></button>
</p>
</div>
<div class="connected-database">
<p data-i18n="popupRememberSaving" i18n-placeholder="<span></span><em></em>"></p>
</div>
<div class="credentials" style="display: none;">
<p class="username-new"><span data-i18n="popupRememberNewUsername" i18n-placeholder="<strong></strong>"></span></p>
<p class="username-exists"><span data-i18n="popupRememberUsernameExists" i18n-placeholder="<strong></strong>"></span></p>
<p data-i18n="popupRememberChooseCredentials"></p>
<ul id="list" class="list-group"></ul>
</div>
<div class="groups" style="display: none;">
<p data-i18n="popupRememberChooseGroup"></p>
<ul id="list" class="list-group"></ul>
</div>
</body>
</html>

View file

@ -1,290 +0,0 @@
'use strict';
const DEFAULT_BROWSER_GROUP = 'KeePassXC-Browser Passwords';
var _tab;
function _initialize(tab) {
_tab = tab;
// No credentials set or credentials already cleared
if (!_tab.credentials.username && !_tab.credentials.password) {
_close();
return;
}
// No existing credentials to update --> disable Update button
if (_tab.credentials.list.length === 0) {
$('#btn-update').attr('disabled', true).removeClass('btn-warning');
}
// No username available. This might be because of trigger from context menu --> disable New button
if (!_tab.credentials.username && _tab.credentials.password) {
$('#btn-new').attr('disabled', true).removeClass('btn-success');
}
let url = _tab.credentials.url;
url = (url.length > 50) ? url.substring(0, 50) + '...' : url;
$('.information-url:first').text(url);
$('.information-username:first').text(_tab.credentials.username);
$('#btn-new').click(function(e) {
e.preventDefault();
$('.credentials').hide();
$('ul#list').empty();
// Get group listing from KeePassXC
browser.runtime.sendMessage({
action: 'get_database_groups'
}).then((result) => {
// Only the Root group and no KeePassXC-Browser passwords -> save to default
// Or when default group is not set and defaultGroupAskAlways is disabled -> save to default
if ((result.groups === undefined || (result.groups.length > 0 && result.groups[0].children.length === 0)) ||
(!result.defaultGroupAlwaysAsk && (result.defaultGroup === '' || result.defaultGroup === DEFAULT_BROWSER_GROUP))) {
browser.runtime.sendMessage({
action: 'add_credentials',
args: [ _tab.credentials.username, _tab.credentials.password, _tab.credentials.url ]
}).then(_verifyResult);
return;
} else if (!result.defaultGroupAlwaysAsk && (result.defaultGroup !== '' || result.defaultGroup !== DEFAULT_BROWSER_GROUP)) {
// Another group name has been specified
const [ gname, guuid ] = getDefaultGroup(result.groups[0].children, result.defaultGroup);
if (gname === '' && guuid === '') {
showNotification(tr('popupRememberInfoDefaultGroupNotFound'));
// Create a new group
browser.runtime.sendMessage({
action: 'create_new_group',
args: [ result.defaultGroup ]
}).then((newGroup) => {
if (newGroup.name && newGroup.uuid) {
browser.runtime.sendMessage({
action: 'add_credentials',
args: [ _tab.credentials.username, _tab.credentials.password, _tab.credentials.url, newGroup.name, newGroup.uuid ]
}).then(_verifyResult);
} else {
showNotification(tr('popupRememberErrorCreatingNewGroup'));
}
return;
});
}
browser.runtime.sendMessage({
action: 'add_credentials',
args: [ _tab.credentials.username, _tab.credentials.password, _tab.credentials.url, gname, guuid ]
}).then(_verifyResult);
return;
}
const addChildren = function(group, parentElement, depth) {
++depth;
const padding = depth * 20;
for (const child of group.children) {
const a = createLink(child.name, child.uuid, child.children.length > 0);
a.attr('id', 'child');
a.css('cssText', 'padding-left: ' + String(padding) + 'px !important;');
if (parentElement.attr('id') === 'root') {
a.attr('id', 'root-child');
}
$('ul#list').append(a);
addChildren(child, a, depth);
}
};
const createLink = function(group, groupUuid, hasChildren) {
const a = $('<a>')
.attr('href', '#')
.attr('class', 'list-group-item')
.text(group)
.click(function(ev) {
ev.preventDefault();
browser.runtime.sendMessage({
action: 'add_credentials',
args: [ _tab.credentials.username, _tab.credentials.password, _tab.credentials.url, group, groupUuid ]
}).then(_verifyResult);
});
if (hasChildren) {
a.text('\u25BE ' + group);
}
return a;
};
// Create the link list for group selection
let depth = 0;
for (const g of result.groups) {
const a = createLink(g.name, g.uuid, g.children.length > 0);
a.attr('id', 'root');
$('ul#list').append(a);
addChildren(g, a, depth);
}
$('.groups').show();
});
});
$('#btn-update').click(function(e) {
e.preventDefault();
$('.groups').hide();
$('ul#list').empty();
// Only one entry which could be updated
if (_tab.credentials.list.length === 1) {
// Use the current username if it's empty
if (!_tab.credentials.username) {
_tab.credentials.username = _tab.credentials.list[0].login;
}
browser.runtime.sendMessage({
action: 'update_credentials',
args: [ _tab.credentials.list[0].uuid, _tab.credentials.username, _tab.credentials.password, _tab.credentials.url ]
}).then(_verifyResult);
} else {
$('.credentials:first .username-new:first strong:first').text(_tab.credentials.username);
$('.credentials:first .username-exists:first strong:first').text(_tab.credentials.username);
if (_tab.credentials.usernameExists) {
$('.credentials:first .username-new:first').hide();
$('.credentials:first .username-exists:first').show();
} else {
$('.credentials:first .username-new:first').show();
$('.credentials:first .username-exists:first').hide();
}
for (let i = 0; i < _tab.credentials.list.length; i++) {
const $a = $('<a>')
.attr('href', '#')
.attr('class', 'list-group-item')
.text(_tab.credentials.list[i].login + ' (' + _tab.credentials.list[i].name + ')')
.data('entryId', i)
.click(function(ev) {
ev.preventDefault();
const entryId = $(this).data('entryId');
// Use the current username if it's empty
if (!_tab.credentials.username) {
_tab.credentials.username = _tab.credentials.list[entryId].login;
}
// Check if the password has changed for the updated credentials
browser.runtime.sendMessage({
action: 'retrieve_credentials',
args: [ url, '', false, true ]
}).then((credentials) => {
if (!credentials || credentials.length !== _tab.credentials.list.length) {
_verifyResult('error');
return;
}
// Show a notification if the user tries to update credentials using the old password
if (credentials[entryId].password === _tab.credentials.password) {
showNotification(tr('popupRememberErrorPasswordNotChanged'));
_close();
return;
}
browser.runtime.sendMessage({
action: 'update_credentials',
args: [ _tab.credentials.list[entryId].uuid, _tab.credentials.username, _tab.credentials.password, _tab.credentials.url ]
}).then(_verifyResult);
});
});
if (_tab.credentials.usernameExists && _tab.credentials.username === _tab.credentials.list[i].login) {
$a.css('font-weight', 'bold');
}
$('ul#list').append($a);
}
$('.credentials').show();
}
});
$('#btn-dismiss').click(function(e) {
e.preventDefault();
_close();
});
$('#btn-ignore').click(function(e) {
browser.windows.getCurrent().then((win) => {
browser.tabs.query({ 'active': true, 'currentWindow': true }).then((tabs) => {
const currentTab = tabs[0];
browser.runtime.getBackgroundPage().then((global) => {
browser.tabs.sendMessage(currentTab.id, {
action: 'ignore_site',
args: [ _tab.credentials.url ]
});
_close();
});
});
});
});
}
function _connectedDatabase(db) {
if (db.count > 1 && db.identifier) {
$('.connected-database:first em:first').text(db.identifier);
$('.connected-database:first').show();
} else {
$('.connected-database:first').hide();
}
}
function _verifyResult(code) {
if (code === 'error') {
showNotification(tr('popupRememberErrorCannotSaveCredentials'));
}
_close();
}
function _close() {
browser.runtime.sendMessage({
action: 'remove_credentials_from_tab_information'
});
browser.runtime.sendMessage({
action: 'pop_stack'
});
close();
}
// Traverse the groups and ensure all paths are found
const getDefaultGroup = function(groups, defaultGroup) {
const getGroup = function(group, splitted, depth) {
++depth;
for (const g of group) {
if (g.name === splitted[depth]) {
if (splitted.length === (depth + 1)) {
return [ g.name, g.uuid ];
}
return getGroup(g.children, splitted, depth);
}
}
return [ '', '' ];
};
let depth = -1;
const splitted = defaultGroup.split('/');
return getGroup(groups, splitted, depth);
};
$(function() {
browser.runtime.sendMessage({
action: 'stack_add',
args: [ 'icon_remember_red_background.png', 'popup_remember.html', 10, true, 0 ]
});
browser.runtime.sendMessage({
action: 'get_tab_information'
}).then(_initialize);
browser.runtime.sendMessage({
action: 'get_connected_database'
}).then(_connectedDatabase);
});