mirror of
https://github.com/keepassxreboot/keepassxc-browser.git
synced 2026-03-11 08:54:43 +00:00
Merge tag '1.2.0'
Version 1.2.0
This commit is contained in:
commit
191cf22a33
20 changed files with 727 additions and 221 deletions
35
CHANGELOG
35
CHANGELOG
|
|
@ -1,3 +1,38 @@
|
|||
1.2.0 (29-07-2018)
|
||||
=========================
|
||||
- Replace "Ignored Sites" with new "Site Preferences" settings page [#208]
|
||||
- Fix checks throwing errors [#207, #222]
|
||||
- Ignore more non-input elements [#212, 210]
|
||||
- Fix URL matching when there is a trailing slash [#231]
|
||||
- Allow infiting waiting on "Remember Credentials" popup [#232]
|
||||
- Minor user interface adjustments [#233, #230, #213]
|
||||
- Improve search field detection [#195]
|
||||
|
||||
1.1.7 (13-06-2018)
|
||||
=========================
|
||||
- Fix credential field detection regression [#199]
|
||||
|
||||
1.1.6 (12-06-2018)
|
||||
=========================
|
||||
- Disable single username field detection [#194]
|
||||
- Fix ignored sites [#196]
|
||||
- Detect credential fields without type [#198]
|
||||
|
||||
1.1.5 (11-06-2018)
|
||||
=========================
|
||||
- Fix search fields being detected as username fields [#189]
|
||||
|
||||
1.1.4 (10-06-2018)
|
||||
=========================
|
||||
- Improve performance of field detection and limit it to 100 fields [#166,#157,185]
|
||||
- Improve option to ignore fields [#170]
|
||||
- Automatically retrieve credentials after unlocking the database [#153]
|
||||
- Fix option to only save the domain name not working as intended [#151]
|
||||
- Fix credentials popup not being shown on some websites [#154]
|
||||
- Improve detection of username fields [#164]
|
||||
- Allow filling of TOTP fields when they are on a separate page [#162]
|
||||
- Ignore invisible input fields more strictly [#176]
|
||||
|
||||
1.1.3 (11-05-2018)
|
||||
=========================
|
||||
- Remove autoreconnect to prevent proxy process leakage on Windows [#147]
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ const BLINK_TIMEOUT_REDIRECT_COUNT_DEFAULT = 1;
|
|||
|
||||
browserAction.show = function(callback, tab) {
|
||||
let data = {};
|
||||
if (!page.tabs[tab.id] || page.tabs[tab.id].stack.length == 0) {
|
||||
if (!page.tabs[tab.id] || page.tabs[tab.id].stack.length === 0) {
|
||||
browserAction.showDefault(callback, tab);
|
||||
return;
|
||||
}
|
||||
|
|
@ -36,7 +36,7 @@ browserAction.update = function(interval) {
|
|||
|
||||
let data = page.tabs[page.currentTabId].stack[page.tabs[page.currentTabId].stack.length - 1];
|
||||
|
||||
if (typeof data.visibleForMilliSeconds !== 'undefined') {
|
||||
if (data.visibleForMilliSeconds !== undefined && data.visibleForMilliSeconds !== -1) {
|
||||
if (data.visibleForMilliSeconds <= 0) {
|
||||
browserAction.stackPop(page.currentTabId);
|
||||
browserAction.show(null, {'id': page.currentTabId});
|
||||
|
|
@ -87,7 +87,7 @@ browserAction.showDefault = function(callback, tab) {
|
|||
});
|
||||
};
|
||||
|
||||
browserAction.stackAdd = function(callback, tab, icon, popup, level, push, visibleForMilliSeconds, visibleForPageUpdates, redirectOffset, dontShow) {
|
||||
browserAction.stackAdd = function(callback, tab, icon, popup, level, push, visibleForMilliSeconds, visibleForPageUpdates, redirectOffset, dontShow) {
|
||||
const id = tab.id || page.currentTabId;
|
||||
|
||||
if (!level) {
|
||||
|
|
@ -103,15 +103,15 @@ browserAction.stackAdd = function(callback, tab, icon, popup, level, push, visib
|
|||
stackData.popup = popup;
|
||||
}
|
||||
|
||||
if (visibleForMilliSeconds) {
|
||||
if (visibleForMilliSeconds !== undefined) {
|
||||
stackData.visibleForMilliSeconds = visibleForMilliSeconds;
|
||||
}
|
||||
|
||||
if (visibleForPageUpdates) {
|
||||
if (visibleForPageUpdates !== undefined) {
|
||||
stackData.visibleForPageUpdates = visibleForPageUpdates;
|
||||
}
|
||||
|
||||
if (redirectOffset) {
|
||||
if (redirectOffset !== undefined) {
|
||||
stackData.redirectOffset = redirectOffset;
|
||||
}
|
||||
|
||||
|
|
@ -181,7 +181,7 @@ browserAction.removeRememberPopup = function(callback, tab, removeImmediately) {
|
|||
return;
|
||||
}
|
||||
|
||||
if( page.tabs[tab.id].stack.length == 0) {
|
||||
if (page.tabs[tab.id].stack.length === 0) {
|
||||
page.clearCredentials(tab.id);
|
||||
return;
|
||||
}
|
||||
|
|
@ -205,21 +205,23 @@ browserAction.setRememberPopup = function(tabId, username, password, url, userna
|
|||
browser.storage.local.get({'settings': {}}).then(function(item) {
|
||||
const settings = item.settings;
|
||||
|
||||
// Don't show anything if the site is in the ignore list
|
||||
for (const site in settings.ignoredSites) {
|
||||
if (site === url) {
|
||||
return;
|
||||
// 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, 0));
|
||||
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, 0);
|
||||
const blinkTimeout = getValueOrDefault(settings, 'blinkTimeout', BLINK_TIMEOUT_DEFAULT, -1);
|
||||
const pageUpdateAllowance = getValueOrDefault(settings, 'allowedRedirect', BLINK_TIMEOUT_REDIRECT_COUNT_DEFAULT, 0);
|
||||
|
||||
const stackData = {
|
||||
|
|
|
|||
|
|
@ -159,6 +159,9 @@ kpxcEvent.onReconnect = function(callback, tab) {
|
|||
// Add a small timeout after reconnecting. Just to make sure. It's not pretty, I know :(
|
||||
setTimeout(() => {
|
||||
keepass.reconnect(callback, tab).then((configured) => {
|
||||
browser.tabs.sendMessage(tab.id, {
|
||||
action: 'redetect_fields'
|
||||
});
|
||||
kpxcEvent.showStatus(configured, tab, callback);
|
||||
});
|
||||
}, 500);
|
||||
|
|
@ -269,6 +272,14 @@ kpxcEvent.pageClearLogins = function(callback, tab, alreadyCalled) {
|
|||
callback();
|
||||
};
|
||||
|
||||
kpxcEvent.pageGetLoginId = function(callback, tab) {
|
||||
callback(page.loginId);
|
||||
};
|
||||
|
||||
kpxcEvent.pageSetLoginId = function(callback, tab, loginId) {
|
||||
page.loginId = loginId;
|
||||
};
|
||||
|
||||
// all methods named in this object have to be declared BEFORE this!
|
||||
kpxcEvent.messageHandlers = {
|
||||
'add_credentials': keepass.addCredentials,
|
||||
|
|
@ -284,6 +295,8 @@ kpxcEvent.messageHandlers = {
|
|||
'load_settings': kpxcEvent.onLoadSettings,
|
||||
'lock-database': kpxcEvent.lockDatabase,
|
||||
'page_clear_logins': kpxcEvent.pageClearLogins,
|
||||
'page_get_login_id': kpxcEvent.pageGetLoginId,
|
||||
'page_set_login_id': kpxcEvent.pageSetLoginId,
|
||||
'pop_stack': kpxcEvent.onPopStack,
|
||||
'popup_login': kpxcEvent.onLoginPopup,
|
||||
'popup_multiple-fields': kpxcEvent.onMultipleFieldsPopup,
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ keepass.migrateKeyRing().then(() => {
|
|||
// Milliseconds for intervall (e.g. to update browserAction)
|
||||
const _interval = 250;
|
||||
|
||||
|
||||
/**
|
||||
* Generate information structure for created tab and invoke all needed
|
||||
* functions if tab is created in foreground
|
||||
|
|
@ -71,7 +70,13 @@ browser.tabs.onActivated.addListener((activeInfo) => {
|
|||
* @param {object} changeInfo
|
||||
*/
|
||||
browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
||||
// If the tab URL has changed (e.g. logged in) clear credentials
|
||||
if (changeInfo.url) {
|
||||
page.clearLogins(tabId);
|
||||
}
|
||||
|
||||
if (changeInfo.status === 'complete') {
|
||||
browserAction.showDefault(null, tab);
|
||||
kpxcEvent.invoke(browserAction.removeRememberPopup, null, tabId, []);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -185,6 +185,7 @@ keepass.updateCredentials = function(callback, tab, entryId, username, password,
|
|||
}
|
||||
else if (response.error && response.errorCode) {
|
||||
keepass.handleError(tab, response.errorCode, response.error);
|
||||
callback('error');
|
||||
}
|
||||
else {
|
||||
browserAction.showDefault(null, tab);
|
||||
|
|
@ -839,6 +840,17 @@ keepass.onNativeMessage = function(response) {
|
|||
keepass.testAssociation((associationResponse) => {
|
||||
keepass.isConfigured().then((configured) => {
|
||||
keepass.updatePopup(configured ? 'normal' : 'cross');
|
||||
|
||||
// Send message to content script
|
||||
browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => {
|
||||
if (tabs.length) {
|
||||
browser.tabs.sendMessage(tabs[0].id, {
|
||||
action: 'check_database_hash',
|
||||
hash: {old: keepass.previousDatabaseHash, new: keepass.databaseHash}
|
||||
});
|
||||
keepass.previousDatabaseHash = keepass.databaseHash;
|
||||
}
|
||||
});
|
||||
});
|
||||
}, null);
|
||||
}
|
||||
|
|
@ -1041,7 +1053,9 @@ keepass.reconnect = function(callback, tab) {
|
|||
};
|
||||
|
||||
keepass.updatePopup = function(iconType) {
|
||||
const data = page.tabs[page.currentTabId].stack[page.tabs[page.currentTabId].stack.length - 1];
|
||||
data.iconType = iconType;
|
||||
browserAction.show(null, {'id': page.currentTabId});
|
||||
if (page && page.tabs.length > 0) {
|
||||
const data = page.tabs[page.currentTabId].stack[page.tabs[page.currentTabId].stack.length - 1];
|
||||
data.iconType = iconType;
|
||||
browserAction.show(null, {'id': page.currentTabId});
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ var page = {};
|
|||
page.tabs = [];
|
||||
page.currentTabId = -1;
|
||||
page.blockedTabs = [];
|
||||
page.loginId = -1;
|
||||
|
||||
page.initSettings = function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
|
@ -104,6 +105,10 @@ page.clearCredentials = function(tabId, complete) {
|
|||
};
|
||||
|
||||
page.clearLogins = function(tabId) {
|
||||
if (!page.tabs[tabId]) {
|
||||
return;
|
||||
}
|
||||
|
||||
page.tabs[tabId].loginList = [];
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
'use strict';
|
||||
|
||||
const IGNORE_NOTHING = 'ignoreNothing';
|
||||
const IGNORE_NORMAL = 'ignoreNormal';
|
||||
const IGNORE_FULL = 'ignoreFull';
|
||||
|
||||
var schemeSegment = '(\\*|http|https|ws|wss|file|ftp)';
|
||||
var hostSegment = '(\\*|(?:\\*\\.)?(?:[^/*]+))?';
|
||||
var pathSegment = '(.*)';
|
||||
|
||||
var isFirefox = function() {
|
||||
if (!(/Chrome/.test(navigator.userAgent) && /Google/.test(navigator.vendor))) {
|
||||
return true;
|
||||
|
|
@ -15,3 +23,71 @@ var showNotification = function(message) {
|
|||
'message': message
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Transforms a valid match pattern into a regular expression
|
||||
* which matches all URLs included by that pattern.
|
||||
*
|
||||
* @param {string} pattern The pattern to transform.
|
||||
* @return {RegExp} The pattern's equivalent as a RegExp.
|
||||
* @throws {TypeError} If the pattern is not a valid MatchPattern
|
||||
*
|
||||
* https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Match_patterns
|
||||
*/
|
||||
var matchPatternToRegExp = function(pattern) {
|
||||
if (pattern === '') {
|
||||
return (/^(?:http|https|file|ftp|app):\/\//);
|
||||
}
|
||||
|
||||
const matchPatternRegExp = new RegExp(
|
||||
`^${schemeSegment}://${hostSegment}/${pathSegment}$`
|
||||
);
|
||||
|
||||
let match = matchPatternRegExp.exec(pattern);
|
||||
if (!match) {
|
||||
throw new TypeError(pattern + ' is not a valid MatchPattern');
|
||||
}
|
||||
|
||||
let [, scheme, host, path] = match;
|
||||
if (!host) {
|
||||
throw new TypeError(pattern + ' does not have a valid host');
|
||||
}
|
||||
|
||||
let regex = '^';
|
||||
|
||||
if (scheme === '*') {
|
||||
regex += '(http|https)';
|
||||
} else {
|
||||
regex += scheme;
|
||||
}
|
||||
|
||||
regex += '://';
|
||||
|
||||
if (host && host === '*') {
|
||||
regex += '[^/]+?';
|
||||
} else if (host) {
|
||||
if (host.match(/^\*\./)) {
|
||||
regex += '[^/]*?';
|
||||
host = host.substring(2);
|
||||
}
|
||||
regex += host.replace(/\./g, '\\.');
|
||||
}
|
||||
|
||||
if (path) {
|
||||
if (path === '*') {
|
||||
regex += '(/.*)?';
|
||||
} else if (path.charAt(0) !== '/') {
|
||||
regex += '/';
|
||||
regex += path.replace(/\./g, '\\.').replace(/\*/g, '.*?');
|
||||
regex += '/?';
|
||||
}
|
||||
}
|
||||
|
||||
regex += '$';
|
||||
return new RegExp(regex);
|
||||
};
|
||||
|
||||
var siteMatch = function(site, url) {
|
||||
const rx = matchPatternToRegExp(site);
|
||||
return url.match(rx);
|
||||
};
|
||||
|
|
@ -6,6 +6,8 @@ _called.retrieveCredentials = false;
|
|||
_called.clearLogins = false;
|
||||
_called.manualFillRequested = 'none';
|
||||
let _loginId = -1;
|
||||
let _singleInputEnabledForPage = false;
|
||||
const _maximumInputs = 100;
|
||||
|
||||
// Count of detected form fields on the page
|
||||
var _detectedFields = 0;
|
||||
|
|
@ -21,12 +23,16 @@ browser.runtime.onMessage.addListener(function(req, sender, callback) {
|
|||
if (cip.u) {
|
||||
cip.setValueWithChange(cip.u, cip.credentials[req.id].login);
|
||||
combination = cipFields.getCombination('username', cip.u);
|
||||
_loginId = req.id;
|
||||
browser.runtime.sendMessage({
|
||||
action: 'page_set_login_id', args: [req.id]
|
||||
});
|
||||
cip.u.focus();
|
||||
}
|
||||
if (cip.p) {
|
||||
cip.setValueWithChange(cip.p, cip.credentials[req.id].password);
|
||||
_loginId = req.id;
|
||||
browser.runtime.sendMessage({
|
||||
action: 'page_set_login_id', args: [req.id]
|
||||
});
|
||||
combination = cipFields.getCombination('password', cip.p);
|
||||
}
|
||||
|
||||
|
|
@ -497,7 +503,7 @@ cipForm.init = function(form, credentialFields) {
|
|||
// TODO: could be called multiple times --> update credentialFields
|
||||
|
||||
// not already initialized && password-field is not null
|
||||
if (!form.data('cipForm-initialized') && (credentialFields.password || credentialFields.username)) {
|
||||
if (!form.data('cipForm-initialized') && (credentialFields.password || (_singleInputEnabledForPage && credentialFields.username))) {
|
||||
form.data('cipForm-initialized', true);
|
||||
cipForm.setInputFields(form, credentialFields);
|
||||
form.submit(cipForm.onSubmit);
|
||||
|
|
@ -614,8 +620,8 @@ cipDefine.initDescription = function() {
|
|||
.addClass('btn').addClass('btn-primary')
|
||||
.css('margin-right', '15px')
|
||||
.click(function(e) {
|
||||
if (!cip.settings['defined-credential-fields']) {
|
||||
cip.settings['defined-credential-fields'] = {};
|
||||
if (!cip.settings['defined-custom-fields']) {
|
||||
cip.settings['defined-custom-fields'] = {};
|
||||
}
|
||||
|
||||
if (cipDefine.selection.username) {
|
||||
|
|
@ -632,7 +638,8 @@ cipDefine.initDescription = function() {
|
|||
fieldIds.push(cipFields.prepareId(i));
|
||||
}
|
||||
|
||||
cip.settings['defined-credential-fields'][document.location.href] = {
|
||||
const location = cip.getDocumentLocation();
|
||||
cip.settings['defined-custom-fields'][location] = {
|
||||
username: cipDefine.selection.username,
|
||||
password: cipDefine.selection.password,
|
||||
fields: fieldIds
|
||||
|
|
@ -652,7 +659,8 @@ cipDefine.initDescription = function() {
|
|||
$description.append($btnAgain);
|
||||
$description.append($btnDismiss);
|
||||
|
||||
if (cip.settings['defined-credential-fields'] && cip.settings['defined-credential-fields'][document.location.href]) {
|
||||
const location = cip.getDocumentLocation();
|
||||
if (cip.settings['defined-custom-fields'] && cip.settings['defined-custom-fields'][location]) {
|
||||
const $p = jQuery('<p>').html('For this page credential fields are already selected and will be overwritten.<br />');
|
||||
const $btnDiscard = jQuery('<button>')
|
||||
.attr('id', 'btn-warning')
|
||||
|
|
@ -662,7 +670,7 @@ cipDefine.initDescription = function() {
|
|||
.addClass('btn-sm')
|
||||
.addClass('btn-danger')
|
||||
.click(function(e) {
|
||||
delete cip.settings['defined-credential-fields'][document.location.href];
|
||||
delete cip.settings['defined-custom-fields'][location];
|
||||
|
||||
browser.runtime.sendMessage({
|
||||
action: 'save_settings',
|
||||
|
|
@ -734,7 +742,7 @@ cipDefine.markFields = function ($chooser, $pattern) {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (jQuery(this).is(':visible') && jQuery(this).css('visibility') !== 'hidden' && jQuery(this).css('visibility') !== 'collapsed') {
|
||||
if (cipFields.isVisible(this)) {
|
||||
const $field = jQuery('<div>').addClass('b2c-fixed-field')
|
||||
.css('top', jQuery(this).offset().top)
|
||||
.css('left', jQuery(this).offset().left)
|
||||
|
|
@ -812,61 +820,117 @@ cipFields.prepareId = function(id) {
|
|||
return id.replace(/[:#.,\[\]\(\)' "]/g, function(m) { return '\\'+m; });
|
||||
};
|
||||
|
||||
// Check aria-hidden attribute by looping the parent elements of input field
|
||||
cipFields.getAriaHidden = function(field) {
|
||||
let $par = jQuery(field).parents();
|
||||
for (const p of $par) {
|
||||
const val = $(p).attr('aria-hidden');
|
||||
if (val) {
|
||||
return val;
|
||||
/**
|
||||
* Returns the first parent element satifying the {@code predicate} mapped by {@code resultFn} or else {@code defaultVal}.
|
||||
* @param {HTMLElement} element The start element (excluded, starting with the parents)
|
||||
* @param {function} predicate Matcher for the element to find, type (HTMLElement) => boolean
|
||||
* @param {function} resultFn Callback function of type (HTMLElement) => {*} called for the first matching element
|
||||
* @param {fun} defaultValFn Fallback return value supplier, if no element matching the predicate can be found
|
||||
*/
|
||||
cipFields.traverseParents = function(element, predicate, resultFn = () => true, defaultValFn = () => false) {
|
||||
for (let f = element.parentElement; f !== null; f = f.parentElement) {
|
||||
if (predicate(f)) {
|
||||
return resultFn(f);
|
||||
}
|
||||
}
|
||||
return 'false';
|
||||
return defaultValFn();
|
||||
};
|
||||
|
||||
cipFields.getAriaHidden = function(field) {
|
||||
// Check the main element
|
||||
const val = field.getAttribute('aria-hidden');
|
||||
if (val) {
|
||||
return val;
|
||||
}
|
||||
|
||||
const ariaFunc = f => f.getAttribute('aria-hidden');
|
||||
return cipFields.traverseParents(field, ariaFunc, ariaFunc, () => 'false');
|
||||
};
|
||||
|
||||
cipFields.getOverflowHidden = function(field) {
|
||||
let $par = jQuery(field).parents();
|
||||
for (const p of $par) {
|
||||
const val = $(p).css('overflow');
|
||||
if (val === 'hidden') {
|
||||
return cipFields.traverseParents(field, f => f.style.overflow === 'hidden');
|
||||
};
|
||||
|
||||
|
||||
// Checks if input field is a search field. Attributes or form action containing 'search', or parent element holding
|
||||
// role="search" will be identified as a search field.
|
||||
cipFields.isSearchField = function(target) {
|
||||
const attributes = target.attributes;
|
||||
|
||||
// Check element attributes
|
||||
for (const attr of attributes) {
|
||||
if ((attr.value && (attr.value.toLowerCase().includes('search')) || attr.value === 'q')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check closest form
|
||||
const closestForm = target.closest('form');
|
||||
if (closestForm) {
|
||||
// Check form action
|
||||
const formAction = closestForm.getAttribute('action');
|
||||
if (formAction && formAction.includes('search')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check form class and id
|
||||
const closestFormId = closestForm.getAttribute('id');
|
||||
const closestFormClass = closestForm.className;
|
||||
if (closestFormClass && (closestForm.className.toLowerCase().includes('search') ||
|
||||
(closestFormId && closestFormId.toLowerCase().includes('search')))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check parent elements for role="search"
|
||||
const roleFunc = f => f.getAttribute('role');
|
||||
const roleValue = cipFields.traverseParents(target, roleFunc, roleFunc, () => null);
|
||||
if (roleValue && roleValue === 'search') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
cipFields.isVisible = function(field) {
|
||||
const rect = field.getBoundingClientRect();
|
||||
|
||||
// Check CSS visibility
|
||||
const fieldStyle = getComputedStyle(field);
|
||||
if (fieldStyle.visibility && (fieldStyle.visibility === 'hidden' || fieldStyle.visibility === 'collapse')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check element position and size
|
||||
if (rect.x < 0 || rect.y < 0 || rect.width < 16 || rect.height < 16) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check aria-hidden property
|
||||
if (cipFields.getAriaHidden(field) !== 'false') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
cipFields.getAllFields = function() {
|
||||
let fields = [];
|
||||
|
||||
// get all input fields which are text, email or password and visible
|
||||
jQuery(cipFields.inputQueryPattern).each(function() {
|
||||
let ariaHidden = cipFields.getAriaHidden(this);
|
||||
let overflowHidden = cipFields.getOverflowHidden(this);
|
||||
|
||||
if (jQuery(this).is(':visible') && jQuery(this).css('visibility') !== 'hidden' && jQuery(this).css('visibility') !== 'collapsed' && ariaHidden === 'false') {
|
||||
cipFields.setUniqueId(jQuery(this));
|
||||
fields.push(jQuery(this));
|
||||
const inputs = cipObserverHelper.getInputs(document);
|
||||
for (const i of inputs) {
|
||||
if (cipFields.isVisible(i) && !cipFields.isSearchField(i)) {
|
||||
cipFields.setUniqueId(jQuery(i));
|
||||
fields.push(jQuery(i));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
_detectedFields = fields.length;
|
||||
return fields;
|
||||
};
|
||||
|
||||
cipFields.getHiddenFieldCount = function() {
|
||||
let count = 0;
|
||||
jQuery(cipFields.inputQueryPattern).each(function() {
|
||||
if (jQuery(this).is(':hidden')) {
|
||||
count++;
|
||||
}
|
||||
});
|
||||
|
||||
return count;
|
||||
};
|
||||
|
||||
cipFields.prepareVisibleFieldsWithID = function($pattern) {
|
||||
jQuery($pattern).each(function() {
|
||||
if (jQuery(this).is(':visible') && jQuery(this).css('visibility') !== 'hidden' && jQuery(this).css('visibility') !== 'collapsed') {
|
||||
if (cipFields.isVisible(this) && !cipFields.isSearchField(this)) {
|
||||
cipFields.setUniqueId(jQuery(this));
|
||||
}
|
||||
});
|
||||
|
|
@ -897,6 +961,14 @@ cipFields.getAllCombinations = function(inputs) {
|
|||
}
|
||||
}
|
||||
|
||||
if (_singleInputEnabledForPage && fields.length === 0 && uField) {
|
||||
const combination = {
|
||||
username: uField[0].getAttribute('data-cip-id'),
|
||||
password: null
|
||||
};
|
||||
fields.push(combination);
|
||||
}
|
||||
|
||||
return fields;
|
||||
};
|
||||
|
||||
|
|
@ -907,7 +979,8 @@ cipFields.getCombination = function(givenType, fieldId) {
|
|||
}
|
||||
}
|
||||
// use defined credential fields (already loaded into combinations)
|
||||
if (cip.settings['defined-credential-fields'] && cip.settings['defined-credential-fields'][document.location.href]) {
|
||||
const location = cip.getDocumentLocation();
|
||||
if (cip.settings['defined-custom-fields'] && cip.settings['defined-custom-fields'][location]) {
|
||||
return cipFields.combinations[0];
|
||||
}
|
||||
|
||||
|
|
@ -1107,8 +1180,9 @@ cipFields.prepareCombinations = function(combinations) {
|
|||
};
|
||||
|
||||
cipFields.useDefinedCredentialFields = function() {
|
||||
if (cip.settings['defined-credential-fields'] && cip.settings['defined-credential-fields'][document.location.href]) {
|
||||
const creds = cip.settings['defined-credential-fields'][document.location.href];
|
||||
const location = cip.getDocumentLocation();
|
||||
if (cip.settings['defined-custom-fields'] && cip.settings['defined-custom-fields'][location]) {
|
||||
const creds = cip.settings['defined-custom-fields'][location];
|
||||
|
||||
let $found = _f(creds.username) || _f(creds.password);
|
||||
for (const i of creds.fields) {
|
||||
|
|
@ -1134,6 +1208,116 @@ cipFields.useDefinedCredentialFields = function() {
|
|||
return false;
|
||||
};
|
||||
|
||||
|
||||
var cipObserverHelper = {};
|
||||
cipObserverHelper.inputTypes = [
|
||||
'text',
|
||||
'email',
|
||||
'password',
|
||||
'tel',
|
||||
'number',
|
||||
null // Input field can be without any type. Include these to the list.
|
||||
];
|
||||
|
||||
// Ignores all nodes that doesn't contain elements
|
||||
cipObserverHelper.ignoredNode = function(target) {
|
||||
if (target.nodeType === Node.ATTRIBUTE_NODE ||
|
||||
target.nodeType === Node.TEXT_NODE ||
|
||||
target.nodeType === Node.CDATA_SECTION_NODE ||
|
||||
target.nodeType === Node.PROCESSING_INSTRUCTION_NODE ||
|
||||
target.nodeType === Node.COMMENT_NODE ||
|
||||
target.nodeType === Node.DOCUMENT_TYPE_NODE ||
|
||||
target.nodeType === Node.NOTATION_NODE) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
cipObserverHelper.getInputs = function(target) {
|
||||
if (cipObserverHelper.ignoredNode(target)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const input = target.getElementsByTagName('input');
|
||||
if (input.length === 0 || input.length > _maximumInputs) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let inputs = [];
|
||||
for (const i of input) {
|
||||
if (cipObserverHelper.inputTypes.includes(i.getAttribute('type'))) {
|
||||
inputs.push(i);
|
||||
}
|
||||
}
|
||||
return inputs;
|
||||
};
|
||||
|
||||
cipObserverHelper.getId = function(target) {
|
||||
return target.classList.length === 0 ? target.id : target.classList;
|
||||
};
|
||||
|
||||
cipObserverHelper.ignoredElement = function(target) {
|
||||
// Ignore SVG elements
|
||||
if (target.nodeName === 'svg' ||
|
||||
target.nodeName === 'g' ||
|
||||
(target.parentNode &&
|
||||
(target.parentNode.nodeName === 'svg' || target.parentNode.nodeName === 'g'))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ignore KeePassXC-Browser classes
|
||||
if (target.className && (target.className.includes('kpxc') || target.className.includes('ui-helper'))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
cipObserverHelper.handleObserverAdd = function(target) {
|
||||
if (cipObserverHelper.ignoredElement(target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const inputs = cipObserverHelper.getInputs(target);
|
||||
if (inputs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const neededLength = _detectedFields === 1 ? 0 : 1;
|
||||
const id = cipObserverHelper.getId(target);
|
||||
if (inputs.length > neededLength && !_observerIds.includes(id)) {
|
||||
// Save target element id for preventing multiple calls to initCredentialsFields()
|
||||
_observerIds.push(id);
|
||||
|
||||
// Sometimes the settings haven't been loaded before new input fields are detected
|
||||
if (Object.keys(cip.settings).length === 0) {
|
||||
cip.init();
|
||||
} else {
|
||||
cip.initCredentialFields(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
cipObserverHelper.handleObserverRemove = function(target) {
|
||||
if (cipObserverHelper.ignoredElement(target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const inputs = cipObserverHelper.getInputs(target);
|
||||
if (inputs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove target element id from the list
|
||||
const id = cipObserverHelper.getId(target);
|
||||
if (_observerIds.includes(id)) {
|
||||
const index = _observerIds.indexOf(id);
|
||||
if (index >= 0) {
|
||||
_observerIds.splice(index, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
|
||||
|
||||
// Detects DOM changes in the document
|
||||
|
|
@ -1143,15 +1327,24 @@ let observer = new MutationObserver(function(mutations, observer) {
|
|||
}
|
||||
|
||||
for (const mut of mutations) {
|
||||
// Check if the added element has any inputs
|
||||
const inputs = mut.target.querySelectorAll(cipFields.inputQueryPattern);
|
||||
// Skip text nodes
|
||||
if (mut.target.nodeType === Node.TEXT_NODE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If only password field is shown it's enough to have one field visible for initCredentialFields
|
||||
const neededLength = _detectedFields === 1 ? 0 : 1;
|
||||
if (inputs.length > neededLength && !_observerIds.includes(mut.target.id)) {
|
||||
// Save target element id for preventing multiple calls to initCredentialsFields()
|
||||
_observerIds.push(mut.target.id);
|
||||
cip.initCredentialFields(true);
|
||||
// Handle attributes only if CSS display is modified
|
||||
if (mut.type === 'attributes') {
|
||||
const newValue = mut.target.getAttribute(mut.attributeName);
|
||||
if (newValue && (newValue.includes('display') || newValue.includes('z-index'))) {
|
||||
if (mut.target.style.display !== 'none') {
|
||||
cipObserverHelper.handleObserverAdd(mut.target);
|
||||
} else {
|
||||
cipObserverHelper.handleObserverRemove(mut.target);
|
||||
}
|
||||
}
|
||||
} else if (mut.type === 'childList') {
|
||||
cipObserverHelper.handleObserverAdd((mut.addedNodes.length > 0) ? mut.addedNodes[0] : mut.target);
|
||||
cipObserverHelper.handleObserverRemove((mut.removedNodes.length > 0) ? mut.removedNodes[0] : mut.target);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -1162,9 +1355,11 @@ observer.observe(document, {
|
|||
subtree: true,
|
||||
attributes: true,
|
||||
childList: true,
|
||||
characterData: true
|
||||
characterData: true,
|
||||
attributeFilter: ['style']
|
||||
});
|
||||
|
||||
|
||||
var cip = {};
|
||||
cip.settings = {};
|
||||
cip.u = null;
|
||||
|
|
@ -1202,6 +1397,7 @@ cip.detectDatabaseChange = function(response) {
|
|||
args: [ true ] // Set polling to true, this is an internal function call
|
||||
});
|
||||
} else if (response.new !== 'no-hash' && response.new !== response.old) {
|
||||
_called.retrieveCredentials = false;
|
||||
browser.runtime.sendMessage({
|
||||
action: 'load_settings',
|
||||
}).then((response) => {
|
||||
|
|
@ -1227,6 +1423,21 @@ cip.initCredentialFields = function(forceCall) {
|
|||
|
||||
browser.runtime.sendMessage({ 'action': 'page_clear_logins', args: [_called.clearLogins] }).then(() => {
|
||||
_called.clearLogins = true;
|
||||
|
||||
// Check site preferences
|
||||
cip.initializeSitePreferences();
|
||||
if (cip.settings.sitePreferences) {
|
||||
for (const site of cip.settings.sitePreferences) {
|
||||
if (site.url === document.location.href || siteMatch(site.url, document.location.href)) {
|
||||
if (site.ignore === IGNORE_FULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
_singleInputEnabledForPage = site.usernameOnly;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const inputs = cipFields.getAllFields();
|
||||
if (inputs.length === 0) {
|
||||
return;
|
||||
|
|
@ -1252,18 +1463,19 @@ cip.initCredentialFields = function(forceCall) {
|
|||
cip.submitUrl = cip.getFormActionUrl(cipFields.combinations[0]);
|
||||
|
||||
// Get submitUrl for a single input
|
||||
if (!cip.submitUrl && cipFields.combinations.length === 1 && inputs.length === 1) {
|
||||
if (_singleInputEnabledForPage && !cip.submitUrl && cipFields.combinations.length === 1 && inputs.length === 1) {
|
||||
cip.submitUrl = cip.getFormActionUrlFromSingleInput(inputs[0]);
|
||||
}
|
||||
}
|
||||
|
||||
if (cip.settings.autoRetrieveCredentials && _called.retrieveCredentials === false && (cip.url && cip.submitUrl)) {
|
||||
_called.retrieveCredentials = true;
|
||||
browser.runtime.sendMessage({
|
||||
action: 'retrieve_credentials',
|
||||
args: [ cip.url, cip.submitUrl ]
|
||||
}).then(cip.retrieveCredentialsCallback).catch((e) => {
|
||||
console.log(e);
|
||||
});
|
||||
} else if (_singleInputEnabledForPage) {
|
||||
cip.preparePageForMultipleCredentials(cip.credentials);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
@ -1510,23 +1722,26 @@ cip.fillInFromActiveElementTOTPOnly = function(suppressWarnings) {
|
|||
const el = document.activeElement;
|
||||
cipFields.setUniqueId(jQuery(el));
|
||||
const fieldId = cipFields.prepareId(jQuery(el).attr('data-cip-id'));
|
||||
const pos = _loginId;
|
||||
|
||||
if (pos >= 0 && cip.credentials[pos]) {
|
||||
// Check the value from stringFields (to be removed)
|
||||
const $sf = _fs(fieldId);
|
||||
if (cip.credentials[pos].stringFields && cip.credentials[pos].stringFields.length > 0) {
|
||||
const sFields = cip.credentials[pos].stringFields;
|
||||
for (const s of sFields) {
|
||||
const val = s["KPH: {TOTP}"];
|
||||
if (val) {
|
||||
cip.setValue($sf, val);
|
||||
browser.runtime.sendMessage({
|
||||
action: 'page_get_login_id'
|
||||
}).then((pos) => {
|
||||
if (pos >= 0 && cip.credentials[pos]) {
|
||||
// Check the value from stringFields (to be removed)
|
||||
const currentField = _fs(fieldId);
|
||||
if (cip.credentials[pos].stringFields && cip.credentials[pos].stringFields.length > 0) {
|
||||
const stringFields = cip.credentials[pos].stringFields;
|
||||
for (const s of stringFields) {
|
||||
const val = s["KPH: {TOTP}"];
|
||||
if (val) {
|
||||
cip.setValue(currentField, val);
|
||||
}
|
||||
}
|
||||
} else if (cip.credentials[pos].totp && cip.credentials[pos].totp.length > 0) {
|
||||
cip.setValue(currentField, cip.credentials[pos].totp);
|
||||
}
|
||||
} else if (cip.credentials[pos].totp && cip.credentials[pos].totp.length > 0) {
|
||||
cip.setValue($sf, cip.credentials[pos].totp);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
cip.setValue = function(field, value) {
|
||||
|
|
@ -1546,22 +1761,22 @@ cip.setValue = function(field, value) {
|
|||
};
|
||||
|
||||
cip.fillInStringFields = function(fields, stringFields, filledInFields) {
|
||||
let $filledIn = false;
|
||||
let filledIn = false;
|
||||
|
||||
filledInFields.list = [];
|
||||
if (fields && stringFields && fields.length > 0 && stringFields.length > 0) {
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
const $sf = _fs(fields[i]);
|
||||
const currentField = _fs(fields[i]);
|
||||
const stringFieldValue = Object.values(stringFields[i]);
|
||||
if ($sf && stringFieldValue[0]) {
|
||||
cip.setValue($sf, stringFieldValue[0]);
|
||||
if (currentField && stringFieldValue[0]) {
|
||||
cip.setValue(currentField, stringFieldValue[0]);
|
||||
filledInFields.list.push(fields[i]);
|
||||
$filledIn = true;
|
||||
filledIn = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $filledIn;
|
||||
return filledIn;
|
||||
};
|
||||
|
||||
cip.setValueWithChange = function(field, value) {
|
||||
|
|
@ -1596,14 +1811,18 @@ cip.fillIn = function(combination, onlyPassword, suppressWarnings) {
|
|||
let filledIn = false;
|
||||
if (uField && !onlyPassword) {
|
||||
cip.setValueWithChange(uField, cip.credentials[0].login);
|
||||
_loginId = 0;
|
||||
browser.runtime.sendMessage({
|
||||
action: 'page_set_login_id', args: [0]
|
||||
});
|
||||
filledIn = true;
|
||||
}
|
||||
if (pField) {
|
||||
pField.attr('type', 'password');
|
||||
cip.setValueWithChange(pField, cip.credentials[0].password);
|
||||
pField.data('unchanged', true);
|
||||
_loginId = 0;
|
||||
browser.runtime.sendMessage({
|
||||
action: 'page_set_login_id', args: [0]
|
||||
});
|
||||
filledIn = true;
|
||||
}
|
||||
|
||||
|
|
@ -1628,14 +1847,18 @@ cip.fillIn = function(combination, onlyPassword, suppressWarnings) {
|
|||
let filledIn = false;
|
||||
if (uField) {
|
||||
cip.setValueWithChange(uField, cip.credentials[combination.loginId].login);
|
||||
_loginId = combination.loginId;
|
||||
browser.runtime.sendMessage({
|
||||
action: 'page_set_login_id', args: [combination.loginId]
|
||||
});
|
||||
filledIn = true;
|
||||
}
|
||||
|
||||
if (pField) {
|
||||
cip.setValueWithChange(pField, cip.credentials[combination.loginId].password);
|
||||
pField.data('unchanged', true);
|
||||
_loginId = combination.loginId;
|
||||
browser.runtime.sendMessage({
|
||||
action: 'page_set_login_id', args: [combination.loginId]
|
||||
});
|
||||
filledIn = true;
|
||||
}
|
||||
|
||||
|
|
@ -1807,7 +2030,7 @@ cip.rememberCredentials = function(usernameValue, passwordValue) {
|
|||
|
||||
let url = jQuery(this)[0].action;
|
||||
if (!url) {
|
||||
url = cip.settings.saveDomainOnly ? document.location.origin : document.location.href;
|
||||
url = cip.getDocumentLocation();
|
||||
if (url.indexOf('?') > 0) {
|
||||
url = url.substring(0, url.indexOf('?'));
|
||||
if (url.length < document.location.origin.length) {
|
||||
|
|
@ -1833,13 +2056,24 @@ cip.ignoreSite = function(sites) {
|
|||
}
|
||||
|
||||
const site = sites[0];
|
||||
if (!cip.settings['ignoredSites']) {
|
||||
cip.settings['ignoredSites'] = {};
|
||||
cip.initializeSitePreferences();
|
||||
|
||||
// Check if the site already exists
|
||||
let siteExists = false;
|
||||
for (const existingSite of cip.settings['sitePreferences']) {
|
||||
if (existingSite.url === site) {
|
||||
existingSite.ignore = IGNORE_NORMAL;
|
||||
siteExists = true;
|
||||
}
|
||||
}
|
||||
|
||||
cip.settings['ignoredSites'][site] = {
|
||||
url: site
|
||||
};
|
||||
if (!siteExists) {
|
||||
cip.settings['sitePreferences'].push({
|
||||
url: site,
|
||||
ignore: IGNORE_NORMAL,
|
||||
usernameOnly: false
|
||||
});
|
||||
}
|
||||
|
||||
browser.runtime.sendMessage({
|
||||
action: 'save_settings',
|
||||
|
|
@ -1847,6 +2081,20 @@ cip.ignoreSite = function(sites) {
|
|||
});
|
||||
};
|
||||
|
||||
// Delete previously created Object if it exists. It will be replaced by an Array
|
||||
cip.initializeSitePreferences = function() {
|
||||
if (cip.settings['sitePreferences'] !== undefined && cip.settings['sitePreferences'].constructor === Object) {
|
||||
delete cip.settings['sitePreferences'];
|
||||
}
|
||||
|
||||
if (!cip.settings['sitePreferences']) {
|
||||
cip.settings['sitePreferences'] = [];
|
||||
}
|
||||
};
|
||||
|
||||
cip.getDocumentLocation = function() {
|
||||
return cip.settings.saveDomainOnly ? document.location.origin : document.location.href;
|
||||
};
|
||||
|
||||
var cipEvents = {};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
{
|
||||
"manifest_version": 2,
|
||||
"name": "KeePassXC-Browser",
|
||||
"version": "1.1.3",
|
||||
"version_name": "1.1.3",
|
||||
"version": "1.2.0",
|
||||
"description": "KeePassXC integration for modern web browsers",
|
||||
"author": "KeePassXC Team",
|
||||
"icons": {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@ body {
|
|||
padding-bottom: 60px;
|
||||
}
|
||||
|
||||
table td {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Custom container */
|
||||
.container {
|
||||
margin: 0 auto;
|
||||
|
|
@ -111,6 +115,10 @@ h2+hr {
|
|||
margin-right: 5px;
|
||||
}
|
||||
|
||||
#manualUrl {
|
||||
width: 75%;
|
||||
}
|
||||
|
||||
tr.clone {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,15 +19,15 @@
|
|||
<ul class="nav navbar-nav">
|
||||
<li class="active"><a href="#general-settings">General</a></li>
|
||||
<li><a href="#connected-databases">Connected Databases</a></li>
|
||||
<li><a href="#specified-fields">Specified credential fields</a></li>
|
||||
<li><a href="#ignored-sites">Ignored sites</a></li>
|
||||
<li><a href="#custom-fields">Custom credential fields</a></li>
|
||||
<li><a href="#site-preferences">Site preferences</a></li>
|
||||
<li><a href="#about">About</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<!-- General Settings -->
|
||||
<div class="tab" id="tab-general-settings">
|
||||
<h2>General Settings</h2>
|
||||
<h2>General settings</h2>
|
||||
<hr />
|
||||
<p>
|
||||
If you just want to insert username + password into the fields where your focus is, press <code><span id="mac-user-shortcut">Ctrl + Shift + U</span><span id="default-user-shortcut">Alt + Shift + U</span></code>.
|
||||
|
|
@ -40,14 +40,14 @@
|
|||
<label for="blinkTimeout">Blink Time:</label>
|
||||
<div class="control-group">
|
||||
<div class="input-append">
|
||||
<input type="number" id="blinkTimeout" placeholder="7500" value="7500" min="0"/>
|
||||
<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> Save</button>
|
||||
</div>
|
||||
</div>
|
||||
<span class="help-inline">
|
||||
What is the maximum time (ms) the icon should blink after detecting new credentials
|
||||
<br />
|
||||
Default: 7500
|
||||
Default: 7500, Infinite: -1
|
||||
</span>
|
||||
</div>
|
||||
</p>
|
||||
|
|
@ -189,7 +189,7 @@
|
|||
|
||||
<!-- Connected Databases -->
|
||||
<div class="tab" id="tab-connected-databases">
|
||||
<h2>Connected Databases</h2>
|
||||
<h2>Connected databases</h2>
|
||||
<hr />
|
||||
<p>
|
||||
The following KeePassXC databases are connected to KeePassXC-Browser.
|
||||
|
|
@ -241,16 +241,16 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Specified credential fields -->
|
||||
<div class="tab" id="tab-specified-fields">
|
||||
<h2>Specified credential fields</h2>
|
||||
<!-- Custom credential fields -->
|
||||
<div class="tab" id="tab-custom-fields">
|
||||
<h2>Custom credential fields</h2>
|
||||
<hr />
|
||||
<p>
|
||||
If KeePassXC-Browser detects the wrong credential fields, you are able to specify the correct fields by yourself.
|
||||
<br />
|
||||
Just go to the page and click on the KeePassXC-Browser-Icon, now select <em>Choose own credential fields for this page</em>.
|
||||
Go to the page and click on the KeePassXC-Browser-Icon, now select <em>Choose custom credential fields for this page</em>.
|
||||
<br />
|
||||
On this page you can manage theses specified credential fields.
|
||||
On this page you can manage saved custom credential fields.
|
||||
</p>
|
||||
<table class="table table-striped table-bordered table-hover">
|
||||
<thead>
|
||||
|
|
@ -261,7 +261,7 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
<tr class="empty">
|
||||
<td colspan="2">No specified credential fields found.</td>
|
||||
<td colspan="2">No saved custom credential fields found.</td>
|
||||
</tr>
|
||||
<tr class="clone">
|
||||
<td></td>
|
||||
|
|
@ -269,16 +269,16 @@
|
|||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div id="dialogDeleteSpecifiedCredentialFields" class="modal fade" tabindex="-1" role="dialog">
|
||||
<div id="dialogDeleteCustomCredentialFields" class="modal fade" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h3 id="myModalLabel">Remove specified credential fields?</h3>
|
||||
<h3 id="myModalLabel">Remove saved custom credential fields?</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Do you really want to remove the specified credential fields on the page <strong></strong>?</p>
|
||||
<p class="help-block">KeePassXC-Browser will automatically detect the credential fields the next time you visit this page.</p>
|
||||
<p>DDo you really want to remove the saved custom credential fields on the page: <strong></strong>?</p>
|
||||
<p class="help-block">KeePassXC-Browser will automatically detect the credential fields next time you visit this page.</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn" data-dismiss="modal" aria-hidden="true">Cancel</button>
|
||||
|
|
@ -289,33 +289,58 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ignored sites -->
|
||||
<div class="tab" id="tab-ignored-sites">
|
||||
<h2>Ignored sites</h2>
|
||||
<!-- Site preferences -->
|
||||
<div class="tab" id="tab-site-preferences">
|
||||
<h2>Site preferences</h2>
|
||||
<hr />
|
||||
<p>
|
||||
Sites in this list are ignored when new credentials are detected.
|
||||
Sites on this page have special handling methods associated with them.
|
||||
<br />
|
||||
Go to the page with new credentials, click the blinking KeePassXC-Browser icon or the notification and select <em>Never ask for this page</em>.
|
||||
To ignore new/modified credentials on a specific site, add it below or click the blinking KeePassXC-Browser icon and select <em>Never ask for this page</em>.
|
||||
<br />
|
||||
If a site is fully ignored (<em>Disable all features</em> is selected), then the plugin will do nothing when visiting that site.
|
||||
<br />
|
||||
Enabling the <em>Username-Only Detection</em> feature allows KeePassXC-Browser to fill-in pages on sites that do present a separated username and password input.
|
||||
</p>
|
||||
<hr />
|
||||
<div class="form-group">
|
||||
<label for="sitePreferencesManualAdd">Add URL manually:</label>
|
||||
<div class="control-group">
|
||||
<div class="input-append">
|
||||
<input type="url" id="manualUrl"/>
|
||||
<button class="btn btn-sm btn-primary" id="sitePreferencesManualAdd" type="button"><span class="glyphicon glyphicon-plus-sign"></span> Add</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<table class="table table-striped table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Page URL</th>
|
||||
<th>Ignore</th>
|
||||
<th>Username-Only Detection</th>
|
||||
<th>Delete</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="empty">
|
||||
<td colspan="2">No ignored sites found.</td>
|
||||
<td colspan="4">No sites found.</td>
|
||||
</tr>
|
||||
<tr class="clone">
|
||||
<td></td>
|
||||
<td>
|
||||
<select name="ignore">
|
||||
<option value="ignoreNothing">Enable all features</option>
|
||||
<option value="ignoreNormal">Disable new/modified credentials</option>
|
||||
<option value="ignoreFull">Disable all features</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="checkbox" name="usernameOnly" value="false" /></td>
|
||||
<td><button class="btn delete btn-danger btn"><span class="glyphicon glyphicon-remove-sign"></span> Remove</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div id="dialogDeleteIgnoredSite" class="modal fade" tabindex="-1" role="dialog">
|
||||
<div id="dialogDeleteSite" class="modal fade" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
|
|
@ -323,8 +348,8 @@
|
|||
<h3 id="myModalLabel">Remove site?</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Do you really want to remove the specified site from the ignore list?</p>
|
||||
<p class="help-block">KeePassXC-Browser will detect new credentials the next time you visit this page.</p>
|
||||
<p>Do you really want to remove the specified site from the list?</p>
|
||||
<p class="help-block">KeePassXC-Browser will enable all features for this site and remove username-only detection.</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn" data-dismiss="modal" aria-hidden="true">Cancel</button>
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ $(function() {
|
|||
options.initMenu();
|
||||
options.initGeneralSettings();
|
||||
options.initConnectedDatabases();
|
||||
options.initSpecifiedCredentialFields();
|
||||
options.initIgnoredSites();
|
||||
options.initCustomCredentialFields();
|
||||
options.initSitePreferences();
|
||||
options.initAbout();
|
||||
});
|
||||
});
|
||||
|
|
@ -52,9 +52,9 @@ options.saveSettingsPromise = function() {
|
|||
}
|
||||
|
||||
options.saveSetting = function(name) {
|
||||
const $id = '#' + name;
|
||||
$($id).closest('.control-group').removeClass('error').addClass('success');
|
||||
setTimeout(() => { $($id).closest('.control-group').removeClass('success'); }, 2500);
|
||||
const id = '#' + name;
|
||||
$(id).closest('.control-group').removeClass('error').addClass('success');
|
||||
setTimeout(() => { $(id).closest('.control-group').removeClass('success'); }, 2500);
|
||||
|
||||
browser.storage.local.set({'settings': options.settings});
|
||||
browser.runtime.sendMessage({
|
||||
|
|
@ -122,7 +122,7 @@ options.initGeneralSettings = function() {
|
|||
const blinkTimeout = $.trim($('#blinkTimeout').val());
|
||||
const blinkTimeoutval = blinkTimeout !== '' ? Number(blinkTimeout) : defaultSettings.blinkTimeout;
|
||||
|
||||
options.settings['blinkTimeout'] = String(blinkTimeoutval);
|
||||
options.settings['blinkTimeout'] = blinkTimeoutval;
|
||||
options.saveSetting('blinkTimeout');
|
||||
});
|
||||
|
||||
|
|
@ -130,7 +130,7 @@ options.initGeneralSettings = function() {
|
|||
const blinkMinTimeout = $.trim($('#blinkMinTimeout').val());
|
||||
const blinkMinTimeoutval = blinkMinTimeout !== '' ? Number(blinkMinTimeout) : defaultSettings.redirectOffset;
|
||||
|
||||
options.settings['blinkMinTimeout'] = String(blinkMinTimeoutval);
|
||||
options.settings['blinkMinTimeout'] = blinkMinTimeoutval;
|
||||
options.saveSetting('blinkMinTimeout');
|
||||
});
|
||||
|
||||
|
|
@ -138,7 +138,7 @@ options.initGeneralSettings = function() {
|
|||
const allowedRedirect = $.trim($('#allowedRedirect').val());
|
||||
const allowedRedirectval = allowedRedirect !== '' ? Number(allowedRedirect) : defaultSettings.redirectAllowance;
|
||||
|
||||
options.settings['allowedRedirect'] = String(allowedRedirectval);
|
||||
options.settings['allowedRedirect'] = allowedRedirectval;
|
||||
options.saveSetting('allowedRedirect');
|
||||
});
|
||||
};
|
||||
|
|
@ -168,10 +168,10 @@ options.initConnectedDatabases = function() {
|
|||
$('#dialogDeleteConnectedDatabase .modal-footer:first button.yes:first').click(function(e) {
|
||||
$('#dialogDeleteConnectedDatabase').modal('hide');
|
||||
|
||||
const $hash = $('#dialogDeleteConnectedDatabase').data('hash');
|
||||
$('#tab-connected-databases #tr-cd-' + $hash).remove();
|
||||
const hash = $('#dialogDeleteConnectedDatabase').data('hash');
|
||||
$('#tab-connected-databases #tr-cd-' + hash).remove();
|
||||
|
||||
delete options.keyRing[$hash];
|
||||
delete options.keyRing[hash];
|
||||
options.saveKeyRing();
|
||||
|
||||
if ($('#tab-connected-databases table tbody:first tr').length > 2) {
|
||||
|
|
@ -183,22 +183,22 @@ options.initConnectedDatabases = function() {
|
|||
|
||||
$('#tab-connected-databases tr.clone:first .dropdown-menu:first').width('230px');
|
||||
|
||||
const $trClone = $('#tab-connected-databases table tr.clone:first').clone(true);
|
||||
$trClone.removeClass('clone');
|
||||
const trClone = $('#tab-connected-databases table tr.clone:first').clone(true);
|
||||
trClone.removeClass('clone');
|
||||
for (let hash in options.keyRing) {
|
||||
const $tr = $trClone.clone(true);
|
||||
$tr.data('hash', hash);
|
||||
$tr.attr('id', 'tr-cd-' + hash);
|
||||
const tr = trClone.clone(true);
|
||||
tr.data('hash', hash);
|
||||
tr.attr('id', 'tr-cd-' + hash);
|
||||
|
||||
$('a.dropdown-toggle:first img:first', $tr).attr('src', '/icons/19x19/icon_normal_19x19.png');
|
||||
$('a.dropdown-toggle:first img:first', tr).attr('src', '/icons/19x19/icon_normal_19x19.png');
|
||||
|
||||
$tr.children('td:first').text(options.keyRing[hash].id);
|
||||
$tr.children('td:eq(1)').text(options.keyRing[hash].key);
|
||||
tr.children('td:first').text(options.keyRing[hash].id);
|
||||
tr.children('td:eq(1)').text(options.keyRing[hash].key);
|
||||
const lastUsed = (options.keyRing[hash].lastUsed) ? new Date(options.keyRing[hash].lastUsed).toLocaleString() : 'unknown';
|
||||
$tr.children('td:eq(2)').text(lastUsed);
|
||||
tr.children('td:eq(2)').text(lastUsed);
|
||||
const date = (options.keyRing[hash].created) ? new Date(options.keyRing[hash].created).toLocaleDateString() : 'unknown';
|
||||
$tr.children('td:eq(3)').text(date);
|
||||
$('#tab-connected-databases table tbody:first').append($tr);
|
||||
tr.children('td:eq(3)').text(date);
|
||||
$('#tab-connected-databases table tbody:first').append(tr);
|
||||
}
|
||||
|
||||
if ($('#tab-connected-databases table tbody:first tr').length > 2) {
|
||||
|
|
@ -214,97 +214,163 @@ options.initConnectedDatabases = function() {
|
|||
});
|
||||
};
|
||||
|
||||
options.initSpecifiedCredentialFields = function() {
|
||||
$('#dialogDeleteSpecifiedCredentialFields').modal({keyboard: true, show: false, backdrop: true});
|
||||
$('#tab-specified-fields tr.clone:first button.delete:first').click(function(e) {
|
||||
options.initCustomCredentialFields = function() {
|
||||
$('#dialogDeleteCustomCredentialFields').modal({keyboard: true, show: false, backdrop: true});
|
||||
$('#tab-custom-fields tr.clone:first button.delete:first').click(function(e) {
|
||||
e.preventDefault();
|
||||
$('#dialogDeleteSpecifiedCredentialFields').data('url', $(this).closest('tr').data('url'));
|
||||
$('#dialogDeleteSpecifiedCredentialFields').data('tr-id', $(this).closest('tr').attr('id'));
|
||||
$('#dialogDeleteSpecifiedCredentialFields .modal-body:first strong:first').text($(this).closest('tr').children('td:first').text());
|
||||
$('#dialogDeleteSpecifiedCredentialFields').modal('show');
|
||||
$('#dialogDeleteCustomCredentialFields').data('url', $(this).closest('tr').data('url'));
|
||||
$('#dialogDeleteCustomCredentialFields').data('tr-id', $(this).closest('tr').attr('id'));
|
||||
$('#dialogDeleteCustomCredentialFields .modal-body:first strong:first').text($(this).closest('tr').children('td:first').text());
|
||||
$('#dialogDeleteCustomCredentialFields').modal('show');
|
||||
});
|
||||
|
||||
$('#dialogDeleteSpecifiedCredentialFields .modal-footer:first button.yes:first').click(function(e) {
|
||||
$('#dialogDeleteSpecifiedCredentialFields').modal('hide');
|
||||
$('#dialogDeleteCustomCredentialFields .modal-footer:first button.yes:first').click(function(e) {
|
||||
$('#dialogDeleteCustomCredentialFields').modal('hide');
|
||||
|
||||
const $url = $('#dialogDeleteSpecifiedCredentialFields').data('url');
|
||||
const $trId = $('#dialogDeleteSpecifiedCredentialFields').data('tr-id');
|
||||
$('#tab-specified-fields #' + $trId).remove();
|
||||
const url = $('#dialogDeleteCustomCredentialFields').data('url');
|
||||
const trId = $('#dialogDeleteCustomCredentialFields').data('tr-id');
|
||||
$('#tab-custom-fields #' + trId).remove();
|
||||
|
||||
delete options.settings['defined-credential-fields'][$url];
|
||||
delete options.settings['defined-custom-fields'][url];
|
||||
options.saveSettings();
|
||||
|
||||
if ($('#tab-specified-fields table tbody:first tr').length > 2) {
|
||||
$('#tab-specified-fields table tbody:first tr.empty:first').hide();
|
||||
if ($('#tab-custom-fields table tbody:first tr').length > 2) {
|
||||
$('#tab-custom-fields table tbody:first tr.empty:first').hide();
|
||||
} else {
|
||||
$('#tab-specified-fields table tbody:first tr.empty:first').show();
|
||||
$('#tab-custom-fields table tbody:first tr.empty:first').show();
|
||||
}
|
||||
});
|
||||
|
||||
const $trClone = $('#tab-specified-fields table tr.clone:first').clone(true);
|
||||
$trClone.removeClass('clone');
|
||||
const trClone = $('#tab-custom-fields table tr.clone:first').clone(true);
|
||||
trClone.removeClass('clone');
|
||||
let counter = 1;
|
||||
for (let url in options.settings['defined-credential-fields']) {
|
||||
const $tr = $trClone.clone(true);
|
||||
$tr.data('url', url);
|
||||
$tr.attr('id', 'tr-scf' + counter);
|
||||
for (let url in options.settings['defined-custom-fields']) {
|
||||
const tr = trClone.clone(true);
|
||||
tr.data('url', url);
|
||||
tr.attr('id', 'tr-scf' + counter);
|
||||
++counter;
|
||||
|
||||
$tr.children('td:first').text(url);
|
||||
$('#tab-specified-fields table tbody:first').append($tr);
|
||||
tr.children('td:first').text(url);
|
||||
$('#tab-custom-fields table tbody:first').append(tr);
|
||||
}
|
||||
|
||||
if ($('#tab-specified-fields table tbody:first tr').length > 2) {
|
||||
$('#tab-specified-fields table tbody:first tr.empty:first').hide();
|
||||
if ($('#tab-custom-fields table tbody:first tr').length > 2) {
|
||||
$('#tab-custom-fields table tbody:first tr.empty:first').hide();
|
||||
} else {
|
||||
$('#tab-specified-fields table tbody:first tr.empty:first').show();
|
||||
$('#tab-custom-fields table tbody:first tr.empty:first').show();
|
||||
}
|
||||
};
|
||||
|
||||
options.initIgnoredSites = function() {
|
||||
$('#dialogDeleteIgnoredSite').modal({keyboard: true, show: false, backdrop: true});
|
||||
$('#tab-ignored-sites tr.clone:first button.delete:first').click(function(e) {
|
||||
options.initSitePreferences = function() {
|
||||
$('#dialogDeleteSite').modal({keyboard: true, show: false, backdrop: true});
|
||||
$('#tab-site-preferences tr.clone:first button.delete:first').click(function(e) {
|
||||
e.preventDefault();
|
||||
$('#dialogDeleteIgnoredSite').data('url', $(this).closest('tr').data('url'));
|
||||
$('#dialogDeleteIgnoredSite').data('tr-id', $(this).closest('tr').attr('id'));
|
||||
$('#dialogDeleteIgnoredSite .modal-body:first strong:first').text($(this).closest('tr').children('td:first').text());
|
||||
$('#dialogDeleteIgnoredSite').modal('show');
|
||||
$('#dialogDeleteSite').data('url', $(this).closest('tr').data('url'));
|
||||
$('#dialogDeleteSite').data('tr-id', $(this).closest('tr').attr('id'));
|
||||
$('#dialogDeleteSite .modal-body:first strong:first').text($(this).closest('tr').children('td:first').text());
|
||||
$('#dialogDeleteSite').modal('show');
|
||||
});
|
||||
|
||||
$('#dialogDeleteIgnoredSite .modal-footer:first button.yes:first').click(function(e) {
|
||||
$('#dialogDeleteIgnoredSite').modal('hide');
|
||||
|
||||
const $url = $('#dialogDeleteIgnoredSite').data('url');
|
||||
const $trId = $('#dialogDeleteIgnoredSite').data('tr-id');
|
||||
$('#tab-ignored-sites #' + $trId).remove();
|
||||
|
||||
delete options.settings['ignoredSites'][$url];
|
||||
$('#tab-site-preferences tr.clone:first input[type=checkbox]:first').change(function() {
|
||||
const url = $(this).closest('tr').data('url');
|
||||
for (let site of options.settings['sitePreferences']) {
|
||||
if (site.url === url) {
|
||||
site.usernameOnly = $(this).is(':checked');
|
||||
}
|
||||
}
|
||||
options.saveSettings();
|
||||
});
|
||||
|
||||
if ($('#tab-ignored-sites table tbody:first tr').length > 2) {
|
||||
$('#tab-ignored-sites table tbody:first tr.empty:first').hide();
|
||||
} else {
|
||||
$('#tab-ignored-sites table tbody:first tr.empty:first').show();
|
||||
$('#tab-site-preferences tr.clone:first select:first').change(function() {
|
||||
const url = $(this).closest('tr').data('url');
|
||||
for (let site of options.settings['sitePreferences']) {
|
||||
if (site.url === url) {
|
||||
site.ignore = $(this).val();
|
||||
}
|
||||
}
|
||||
options.saveSettings();
|
||||
});
|
||||
|
||||
$("#manualUrl").keyup(function(event) {
|
||||
if (event.keyCode === 13) {
|
||||
$("#sitePreferencesManualAdd").click();
|
||||
}
|
||||
});
|
||||
|
||||
const $trClone = $('#tab-ignored-sites table tr.clone:first').clone(true);
|
||||
$trClone.removeClass('clone');
|
||||
$('#sitePreferencesManualAdd').click(function(e) {
|
||||
e.preventDefault();
|
||||
let value = $('#manualUrl').val();
|
||||
if (value.length > 10 && value.length <= 2000) {
|
||||
if (options.settings['sitePreferences'] === undefined) {
|
||||
options.settings['sitePreferences'] = [];
|
||||
}
|
||||
|
||||
const newValue = options.settings['sitePreferences'].length + 1;
|
||||
const trClone = $('#tab-site-preferences table tr.clone:first').clone(true);
|
||||
trClone.removeClass('clone');
|
||||
|
||||
// Fills the last / char if needed. This ensures the compatibility with Match Patterns
|
||||
if (options.slashNeededForUrl(value)) {
|
||||
value += '/';
|
||||
}
|
||||
|
||||
const tr = trClone.clone(true);
|
||||
tr.data('url', value);
|
||||
tr.attr('id', 'tr-scf' + newValue);
|
||||
tr.children('td:first').text(value);
|
||||
tr.children('td:nth-child(2)').children('select').val(IGNORE_NORMAL);
|
||||
$('#tab-site-preferences table tbody:first').append(tr);
|
||||
$('#tab-site-preferences table tbody:first tr.empty:first').hide();
|
||||
|
||||
options.settings['sitePreferences'].push({url: value, ignore: IGNORE_NORMAL, usernameOnly: false});
|
||||
options.saveSettings();
|
||||
|
||||
$('#manualUrl').val('');
|
||||
}
|
||||
});
|
||||
|
||||
$('#dialogDeleteSite .modal-footer:first button.yes:first').click(function(e) {
|
||||
$('#dialogDeleteSite').modal('hide');
|
||||
|
||||
const url = $('#dialogDeleteSite').data('url');
|
||||
const trId = $('#dialogDeleteSite').data('tr-id');
|
||||
$('#tab-site-preferences #' + trId).remove();
|
||||
|
||||
for (let i = 0; i < options.settings['sitePreferences'].length; ++i) {
|
||||
if (options.settings['sitePreferences'][i].url === url) {
|
||||
options.settings['sitePreferences'].splice(i, 1);
|
||||
}
|
||||
}
|
||||
options.saveSettings();
|
||||
|
||||
if ($('#tab-site-preferences table tbody:first tr').length > 2) {
|
||||
$('#tab-site-preferences table tbody:first tr.empty:first').hide();
|
||||
} else {
|
||||
$('#tab-site-preferences table tbody:first tr.empty:first').show();
|
||||
}
|
||||
});
|
||||
|
||||
const trClone = $('#tab-site-preferences table tr.clone:first').clone(true);
|
||||
trClone.removeClass('clone');
|
||||
let counter = 1;
|
||||
for (let url in options.settings['ignoredSites']) {
|
||||
const $tr = $trClone.clone(true);
|
||||
$tr.data('url', url);
|
||||
$tr.attr('id', 'tr-scf' + counter);
|
||||
++counter;
|
||||
if (options.settings['sitePreferences']){
|
||||
for (let site of options.settings['sitePreferences']) {
|
||||
const tr = trClone.clone(true);
|
||||
tr.data('url', site.url);
|
||||
tr.attr('id', 'tr-scf' + counter);
|
||||
++counter;
|
||||
|
||||
$tr.children('td:first').text(url);
|
||||
$('#tab-ignored-sites table tbody:first').append($tr);
|
||||
tr.children('td:first').text(site.url);
|
||||
tr.children('td:nth-child(2)').children('select').val(site.ignore);
|
||||
tr.children('td:nth-child(3)').children('input[type=checkbox]').attr('checked', site.usernameOnly);
|
||||
$('#tab-site-preferences table tbody:first').append(tr);
|
||||
}
|
||||
}
|
||||
|
||||
if ($('#tab-ignored-sites table tbody:first tr').length > 2) {
|
||||
$('#tab-ignored-sites table tbody:first tr.empty:first').hide();
|
||||
|
||||
if ($('#tab-site-preferences table tbody:first tr').length > 2) {
|
||||
$('#tab-site-preferences table tbody:first tr.empty:first').hide();
|
||||
} else {
|
||||
$('#tab-ignored-sites table tbody:first tr.empty:first').show();
|
||||
$('#tab-site-preferences table tbody:first tr.empty:first').show();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -326,3 +392,9 @@ options.initAbout = function() {
|
|||
$('#default-pass-shortcut').show();
|
||||
}
|
||||
};
|
||||
|
||||
// Checks if URL has only scheme and host without the last / char.
|
||||
options.slashNeededForUrl = function(pattern) {
|
||||
const matchPattern = new RegExp(`^${schemeSegment}://${hostSegment}$`);
|
||||
return matchPattern.exec(pattern);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
body {
|
||||
font-family: sans-serif;
|
||||
min-width: 440px;
|
||||
max-width: 440px;
|
||||
overflow-x: hidden;
|
||||
background-color: #eee;
|
||||
font-size: 15px;
|
||||
padding: 8px;
|
||||
}
|
||||
.container {
|
||||
min-width: 440px;
|
||||
max-width: 460px;
|
||||
width: auto;
|
||||
}
|
||||
.list-group {
|
||||
font-size: .9em !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
<div class="container">
|
||||
<div id="settings" class="settings">
|
||||
<button id="btn-options" class="btn btn-sm btn-success"><span class="glyphicon glyphicon-cog"></span> Settings</button>
|
||||
<button id="btn-choose-credential-fields" class="btn btn-sm btn-warning"><span class="glyphicon glyphicon-list-alt"></span> Choose own credential fields for this page</button>
|
||||
<button id="btn-choose-credential-fields" class="btn btn-sm btn-warning"><span class="glyphicon glyphicon-list-alt"></span> Choose custom credential fields for this page</button>
|
||||
<button id="lock-database-button" class="btn btn-danger" title="Lock database"><span class="glyphicon glyphicon-lock"></span></button>
|
||||
|
||||
<div id="update-available" class="alert alert-danger">
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
<div class="container">
|
||||
<div id="settings" class="settings">
|
||||
<button id="btn-options" class="btn btn-sm btn-success"><span class="glyphicon glyphicon-cog"></span> Settings</button>
|
||||
<button id="btn-choose-credential-fields" class="btn btn-sm btn-warning"><span class="glyphicon glyphicon-list-alt"></span> Choose own credential fields for this page</button>
|
||||
<button id="btn-choose-credential-fields" class="btn btn-sm btn-warning"><span class="glyphicon glyphicon-list-alt"></span> Choose custom credential fields for this page</button>
|
||||
<button id="lock-database-button" class="btn btn-danger" title="Lock database"><span class="glyphicon glyphicon-lock"></span></button>
|
||||
|
||||
<div id="update-available" class="alert alert-danger">
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
<div class="container">
|
||||
<div id="settings" class="settings">
|
||||
<button id="btn-options" class="btn btn-sm btn-success"><span class="glyphicon glyphicon-cog"></span> Settings</button>
|
||||
<button id="btn-choose-credential-fields" class="btn btn-sm btn-warning"><span class="glyphicon glyphicon-list-alt"></span> Choose own credential fields for this page</button>
|
||||
<button id="btn-choose-credential-fields" class="btn btn-sm btn-warning"><span class="glyphicon glyphicon-list-alt"></span> Choose custom credential fields for this page</button>
|
||||
<button id="lock-database-button" class="btn btn-danger" title="Lock database"><span class="glyphicon glyphicon-lock"></span></button>
|
||||
|
||||
<div id="update-available" class="alert alert-danger">
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ $(function() {
|
|||
const id = e.target.id;
|
||||
browser.tabs.sendMessage(tab.id, {
|
||||
action: 'fill_user_pass_with_specific_login',
|
||||
id: id
|
||||
id: Number(id)
|
||||
});
|
||||
close();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
<div class="container">
|
||||
<div id="settings" class="settings">
|
||||
<button id="btn-options" class="btn btn-sm btn-success"><span class="glyphicon glyphicon-cog"></span> Settings</button>
|
||||
<button id="btn-choose-credential-fields" class="btn btn-sm btn-warning"><span class="glyphicon glyphicon-list-alt"></span> Choose own credential fields for this page</button>
|
||||
<button id="btn-choose-credential-fields" class="btn btn-sm btn-warning"><span class="glyphicon glyphicon-list-alt"></span> Choose custom credential fields for this page</button>
|
||||
<button id="lock-database-button" class="btn btn-danger" title="Lock database"><span class="glyphicon glyphicon-lock"></span></button>
|
||||
|
||||
<div id="update-available" class="alert alert-danger">
|
||||
|
|
|
|||
|
|
@ -108,9 +108,10 @@ function _connected_database(db) {
|
|||
}
|
||||
|
||||
function _verifyResult(code) {
|
||||
if (code === 'success') {
|
||||
_close();
|
||||
if (code === 'error') {
|
||||
showNotification('Error: Credentials cannot be saved or updated.');
|
||||
}
|
||||
_close();
|
||||
}
|
||||
|
||||
function _close() {
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 673 B After Width: | Height: | Size: 583 B |
Loading…
Reference in a new issue