diff --git a/keepassxc-browser/content/autocomplete.js b/keepassxc-browser/content/autocomplete.js index 5022e6e..5c4c6bb 100644 --- a/keepassxc-browser/content/autocomplete.js +++ b/keepassxc-browser/content/autocomplete.js @@ -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); + } } }); diff --git a/keepassxc-browser/content/banner.js b/keepassxc-browser/content/banner.js index fdeea14..866b6cd 100644 --- a/keepassxc-browser/content/banner.js +++ b/keepassxc-browser/content/banner.js @@ -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); }; diff --git a/keepassxc-browser/content/pwgen.js b/keepassxc-browser/content/pwgen.js index f9a2eed..556db29 100644 --- a/keepassxc-browser/content/pwgen.js +++ b/keepassxc-browser/content/pwgen.js @@ -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'; }; diff --git a/keepassxc-browser/content/totp-field.js b/keepassxc-browser/content/totp-field.js index e537405..1e317fd 100644 --- a/keepassxc-browser/content/totp-field.js +++ b/keepassxc-browser/content/totp-field.js @@ -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); }; diff --git a/keepassxc-browser/content/ui.js b/keepassxc-browser/content/ui.js index 44c7576..7394d82 100644 --- a/keepassxc-browser/content/ui.js +++ b/keepassxc-browser/content/ui.js @@ -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; +}; diff --git a/keepassxc-browser/content/username-field.js b/keepassxc-browser/content/username-field.js index bfaf0f1..b284dde 100644 --- a/keepassxc-browser/content/username-field.js +++ b/keepassxc-browser/content/username-field.js @@ -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) { diff --git a/keepassxc-browser/css/pwgen.css b/keepassxc-browser/css/pwgen.css index d44e871..92d2bb6 100644 --- a/keepassxc-browser/css/pwgen.css +++ b/keepassxc-browser/css/pwgen.css @@ -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; diff --git a/keepassxc-browser/manifest.json b/keepassxc-browser/manifest.json index 33103ab..ba68ba4 100755 --- a/keepassxc-browser/manifest.json +++ b/keepassxc-browser/manifest.json @@ -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",