Merge pull request #719 from keepassxreboot/feature/shadow_dom

Move DOM elements to Shadow DOM
This commit is contained in:
Sami Vänttinen 2020-02-09 21:49:03 +02:00 committed by GitHub
commit 90fb5ad9e9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 165 additions and 80 deletions

View file

@ -6,6 +6,8 @@ kpxcAutocomplete.elements = [];
kpxcAutocomplete.started = false;
kpxcAutocomplete.index = -1;
kpxcAutocomplete.input = undefined;
kpxcAutocomplete.shadowRoot = undefined;
kpxcAutocomplete.wrapper = undefined;
kpxcAutocomplete.create = function(input, showListInstantly = false, autoSubmit = false) {
kpxcAutocomplete.autoSubmit = autoSubmit;
@ -36,10 +38,21 @@ kpxcAutocomplete.showList = function(inputField) {
kpxcAutocomplete.input = inputField;
const div = kpxcUI.createElement('div', 'kpxcAutocomplete-items', { 'id': 'kpxcAutocomplete-list' });
initColorTheme(div);
kpxcAutocomplete.updatePosition(inputField, div);
div.style.zIndex = '2147483646';
initColorTheme(div);
document.body.append(div);
const styleSheet = createStylesheet('css/autocomplete.css');
const colorStyleSheet = createStylesheet('css/colors.css');
const wrapper = kpxcUI.createElement('div');
kpxcAutocomplete.shadowRoot = wrapper.attachShadow({ mode: 'closed' });
kpxcAutocomplete.shadowRoot.append(colorStyleSheet);
kpxcAutocomplete.shadowRoot.append(styleSheet);
kpxcAutocomplete.shadowRoot.append(div);
kpxcAutocomplete.wrapper = wrapper;
document.body.append(wrapper);
for (const c of kpxcAutocomplete.elements) {
const item = document.createElement('div');
@ -61,6 +74,7 @@ kpxcAutocomplete.showList = function(inputField) {
kpxcAutocomplete.fillPassword(inputField.value, index);
kpxcAutocomplete.closeList();
inputField.focus();
document.body.removeChild(wrapper);
});
// These events prevent the double hover effect if both keyboard and mouse are used
@ -114,7 +128,15 @@ kpxcAutocomplete.removeItem = function(items) {
};
kpxcAutocomplete.closeList = function(elem) {
const items = document.getElementsByClassName('kpxcAutocomplete-items');
if (!kpxcAutocomplete.shadowRoot) {
return;
}
const items = kpxcAutocomplete.shadowSelectorAll('.kpxcAutocomplete-items');
if (!items) {
return;
}
for (const item of items) {
if (elem !== item && kpxcAutocomplete.input) {
item.parentNode.removeChild(item);
@ -123,7 +145,7 @@ kpxcAutocomplete.closeList = function(elem) {
};
kpxcAutocomplete.getAllItems = function() {
const list = document.getElementById('kpxcAutocomplete-list');
const list = kpxcAutocomplete.shadowSelector('#kpxcAutocomplete-list');
if (!list) {
return [];
}
@ -199,7 +221,7 @@ kpxcAutocomplete.fillPassword = function(value, index) {
};
kpxcAutocomplete.updatePosition = function(inputField, elem) {
const div = elem || $('.kpxcAutocomplete-items');
const div = elem || kpxcAutocomplete.shadowSelector('.kpxcAutocomplete-items');
if (!div) {
return;
}
@ -216,7 +238,7 @@ document.addEventListener('click', function(e) {
return;
}
const list = document.getElementById('kpxcAutocomplete-list');
const list = kpxcAutocomplete.shadowRoot ? kpxcAutocomplete.shadowSelector('#kpxcAutocomplete-list') : undefined;
if (!list) {
return;
}
@ -225,6 +247,10 @@ document.addEventListener('click', function(e) {
!e.target.classList.contains('kpxc-username-icon') &&
e.target.nodeName !== kpxcAutocomplete.input.nodeName) {
kpxcAutocomplete.closeList(e.target);
if (kpxcAutocomplete.wrapper) {
document.body.removeChild(kpxcAutocomplete.wrapper);
}
}
});

View file

@ -6,29 +6,26 @@ const kpxcBanner = {};
kpxcBanner.banner = undefined;
kpxcBanner.created = false;
kpxcBanner.credentials = {};
kpxcBanner.wrapper = undefined;
kpxcBanner.destroy = function() {
kpxcBanner.created = false;
kpxcBanner.credentials = {};
const dialog = $('.kpxc-banner-dialog');
const dialog = kpxcBanner.shadowSelector('.kpxc-banner-dialog');
if (dialog) {
document.body.removeChild(dialog);
kpxcBanner.banner.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;
if (kpxcBanner.wrapper && window.parent.document.body.contains(kpxcBanner.wrapper)) {
window.parent.document.body.removeChild(kpxcBanner.wrapper);
} else {
window.parent.document.body.removeChild(window.parent.document.body.querySelector('#kpxc-banner'));
}
document.body.removeChild(kpxcBanner.banner);
};
kpxcBanner.create = async function(credentials = {}) {
@ -48,10 +45,10 @@ kpxcBanner.create = async function(credentials = {}) {
return;
}
kpxcBanner.created = true;
kpxcBanner.credentials = credentials;
const banner = kpxcUI.createElement('div', 'kpxc-banner', { 'id': 'container' });
initColorTheme(banner);
banner.style.zIndex = '2147483646';
const bannerInfo = kpxcUI.createElement('div', 'banner-info');
@ -98,12 +95,12 @@ kpxcBanner.create = async function(credentials = {}) {
}
// If a banner dialog is shown, display the main banner
const dialog = $('.kpxc-banner-dialog');
const dialog = kpxcBanner.shadowSelector('.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);
kpxcBanner.shadowSelector('#kpxc-banner-btn-new').hidden = false;
kpxcBanner.shadowSelector('#kpxc-banner-btn-update').hidden = false;
kpxcBanner.shadowSelector('.kpxc-checkbox').disabled = false;
kpxcBanner.banner.removeChild(dialog);
} else {
if (ignoreCheckbox.checked) {
kpxc.ignoreSite([ window.top.location.href ]);
@ -119,9 +116,23 @@ kpxcBanner.create = async function(credentials = {}) {
initColorTheme(banner);
const existingBanner = window.parent.document.body.querySelector('.kpxc-banner');
if (existingBanner === null) {
window.parent.document.body.appendChild(banner);
const styleSheet = createStylesheet('css/banner.css');
const buttonStyleSheet = createStylesheet('css/button.css');
const colorStyleSheet = createStylesheet('css/colors.css');
const wrapper = document.createElement('div');
wrapper.setAttribute('id', 'kpxc-banner');
this.shadowRoot = wrapper.attachShadow({ mode: 'closed' });
this.shadowRoot.append(colorStyleSheet);
this.shadowRoot.append(styleSheet);
this.shadowRoot.append(buttonStyleSheet);
this.shadowRoot.append(banner);
kpxcBanner.wrapper = wrapper;
if (window.self === window.top && !kpxcBanner.created) {
window.parent.document.body.appendChild(wrapper);
kpxcBanner.created = true;
}
};
@ -195,7 +206,7 @@ kpxcBanner.saveNewCredentials = async function(credentials = {}) {
a.setAttribute('id', 'root-child');
}
$('ul#list').appendChild(a);
kpxcBanner.shadowSelector('ul#list').appendChild(a);
addChildren(child, a, depth);
}
};
@ -230,11 +241,11 @@ kpxcBanner.saveNewCredentials = async function(credentials = {}) {
const a = createLink(g.name, g.uuid, g.children.length > 0);
a.setAttribute('id', 'root');
$('ul#list').appendChild(a);
kpxcBanner.shadowSelector('ul#list').appendChild(a);
addChildren(g, a, depth);
}
$('.kpxc-banner-dialog').style.display = 'block';
kpxcBanner.shadowSelector('.kpxc-banner-dialog').style.display = 'block';
};
kpxcBanner.updateCredentials = async function(credentials = {}) {
@ -252,15 +263,15 @@ kpxcBanner.updateCredentials = async function(credentials = {}) {
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;
kpxcBanner.shadowSelector('.kpxc-banner-dialog .username-new .strong').textContent = credentials.username;
kpxcBanner.shadowSelector('.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';
kpxcBanner.shadowSelector('.kpxc-banner-dialog .username-new').style.display = 'none';
kpxcBanner.shadowSelector('.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';
kpxcBanner.shadowSelector('.kpxc-banner-dialog .username-new').style.display = 'block';
kpxcBanner.shadowSelector('.kpxc-banner-dialog .username-exists').style.display = 'none';
}
for (let i = 0; i < credentials.list.length; i++) {
@ -303,10 +314,10 @@ kpxcBanner.updateCredentials = async function(credentials = {}) {
a.style.fontWeight = 'bold';
}
$('ul#list').appendChild(a);
kpxcBanner.shadowSelector('ul#list').appendChild(a);
}
$('.kpxc-banner-dialog').style.display = 'block';
kpxcBanner.shadowSelector('.kpxc-banner-dialog').style.display = 'block';
}
};
@ -346,9 +357,9 @@ kpxcBanner.getDefaultGroup = function(groups, defaultGroup) {
};
kpxcBanner.createCredentialDialog = async function() {
$('#kpxc-banner-btn-new').hidden = true;
$('#kpxc-banner-btn-update').hidden = true;
$('.kpxc-checkbox').disabled = true;
kpxcBanner.shadowSelector('#kpxc-banner-btn-new').hidden = true;
kpxcBanner.shadowSelector('#kpxc-banner-btn-update').hidden = true;
kpxcBanner.shadowSelector('.kpxc-checkbox').disabled = true;
const connectedDatabase = await browser.runtime.sendMessage({
action: 'get_connected_database'
@ -379,13 +390,13 @@ kpxcBanner.createCredentialDialog = async function() {
usernameExists.append(kpxcUI.createElement('span', 'strong'));
dialog.appendMultiple(databaseText, usernameNew, usernameExists, chooseCreds, list);
initColorTheme(dialog);
document.body.appendChild(dialog);
kpxcBanner.banner.appendChild(dialog);
};
kpxcBanner.createGroupDialog = function() {
$('#kpxc-banner-btn-new').hidden = true;
$('#kpxc-banner-btn-update').hidden = true;
$('.kpxc-checkbox').disabled = true;
kpxcBanner.shadowSelector('#kpxc-banner-btn-new').hidden = true;
kpxcBanner.shadowSelector('#kpxc-banner-btn-update').hidden = true;
kpxcBanner.shadowSelector('.kpxc-checkbox').disabled = true;
const dialog = kpxcUI.createElement('div', 'kpxc-banner-dialog');
const chooseGroup = kpxcUI.createElement('p', '', {}, tr('rememberChooseGroup'));
@ -396,5 +407,5 @@ kpxcBanner.createGroupDialog = function() {
dialog.style.right = Pixels(0);
dialog.appendMultiple(chooseGroup, list);
document.body.appendChild(dialog);
kpxcBanner.banner.appendChild(dialog);
};

View file

@ -92,7 +92,16 @@ PasswordIcon.prototype.createIcon = function(field) {
kpxcUI.setIconPosition(icon, field);
this.icon = icon;
document.body.appendChild(icon);
const styleSheet = document.createElement('link');
styleSheet.setAttribute('rel', 'stylesheet');
styleSheet.setAttribute('href', browser.runtime.getURL('css/pwgen.css'));
const wrapper = document.createElement('div');
this.shadowRoot = wrapper.attachShadow({ mode: 'closed' });
this.shadowRoot.append(styleSheet);
this.shadowRoot.append(icon);
document.body.append(wrapper);
};
@ -121,7 +130,7 @@ kpxcPasswordDialog.removeIcon = function(field) {
kpxcPasswordDialog.createDialog = function() {
if (kpxcPasswordDialog.created) {
// If database is open again, generate a new password right away
const input = $('.kpxc-pwgen-input');
const input = kpxcPasswordDialog.shadowSelector('.kpxc-pwgen-input');
if (input.style.display === 'none') {
kpxcPasswordDialog.generate();
}
@ -129,7 +138,9 @@ kpxcPasswordDialog.createDialog = function() {
}
kpxcPasswordDialog.created = true;
const wrapper = kpxcUI.createElement('div', 'kpxc');
const wrapper = kpxcUI.createElement('div');
kpxcPasswordDialog.shadowRoot = wrapper.attachShadow({ mode: 'closed' });
const dialog = kpxcUI.createElement('div', 'kpxc kpxc-pwgen-dialog');
const titleBar = kpxcUI.createElement('div', 'kpxc-pwgen-titlebar', {}, tr('passwordGeneratorTitle'));
const closeButton = kpxcUI.createElement('div', 'kpxc-pwgen-close', {}, '×');
@ -167,7 +178,15 @@ kpxcPasswordDialog.createDialog = function() {
buttonsRow.appendMultiple(generateButton, copyButton, fillButton);
dialog.appendMultiple(titleBar, passwordRow, buttonsRow);
wrapper.append(dialog);
const styleSheet = createStylesheet('css/pwgen.css');
const buttonStyle = createStylesheet('css/button.css');
const colorStyleSheet = createStylesheet('css/colors.css');
kpxcPasswordDialog.shadowRoot.append(colorStyleSheet);
kpxcPasswordDialog.shadowRoot.append(styleSheet);
kpxcPasswordDialog.shadowRoot.append(buttonStyle);
kpxcPasswordDialog.shadowRoot.append(dialog);
const icon = $('.kpxc-pwgen-icon');
if (icon) {
@ -236,17 +255,6 @@ kpxcPasswordDialog.showDialog = function(field, icon) {
kpxcPasswordDialog.dialog.setAttribute('kpxc-pwgen-field-id', field.getAttribute('data-kpxc-id'));
kpxcPasswordDialog.dialog.setAttribute('kpxc-pwgen-next-field-id', field.getAttribute('kpxc-pwgen-next-field-id'));
kpxcPasswordDialog.dialog.setAttribute('kpxc-pwgen-next-is-password-field', field.getAttribute('kpxc-pwgen-next-is-password-field'));
const fieldExists = Boolean(field.getAttribute('kpxc-pwgen-next-field-exists'));
const checkbox = $('.kpxc-pwgen-checkbox');
if (checkbox) {
checkbox.setAttribute('checked', fieldExists);
if (fieldExists) {
checkbox.removeAttribute('disabled');
} else {
checkbox.setAttribute('disabled', '');
}
}
}
};
@ -283,7 +291,7 @@ kpxcPasswordDialog.fill = function(e) {
// Use the active input field
const field = _f(kpxcPasswordDialog.dialog.getAttribute('kpxc-pwgen-field-id'));
if (field) {
const password = $('.kpxc-pwgen-input');
const password = kpxcPasswordDialog.shadowSelector('.kpxc-pwgen-input');
if (field.getAttribute('maxlength')) {
if (password.value.length > field.getAttribute('maxlength')) {
const message = tr('passwordGeneratorErrorTooLong') + '\r\n' +
@ -307,7 +315,7 @@ kpxcPasswordDialog.fill = function(e) {
};
kpxcPasswordDialog.copyPasswordToClipboard = function() {
$('.kpxc-pwgen-input').select();
kpxcPasswordDialog.shadowSelector('.kpxc-pwgen-input').select();
try {
return document.execCommand('copy');
} catch (err) {
@ -318,19 +326,19 @@ kpxcPasswordDialog.copyPasswordToClipboard = function() {
const callbackGeneratedPassword = function(entries) {
if (entries && entries.length >= 1) {
const errorMessage = $('#kpxc-pwgen-error');
const errorMessage = kpxcPasswordDialog.shadowSelector('#kpxc-pwgen-error');
if (errorMessage) {
enableButtons();
const input = $('.kpxc-pwgen-input');
const input = kpxcPasswordDialog.shadowSelector('.kpxc-pwgen-input');
input.style.display = 'block';
errorMessage.remove();
}
$('.kpxc-pwgen-input').value = entries[0].password;
kpxcPasswordDialog.shadowSelector('.kpxc-pwgen-input').value = entries[0].password;
} else {
if (document.querySelectorAll('div#kpxc-pwgen-error').length === 0) {
const input = $('.kpxc-pwgen-input');
if (kpxcPasswordDialog.shadowSelectorAll('div#kpxc-pwgen-error').length === 0) {
const input = kpxcPasswordDialog.shadowSelector('.kpxc-pwgen-input');
input.style.display = 'none';
const errorMessage = kpxcUI.createElement('div', '', { 'id': 'kpxc-pwgen-error' },
@ -344,13 +352,13 @@ const callbackGeneratedPassword = function(entries) {
};
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';
kpxcPasswordDialog.shadowSelector('#kpxc-pwgen-btn-generate').textContent = tr('passwordGeneratorGenerate');
kpxcPasswordDialog.shadowSelector('#kpxc-pwgen-btn-copy').style.display = 'inline-block';
kpxcPasswordDialog.shadowSelector('#kpxc-pwgen-btn-fill').style.display = 'inline-block';
};
const disableButtons = function() {
$('#kpxc-pwgen-btn-generate').textContent = tr('passwordGeneratorTryAgain');
$('#kpxc-pwgen-btn-copy').style.display = 'none';
$('#kpxc-pwgen-btn-fill').style.display = 'none';
kpxcPasswordDialog.shadowSelector('#kpxc-pwgen-btn-generate').textContent = tr('passwordGeneratorTryAgain');
kpxcPasswordDialog.shadowSelector('#kpxc-pwgen-btn-copy').style.display = 'none';
kpxcPasswordDialog.shadowSelector('#kpxc-pwgen-btn-fill').style.display = 'none';
};

View file

@ -81,5 +81,14 @@ TOTPFieldIcon.prototype.createIcon = function(field) {
kpxcUI.setIconPosition(icon, field);
this.icon = icon;
document.body.appendChild(icon);
const styleSheet = document.createElement('link');
styleSheet.setAttribute('rel', 'stylesheet');
styleSheet.setAttribute('href', browser.runtime.getURL('css/totp.css'));
const wrapper = document.createElement('div');
this.shadowRoot = wrapper.attachShadow({ mode: 'closed' });
this.shadowRoot.append(styleSheet);
this.shadowRoot.append(icon);
document.body.append(wrapper);
};

View file

@ -166,6 +166,13 @@ const initColorTheme = function(elem) {
}
};
const createStylesheet = function(file) {
const stylesheet = document.createElement('link');
stylesheet.setAttribute('rel', 'stylesheet');
stylesheet.setAttribute('href', browser.runtime.getURL(file));
return stylesheet;
};
// Enables dragging
document.addEventListener('mousemove', function(e) {
if (!kpxcUI.mouseDown) {
@ -212,3 +219,16 @@ HTMLDivElement.prototype.appendMultiple = function(...args) {
Element.prototype.getLowerCaseAttribute = function(attr) {
return this.getAttribute(attr) ? this.getAttribute(attr).toLowerCase() : undefined;
};
Element.prototype._attachShadow = Element.prototype.attachShadow;
Element.prototype.attachShadow = function () {
return this._attachShadow( { mode: 'closed' } );
};
Object.prototype.shadowSelector = function(value) {
return this.shadowRoot ? this.shadowRoot.querySelector(value) : undefined;
};
Object.prototype.shadowSelectorAll = function(value) {
return this.shadowRoot ? this.shadowRoot.querySelectorAll(value) : undefined;
};

View file

@ -100,7 +100,16 @@ UsernameFieldIcon.prototype.createIcon = function(target) {
kpxcUI.setIconPosition(icon, field);
this.icon = icon;
document.body.appendChild(icon);
const styleSheet = document.createElement('link');
styleSheet.setAttribute('rel', 'stylesheet');
styleSheet.setAttribute('href', browser.runtime.getURL('css/username.css'));
const wrapper = document.createElement('div');
this.shadowRoot = wrapper.attachShadow({ mode: 'closed' });
this.shadowRoot.append(styleSheet);
this.shadowRoot.append(icon);
document.body.append(wrapper);
};
const iconClicked = async function(field, icon) {

View file

@ -65,11 +65,6 @@
width: 100% !important;
}
.kpxc-pwgen-checkbox-label {
font-style: normal !important;
font-weight: normal !important;
}
.kpxc #kpxc-pwgen-error {
color: var(--text-color);
font-size: 12px !important;

View file

@ -110,7 +110,14 @@
"icons/keepassxc.svg",
"icons/key.svg",
"icons/locked.svg",
"icons/otp.svg"
"icons/otp.svg",
"css/autocomplete.css",
"css/banner.css",
"css/button.css",
"css/colors.css",
"css/pwgen.css",
"css/username.css",
"css/totp.css"
],
"permissions": [
"activeTab",