diff --git a/README.md b/README.md index 69a2fe0..9f8af7d 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,18 @@ If you can try a development version, the following steps are needed. # Change logs +## [0.1.9](https://github.com/horihiro/TextBlurrer-ChromeExtension/releases/tag/0.1.9) + + - New features + - Add title masking by the keywords + - Bug fixes + - Improve misalignment of mask position for input element + +## [0.1.8](https://github.com/horihiro/TextBlurrer-ChromeExtension/releases/tag/0.1.8) + + - Bug fixes + - Fix misalignment of mask position for input element with `box-sizing` set to `border-box` ([#32](https://github.com/horihiro/TextBlurrer-ChromeExtension/issues/32)) + ## [0.1.7](https://github.com/horihiro/TextBlurrer-ChromeExtension/releases/tag/0.1.7) - Bug fixes diff --git a/content/js/main.js b/content/js/main.js index b237636..d8d1cce 100644 --- a/content/js/main.js +++ b/content/js/main.js @@ -3,6 +3,7 @@ const exElmList = ['html', 'title', 'script', 'noscript', 'style', 'meta', 'link', 'head', 'textarea', '#comment']; const blurredClassName = 'tb_blurred_class'; const keepClassName = 'tb_keep_this_class'; + const originalTitleAttributeName = 'data-tb-original-title'; const maskContainerClassName = 'tb_mask_container_class'; const textLayerClassName = 'tb_mask_text_layer_class'; const inputCloneId = 'tb_input_clone'; @@ -338,7 +339,8 @@ }px`); mask.style.setProperty('top', `${input.offsetTop + input.offsetHeight - blurredSpan.offsetHeight - (verticalGap > 0 ? verticalGap / 2 : 0) - - (isBorderBox ? 0 : parseFloat(inputStyle.getPropertyValue('border-bottom-width')) + parseFloat(inputStyle.getPropertyValue('padding-bottom'))) + - (isBorderBox ? - parseFloat(inputStyle.getPropertyValue('border-top-width')) : parseFloat(inputStyle.getPropertyValue('border-bottom-width')) ) + - parseFloat(inputStyle.getPropertyValue('padding-bottom')) }px`); const maskBoundingBox = mask.getBoundingClientRect(); const tmpWidth = inputBoundingBox.width + inputBoundingBox.left - maskBoundingBox.left - parseFloat(inputStyle.getPropertyValue('border-left-width')); @@ -348,7 +350,7 @@ ? tmpWidth : 0}px`); mask.style.setProperty('height', `${blurredBoundingBox.height}px`); - mask.style.setProperty('z-index', `${parseInt(inputStyle.getPropertyValue) + 1}`); + mask.style.setProperty('z-index', `${parseInt(inputStyle.getPropertyValue('z-index')) + 1}`); mask.style.setProperty('border', 'none'); mask.style.setProperty('background-color', getBackgroundColorAlongDOMTree(input)); @@ -361,7 +363,7 @@ if (element == document) return ''; const computedStyle = getComputedStyle(element); return (!/(?:^| )rgba *\( *\d+ *, *\d+ *, *\d+ *, *0 *\)(?:$| )/.test(computedStyle.getPropertyValue('background-color'))) - ? computedStyle.getPropertyValue('background-color') + ? computedStyle.getPropertyValue('background-color').replace(/rgba *\( *(\d+) *, *(\d+) *, *(\d+) *, *[^)]+ *\)/, 'rgb($1, $2, $3)') : getBackgroundColorAlongDOMTree(element.parentNode); } const inputOnFocus = (e) => { @@ -456,10 +458,10 @@ m.forEach((n) => { if (n.classList.contains(blurredClassName) && n.classList.contains(keepClassName)) { // restore title - const originalTitle = n.getAttribute('data-tb-original-title'); + const originalTitle = n.getAttribute(originalTitleAttributeName); if (originalTitle) { n.setAttribute('title', originalTitle); - n.removeAttribute('data-tb-original-title'); + n.removeAttribute(originalTitleAttributeName); } else n.removeAttribute('title'); @@ -504,6 +506,50 @@ console.debug(`Took ${Date.now() - now} ms`) }; + const unblurTabTitle = (title) => { + if (!title) return; + if (title.getAttribute(originalTitleAttributeName)) { + title.textContent = title.getAttribute(originalTitleAttributeName); + title.removeAttribute(originalTitleAttributeName); + } + if (!w.__titleObserver) return; + w.__titleObserver.disconnect(); + delete w.__titleObserver; + }; + + const blurTabTitleCore = (pattern, target) => { + const title = target.textContent; + let result = title.match(pattern); + while (result) { + const mask = new Array(result[0].length).fill('*').join(''); + target.textContent = target.textContent.replace(result[0], mask); + result = target.textContent.match(pattern); + if (!target.getAttribute(originalTitleAttributeName)) { + target.setAttribute(originalTitleAttributeName, title); + } + } + }; + + const blurTabTitle = (pattern, title) => { + if (!title) return; + blurTabTitleCore(pattern, title); + if (!w.__titleObserver) { + w.__titleObserver = new MutationObserver((records) => { + w.__titleObserver.disconnect(); + title.removeAttribute(originalTitleAttributeName); + records.forEach((record) => { + blurTabTitleCore(pattern, record.target); + }); + w.__titleObserver.observe(title, { + characterData: true, childList: true + }); + }); + } + w.__titleObserver.observe(title, { + characterData: true, childList: true + }); + }; + const escapeRegExp = (str) => { return str.replace(/([\(\)\{\}\+\*\?\[\]\.\^\$\|\\])/g, '\\$1'); }; @@ -515,19 +561,25 @@ ); }; + const title = document.querySelector('title'); chrome.storage.onChanged.addListener(async (changes, area) => { if (area !== 'local') return; - const { status, keywords, mode, matchCase, showValue, blurInput } = (await chrome.storage.local.get(['status', 'keywords', 'mode', 'matchCase', 'showValue', 'blurInput'])); + const { status, keywords, mode, matchCase, showValue, blurInput, blurTitle } = (await chrome.storage.local.get(['status', 'keywords', 'mode', 'matchCase', 'showValue', 'blurInput', 'blurTitle'])); unblur(); + unblurTabTitle(title); if (status === 'disabled' || !keywords || keywords.trim() === '') return; - blur(keywords2RegExp(keywords, mode, !!matchCase), { showValue, blurInput }); + const pattern = keywords2RegExp(keywords, mode, !!matchCase); + blur(pattern, { showValue, blurInput }); + blurTitle && blurTabTitle(pattern, title); }); - const { status, keywords, mode, matchCase, showValue, blurInput } = (await chrome.storage.local.get(['status', 'keywords', 'mode', 'matchCase', 'showValue', 'blurInput'])); + const { status, keywords, mode, matchCase, showValue, blurInput, blurTitle } = (await chrome.storage.local.get(['status', 'keywords', 'mode', 'matchCase', 'showValue', 'blurInput', 'blurTitle'])); if (status === 'disabled' || !keywords || keywords.trim() === '') return; window.addEventListener('resize', () => { inputs.forEach((input) => { input.element.dispatchEvent(new InputEvent('input', { data: input.value })); }); }) - blur(keywords2RegExp(keywords, mode, !!matchCase), { showValue, blurInput }); + const pattern = keywords2RegExp(keywords, mode, !!matchCase); + blur(pattern, { showValue, blurInput }); + blurTitle && blurTabTitle(pattern, title) })(); \ No newline at end of file diff --git a/manifest.json b/manifest.json index ede619d..a10f561 100644 --- a/manifest.json +++ b/manifest.json @@ -1,8 +1,8 @@ { "manifest_version": 3, "name": "Text Blurrer", - "version": "0.1.8", - "version_name": "0.1.8", + "version": "0.1.9", + "version_name": "0.1.9", "description": "Blurring sensitive specified text/keyword.", "permissions": [ "storage" diff --git a/popup/css/main.css b/popup/css/main.css index 231cf19..101a93a 100644 --- a/popup/css/main.css +++ b/popup/css/main.css @@ -157,6 +157,15 @@ input[type="checkbox"]:checked.blur-input+span::before { background-image: url("../img/symbol-string.svg"); } +input[type="checkbox"].blur-title+span::before { + -webkit-mask: url("../img/tab-title.svg") no-repeat center center; + mask: url("../img/tab-title.svg") no-repeat center center; +} + +input[type="checkbox"]:checked.blur-title+span::before { + background-image: url("../img/tab-title.svg"); +} + #applyButton { margin-top: 10px; display: flex; diff --git a/popup/img/tab-title.svg b/popup/img/tab-title.svg new file mode 100644 index 0000000..91c6a7b --- /dev/null +++ b/popup/img/tab-title.svg @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/popup/js/main.js b/popup/js/main.js index c0e9c48..b3248dc 100644 --- a/popup/js/main.js +++ b/popup/js/main.js @@ -1,5 +1,5 @@ document.addEventListener('DOMContentLoaded', async (e) => { - const { status, keywords, mode, matchCase, showValue, blurInput } = (await chrome.storage.local.get(['status', 'keywords', 'mode', 'matchCase', 'showValue', 'blurInput'])); + const { status, keywords, mode, matchCase, showValue, blurInput, blurTitle } = (await chrome.storage.local.get(['status', 'keywords', 'mode', 'matchCase', 'showValue', 'blurInput', 'blurTitle'])); const applyButton = document.querySelector('#applyButton'); const patternInput = document.querySelector('#patternInput'); @@ -8,6 +8,7 @@ document.addEventListener('DOMContentLoaded', async (e) => { const regexpCheckbox = document.querySelector('#regexpCheckbox'); const showValueCheckbox = document.querySelector('#showValueCheckbox'); const blurInputCheckbox = document.querySelector('#blurInputCheckbox'); + const blurTitleCheckbox = document.querySelector('#blurTitleCheckbox'); const _bufferTextArea = document.querySelector('#_bufferTextArea'); const COLOR_DEFAULT = getComputedStyle(_bufferTextArea).getPropertyValue('background-color'); @@ -25,6 +26,7 @@ document.addEventListener('DOMContentLoaded', async (e) => { let savedMode = false; let savedShowValue = false; let savedBlurInput = false; + let savedBlurTitle = false; let validationResults = []; let pointedRow = -1; @@ -122,7 +124,8 @@ document.addEventListener('DOMContentLoaded', async (e) => { caseCheckbox.checked === savedMatchCase && showValueCheckbox.checked === savedShowValue && regexpCheckbox.checked === savedMode && - blurInputCheckbox.checked === savedBlurInput + blurInputCheckbox.checked === savedBlurInput && + blurTitleCheckbox.checked === savedBlurTitle ); const re = /\*(\d+)( - [\d.]+px\))$/; const bgColors = validationResults.reduce((prev, curr, pos, array) => { @@ -161,6 +164,7 @@ textarea#${patternInput.id} { 'matchCase': caseCheckbox.checked, 'showValue': showValueCheckbox.checked, 'blurInput': blurInputCheckbox.checked, + 'blurTitle': blurTitleCheckbox.checked, }); patternInput.focus(); savedKeywords = patternInput.value; @@ -168,6 +172,7 @@ textarea#${patternInput.id} { savedMatchCase = caseCheckbox.checked; savedShowValue = showValueCheckbox.checked; savedBlurInput = blurInputCheckbox.checked; + savedBlurTitle = blurTitleCheckbox.checked; e.target.disabled = true; }); @@ -175,6 +180,7 @@ textarea#${patternInput.id} { caseCheckbox.disabled = showValueCheckbox.disabled = blurInputCheckbox.disabled = + blurTitleCheckbox.disabled = regexpCheckbox.disabled = patternInput.disabled = !e.target.checked; applyButton.disabled = !e.target.checked || !validationResults.every(r => r.isValid); @@ -213,6 +219,11 @@ textarea#${patternInput.id} { patternInput.focus(); }); + blurTitleCheckbox.addEventListener('change', async (e) => { + await renderBackground(); + patternInput.focus(); + }); + patternInput.addEventListener('scroll', renderBackground); patternInput.addEventListener('scroll', () => { if (patternInput.scrollLeft < textAreaPaddingLeft) patternInput.scrollLeft = 0; @@ -243,12 +254,14 @@ textarea#${patternInput.id} { savedMatchCase = caseCheckbox.checked = matchCase; savedShowValue = showValueCheckbox.checked = showValue; savedBlurInput = blurInputCheckbox.checked = blurInput; + savedBlurTitle = blurTitleCheckbox.checked = blurTitle; savedMode = regexpCheckbox.checked = mode === 'regexp'; savedKeywords = patternInput.value = keywords || ''; caseCheckbox.disabled = blurInputCheckbox.disabled = + blurTitleCheckbox.disabled = showValueCheckbox.disabled = regexpCheckbox.disabled = patternInput.disabled = diff --git a/popup/main.html b/popup/main.html index cfb2b90..ad21938 100644 --- a/popup/main.html +++ b/popup/main.html @@ -27,9 +27,13 @@
+