From 40deab09812387910e751c7c5cb78d03f90cb3dd Mon Sep 17 00:00:00 2001 From: horihiro Date: Fri, 29 Mar 2024 20:05:32 +0900 Subject: [PATCH 01/29] Fix title blur issue and optimize tab title handling --- content/js/main.js | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/content/js/main.js b/content/js/main.js index d8d1cce..5cea20c 100644 --- a/content/js/main.js +++ b/content/js/main.js @@ -339,7 +339,7 @@ }px`); mask.style.setProperty('top', `${input.offsetTop + input.offsetHeight - blurredSpan.offsetHeight - (verticalGap > 0 ? verticalGap / 2 : 0) - - (isBorderBox ? - parseFloat(inputStyle.getPropertyValue('border-top-width')) : parseFloat(inputStyle.getPropertyValue('border-bottom-width')) ) + - (isBorderBox ? - parseFloat(inputStyle.getPropertyValue('border-top-width')) : parseFloat(inputStyle.getPropertyValue('border-bottom-width'))) - parseFloat(inputStyle.getPropertyValue('padding-bottom')) }px`); const maskBoundingBox = mask.getBoundingClientRect(); @@ -506,7 +506,8 @@ console.debug(`Took ${Date.now() - now} ms`) }; - const unblurTabTitle = (title) => { + const unblurTabTitle = () => { + const title = document.querySelector('title'); if (!title) return; if (title.getAttribute(originalTitleAttributeName)) { title.textContent = title.getAttribute(originalTitleAttributeName); @@ -530,24 +531,31 @@ } }; - const blurTabTitle = (pattern, title) => { - if (!title) return; - blurTabTitleCore(pattern, title); + const blurTabTitle = (pattern) => { 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 + records.some((record) => { + return Array.from(record.addedNodes).some((node) => { + if (node.nodeName === 'TITLE') { + blurTabTitleCore(pattern, node); + return true; + } else if (node.nodeName === "#text" && node.parentNode.nodeName === "TITLE") { + blurTabTitleCore(pattern, node.parentNode); + return true; + } + return false; + }); }); }); } - w.__titleObserver.observe(title, { - characterData: true, childList: true - }); + w.__titleObserver.observe(document.head, { + childList: true, + subtree: true, + characterData: true + }) + const title = document.querySelector('title'); + if (!title) return; + blurTabTitleCore(pattern, title); }; const escapeRegExp = (str) => { @@ -561,16 +569,15 @@ ); }; - const title = document.querySelector('title'); chrome.storage.onChanged.addListener(async (changes, area) => { if (area !== 'local') return; const { status, keywords, mode, matchCase, showValue, blurInput, blurTitle } = (await chrome.storage.local.get(['status', 'keywords', 'mode', 'matchCase', 'showValue', 'blurInput', 'blurTitle'])); unblur(); - unblurTabTitle(title); + unblurTabTitle(); if (status === 'disabled' || !keywords || keywords.trim() === '') return; const pattern = keywords2RegExp(keywords, mode, !!matchCase); blur(pattern, { showValue, blurInput }); - blurTitle && blurTabTitle(pattern, title); + blurTitle && blurTabTitle(pattern); }); 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; @@ -581,5 +588,5 @@ }) const pattern = keywords2RegExp(keywords, mode, !!matchCase); blur(pattern, { showValue, blurInput }); - blurTitle && blurTabTitle(pattern, title) + blurTitle && blurTabTitle(pattern) })(); \ No newline at end of file From f377f15c03b7742f62ebe7131740c508b7c425fc Mon Sep 17 00:00:00 2001 From: horihiro Date: Fri, 29 Mar 2024 20:33:03 +0900 Subject: [PATCH 02/29] Refactor unblurCore function to improve readability and maintainability --- content/js/main.js | 87 +++++++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 40 deletions(-) diff --git a/content/js/main.js b/content/js/main.js index d8d1cce..161c649 100644 --- a/content/js/main.js +++ b/content/js/main.js @@ -185,6 +185,9 @@ const blurByRegExpPattern = (pattern, options, target) => { const now = Date.now(); + if (target.classList && target.classList.contains(blurredClassName)) { + unblurCore(target); + } const targetObjects = getElementsByNodeValue(pattern, target || document.body, options).filter((o) => { return (Array.prototype.filter.call(o.node.childNodes, (c) => { return c.nodeName === '#text' && pattern.test(inlineFormatting(c.textContent)); @@ -456,54 +459,58 @@ const now = Date.now(); m.forEach((n) => { - if (n.classList.contains(blurredClassName) && n.classList.contains(keepClassName)) { - // restore title - const originalTitle = n.getAttribute(originalTitleAttributeName); - if (originalTitle) { - n.setAttribute('title', originalTitle); - n.removeAttribute(originalTitleAttributeName); - } - else n.removeAttribute('title'); + unblurCore(n); + }); + console.debug(`Took ${Date.now() - now} ms`) + }; + + const unblurCore = (n) => { + if (n.classList.contains(blurredClassName) && n.classList.contains(keepClassName)) { + // restore title + const originalTitle = n.getAttribute(originalTitleAttributeName); + if (originalTitle) { + n.setAttribute('title', originalTitle); + n.removeAttribute(originalTitleAttributeName); + } + else n.removeAttribute('title'); - // restore class - n.classList.remove(blurredClassName); - n.classList.remove(keepClassName); - if (n.classList.length == 0) n.removeAttribute('class'); + // restore class + n.classList.remove(blurredClassName); + n.classList.remove(keepClassName); + if (n.classList.length == 0) n.removeAttribute('class'); - // restore style - n.style.filter = n.style.filter.replace(/blur\([^\)]+\)/, '').trim(); - if (n.style.length == 0) n.removeAttribute('style'); + // restore style + n.style.filter = n.style.filter.replace(/blur\([^\)]+\)/, '').trim(); + if (n.style.length == 0) n.removeAttribute('style'); + return; + } + const p = n.parentNode; + n.childNodes.forEach((c) => { + if (c.nodeName !== '#text') { + p.insertBefore(c, n); return; } - const p = n.parentNode; - n.childNodes.forEach((c) => { - if (c.nodeName !== '#text') { + + let textContainer = n.previousSibling; + do { + if (!textContainer || textContainer.nodeName !== '#text') { p.insertBefore(c, n); - return; + break; } + if (textContainer.previousSibling && textContainer.textContent === '') { + textContainer = textContainer.previousSibling; + continue; + } + textContainer.textContent += c.textContent; - let textContainer = n.previousSibling; - do { - if (!textContainer || textContainer.nodeName !== '#text') { - p.insertBefore(c, n); - break; - } - if (textContainer.previousSibling && textContainer.textContent === '') { - textContainer = textContainer.previousSibling; - continue; - } - textContainer.textContent += c.textContent; - - if (n.nextSibling?.nodeName === '#text') { - n.previousSibling.textContent += n.nextSibling.textContent; - p.removeChild(n.nextSibling); - } - break; - } while (true); - }); - p.removeChild(n); + if (n.nextSibling?.nodeName === '#text') { + n.previousSibling.textContent += n.nextSibling.textContent; + p.removeChild(n.nextSibling); + } + break; + } while (true); }); - console.debug(`Took ${Date.now() - now} ms`) + p.removeChild(n); }; const unblurTabTitle = (title) => { From 21e67318ed9fa93afe2154101f2371aad2164102 Mon Sep 17 00:00:00 2001 From: horihiro Date: Fri, 29 Mar 2024 20:37:20 +0900 Subject: [PATCH 03/29] Fix unblurring and improve title masking --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 25d7982..ef7bf55 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,12 @@ If you can try a development version, the following steps are needed. # Change logs +## [0.1.10](https://github.com/horihiro/TextBlurrer-ChromeExtension/releases/tag/0.1.10) + + - Bug fixes + - Improve unblurring on updating text node by javascript + - Improve title masking + ## [0.1.9](https://github.com/horihiro/TextBlurrer-ChromeExtension/releases/tag/0.1.9) - New features From b09d971378a1e3d46cd2335465ce978bd57e48b6 Mon Sep 17 00:00:00 2001 From: horihiro Date: Fri, 29 Mar 2024 20:38:11 +0900 Subject: [PATCH 04/29] Update manifest.json version to 0.1.10 --- manifest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.json b/manifest.json index a10f561..eb5749d 100644 --- a/manifest.json +++ b/manifest.json @@ -1,8 +1,8 @@ { "manifest_version": 3, "name": "Text Blurrer", - "version": "0.1.9", - "version_name": "0.1.9", + "version": "0.1.10", + "version_name": "0.1.10", "description": "Blurring sensitive specified text/keyword.", "permissions": [ "storage" From 53cb405a9bd052d0d41270c167eaa84f002f9c71 Mon Sep 17 00:00:00 2001 From: horihiro Date: Sat, 30 Mar 2024 13:30:05 +0900 Subject: [PATCH 05/29] Refactor constants --- content/js/main.js | 90 +++++++++++++++++++++++----------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/content/js/main.js b/content/js/main.js index 787d059..4210af1 100644 --- a/content/js/main.js +++ b/content/js/main.js @@ -1,29 +1,29 @@ (async () => { const w = window; 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'; - const globalStyleId = '__blurring_style'; - const globalStyle = `.${blurredClassName} { + const CLASS_NAME_BLURRED = 'tb-blurred'; + const CLASS_NAME_KEEP = 'tb-keep-this'; + const ATTR_NAME_ORIGINAL_TITLE = 'data-tb-original-title'; + const CLASS_NAME_MASK_CONTAINER = 'tb-mask-container'; + const CLASS_NAME_TEXT_LAYER = 'tb-mask-text-layer'; + const ID_INPUT_CLONE = 'tb-input-clone'; + const ID_GLOBAL_STYLE = '__blurring-style'; + const GLOBAL_STYLE = `.${CLASS_NAME_BLURRED} { filter: blur(5px)!important; } -.${maskContainerClassName} { +.${CLASS_NAME_MASK_CONTAINER} { border: none!important; overflow: hidden!important; } -#${inputCloneId}, .${maskContainerClassName}, .${textLayerClassName} { +#${ID_INPUT_CLONE}, .${CLASS_NAME_MASK_CONTAINER}, .${CLASS_NAME_TEXT_LAYER} { position: absolute!important; border: none!important; overflow: hidden!important; white-space: nowrap!important; } -#${inputCloneId} { +#${ID_INPUT_CLONE} { visibility: hidden!important; white-space-collapse: preserve!important; }`; @@ -44,7 +44,7 @@ const getElementsByNodeValue = (pattern, target, options) => { return Array.prototype.filter.call((target || document.body).childNodes, (n) => { - return !exElmList.includes(n.nodeName.toLowerCase()) && (n.nodeName.toLowerCase() !== 'span' || !(n.classList.contains(blurredClassName))); + return !exElmList.includes(n.nodeName.toLowerCase()) && (n.nodeName.toLowerCase() !== 'span' || !(n.classList.contains(CLASS_NAME_BLURRED))); }).reduce((array, n) => { if (n.nodeName !== "#text") { if (n.shadowRoot) { @@ -119,7 +119,7 @@ let str = ''; let pos = tail; do { - str = `${str}${pos.parentNode.classList.contains(blurredClassName) ? '' : pos.textContent}`; + str = `${str}${pos.parentNode.classList.contains(CLASS_NAME_BLURRED) ? '' : pos.textContent}`; result = inlineFormatting(str).match(pattern); if (result) break; pos = getNextTextNode(pos, e); @@ -134,7 +134,7 @@ str = ''; pos = head; do { - str = `${pos.parentNode.classList.contains(blurredClassName) ? '' : pos.textContent}${str}`; + str = `${pos.parentNode.classList.contains(CLASS_NAME_BLURRED) ? '' : pos.textContent}${str}`; result = inlineFormatting(str).match(pattern); if (result) break; pos = getPreviousTextNode(pos, e); @@ -149,7 +149,7 @@ const reStartWithSpaces = /^(\s+).*/; const blurred1 = document.createElement('span'); const numOfLeftSpacesInTail = reStartWithSpaces.test(tail.textContent) ? tail.textContent.replace(reStartWithSpaces, '$1').length : 0; - blurred1.classList.add(blurredClassName); + blurred1.classList.add(CLASS_NAME_BLURRED); blurred1.textContent = tail.textContent.slice(result.index + numOfLeftSpacesInTail); options?.showValue && blurred1.setAttribute('title', keyword); tail.textContent = tail.textContent.slice(0, result.index + numOfLeftSpacesInTail); @@ -159,7 +159,7 @@ while (pos && pos != head) { if (pos.textContent !== '') { const span = document.createElement('span'); - span.classList.add(blurredClassName); + span.classList.add(CLASS_NAME_BLURRED); options?.showValue && span.setAttribute('title', keyword); pos.parentNode.insertBefore(document.createTextNode(''), pos); pos.parentNode.insertBefore(span, pos); @@ -170,7 +170,7 @@ const blurred2 = document.createElement('span'); const numOfLeftSpacesInHead = reStartWithSpaces.test(head.textContent) ? head.textContent.replace(reStartWithSpaces, '$1').length : 0; const p = head.textContent.trim().length - inlineFormatting(str).length + result.index + result[0].length + numOfLeftSpacesInHead; - blurred2.classList.add(blurredClassName); + blurred2.classList.add(CLASS_NAME_BLURRED); blurred2.textContent = head.textContent.slice(0, p); options?.showValue && blurred2.setAttribute('title', keyword); head.textContent = head.textContent.slice(p); @@ -185,7 +185,7 @@ const blurByRegExpPattern = (pattern, options, target) => { const now = Date.now(); - if (target.classList && target.classList.contains(blurredClassName)) { + if (target.classList && target.classList.contains(CLASS_NAME_BLURRED)) { unblurCore(target); } const targetObjects = getElementsByNodeValue(pattern, target || document.body, options).filter((o) => { @@ -198,7 +198,7 @@ return !a.exact ? 1 : a.splitted ? 1 : -1; }).forEach((o) => { const n = o.node; - if (n.classList.contains(blurredClassName)) return; + if (n.classList.contains(CLASS_NAME_BLURRED)) return; const computedStyle = getComputedStyle(n); const size = Math.floor(parseFloat(computedStyle.fontSize) / 4); @@ -208,8 +208,8 @@ && Array.prototype.every.call(n.childNodes, c => c.nodeName === '#text') && computedStyle.filter === 'none' ) { - n.classList.add(blurredClassName); - n.classList.add(keepClassName); + n.classList.add(CLASS_NAME_BLURRED); + n.classList.add(CLASS_NAME_KEEP); if (options?.showValue) { const originalTitle = n.getAttribute('title'); if (originalTitle) { @@ -235,7 +235,7 @@ textArray.forEach((t) => { const blurredSpan = document.createElement('span'); - blurredSpan.classList.add(blurredClassName); + blurredSpan.classList.add(CLASS_NAME_BLURRED); blurredSpan.textContent = matched.shift(); options?.showValue && blurredSpan.setAttribute('title', o.keyword); if (size > 5) blurredSpan.style.filter = `blur(${size}px)`; @@ -282,10 +282,10 @@ if (!pattern.test(input.value)) return; const clone = (() => { - return this.root.querySelector(`#${inputCloneId}`) || document.createElement('div'); + return this.root.querySelector(`#${ID_INPUT_CLONE}`) || document.createElement('div'); })(); if (!clone.parentNode) { - clone.id = inputCloneId; + clone.id = ID_INPUT_CLONE; this.root.appendChild(clone); } clone.textCotent = ''; @@ -307,16 +307,16 @@ const referenceNode = clone.lastChild.nextSibling; textArray.forEach((t) => { const blurredSpan = document.createElement('span'); - blurredSpan.classList.add(blurredClassName); + blurredSpan.classList.add(CLASS_NAME_BLURRED); blurredSpan.textContent = matched.shift(); if (size > 5) blurredSpan.style.filter = `blur(${size}px)`; clone.insertBefore(blurredSpan, referenceNode); clone.insertBefore(document.createTextNode(t), referenceNode); const mask = document.createElement('div'); - mask.classList.add(maskContainerClassName); + mask.classList.add(CLASS_NAME_MASK_CONTAINER); mask.appendChild(document.createElement('div')); - mask.lastChild.classList.add(textLayerClassName); + mask.lastChild.classList.add(CLASS_NAME_TEXT_LAYER); mask.lastChild.textContent = blurredSpan.textContent; mask.lastChild.style.setProperty('width', '100%'); mask.lastChild.style.setProperty('height', '100%'); @@ -391,8 +391,8 @@ if (observedNodes.includes(observed)) return; const style = document.createElement('style'); - style.innerHTML = globalStyle; - style.id = globalStyleId; + style.innerHTML = GLOBAL_STYLE; + style.id = ID_GLOBAL_STYLE; !observed.querySelector(`#${style.id}`) && (observed == document.body ? document.head : observed).appendChild(style); observedNodes.push(observed); if (!w.__observer) { @@ -421,10 +421,10 @@ }); const inputClone = (() => { - return document.querySelector(`#${inputCloneId}`) || document.createElement('div'); + return document.querySelector(`#${ID_INPUT_CLONE}`) || document.createElement('div'); })(); if (!inputClone.parentNode) { - inputClone.id = inputCloneId; + inputClone.id = ID_INPUT_CLONE; document.body.appendChild(inputClone); } @@ -445,12 +445,12 @@ inputs.length = 0; const m = observedNodes.reduce((array, target) => { - const globalStyle = target.querySelector(`#${globalStyleId}`); - globalStyle && globalStyle.parentNode.removeChild(globalStyle); - const inputClone = target.querySelector(`#${inputCloneId}`); + const GLOBAL_STYLE = target.querySelector(`#${ID_GLOBAL_STYLE}`); + GLOBAL_STYLE && GLOBAL_STYLE.parentNode.removeChild(GLOBAL_STYLE); + const inputClone = target.querySelector(`#${ID_INPUT_CLONE}`); inputClone && inputClone.parentNode.removeChild(inputClone); - array.push(...target.querySelectorAll(`.${blurredClassName}`)); + array.push(...target.querySelectorAll(`.${CLASS_NAME_BLURRED}`)); return array; }, []); observedNodes.length = 0; @@ -465,18 +465,18 @@ }; const unblurCore = (n) => { - if (n.classList.contains(blurredClassName) && n.classList.contains(keepClassName)) { + if (n.classList.contains(CLASS_NAME_BLURRED) && n.classList.contains(CLASS_NAME_KEEP)) { // restore title - const originalTitle = n.getAttribute(originalTitleAttributeName); + const originalTitle = n.getAttribute(ATTR_NAME_ORIGINAL_TITLE); if (originalTitle) { n.setAttribute('title', originalTitle); - n.removeAttribute(originalTitleAttributeName); + n.removeAttribute(ATTR_NAME_ORIGINAL_TITLE); } else n.removeAttribute('title'); // restore class - n.classList.remove(blurredClassName); - n.classList.remove(keepClassName); + n.classList.remove(CLASS_NAME_BLURRED); + n.classList.remove(CLASS_NAME_KEEP); if (n.classList.length == 0) n.removeAttribute('class'); // restore style @@ -516,9 +516,9 @@ const unblurTabTitle = () => { const title = document.querySelector('title'); if (!title) return; - if (title.getAttribute(originalTitleAttributeName)) { - title.textContent = title.getAttribute(originalTitleAttributeName); - title.removeAttribute(originalTitleAttributeName); + if (title.getAttribute(ATTR_NAME_ORIGINAL_TITLE)) { + title.textContent = title.getAttribute(ATTR_NAME_ORIGINAL_TITLE); + title.removeAttribute(ATTR_NAME_ORIGINAL_TITLE); } if (!w.__titleObserver) return; w.__titleObserver.disconnect(); @@ -532,8 +532,8 @@ 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); + if (!target.getAttribute(ATTR_NAME_ORIGINAL_TITLE)) { + target.setAttribute(ATTR_NAME_ORIGINAL_TITLE, title); } } }; From 183ba28c0fa90b92995169e085d104d85994590c Mon Sep 17 00:00:00 2001 From: horihiro Date: Sat, 30 Mar 2024 14:37:07 +0900 Subject: [PATCH 06/29] Improve blur/unblur logic for dynamic change --- content/js/main.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/content/js/main.js b/content/js/main.js index 4210af1..5223549 100644 --- a/content/js/main.js +++ b/content/js/main.js @@ -2,6 +2,7 @@ const w = window; const exElmList = ['html', 'title', 'script', 'noscript', 'style', 'meta', 'link', 'head', 'textarea', '#comment']; const CLASS_NAME_BLURRED = 'tb-blurred'; + const CLASS_PREFIX_BLURRED_GROUP = 'tb-blurred-group-'; const CLASS_NAME_KEEP = 'tb-keep-this'; const ATTR_NAME_ORIGINAL_TITLE = 'data-tb-original-title'; const CLASS_NAME_MASK_CONTAINER = 'tb-mask-container'; @@ -149,7 +150,9 @@ const reStartWithSpaces = /^(\s+).*/; const blurred1 = document.createElement('span'); const numOfLeftSpacesInTail = reStartWithSpaces.test(tail.textContent) ? tail.textContent.replace(reStartWithSpaces, '$1').length : 0; + const groupedClass = `${CLASS_PREFIX_BLURRED_GROUP}${Date.now()}`; blurred1.classList.add(CLASS_NAME_BLURRED); + blurred1.classList.add(groupedClass); blurred1.textContent = tail.textContent.slice(result.index + numOfLeftSpacesInTail); options?.showValue && blurred1.setAttribute('title', keyword); tail.textContent = tail.textContent.slice(0, result.index + numOfLeftSpacesInTail); @@ -160,6 +163,7 @@ if (pos.textContent !== '') { const span = document.createElement('span'); span.classList.add(CLASS_NAME_BLURRED); + span.classList.add(groupedClass); options?.showValue && span.setAttribute('title', keyword); pos.parentNode.insertBefore(document.createTextNode(''), pos); pos.parentNode.insertBefore(span, pos); @@ -171,6 +175,7 @@ const numOfLeftSpacesInHead = reStartWithSpaces.test(head.textContent) ? head.textContent.replace(reStartWithSpaces, '$1').length : 0; const p = head.textContent.trim().length - inlineFormatting(str).length + result.index + result[0].length + numOfLeftSpacesInHead; blurred2.classList.add(CLASS_NAME_BLURRED); + blurred2.classList.add(groupedClass); blurred2.textContent = head.textContent.slice(0, p); options?.showValue && blurred2.setAttribute('title', keyword); head.textContent = head.textContent.slice(p); @@ -185,8 +190,15 @@ const blurByRegExpPattern = (pattern, options, target) => { const now = Date.now(); - if (target.classList && target.classList.contains(CLASS_NAME_BLURRED)) { - unblurCore(target); + if (target.classList && target.classList.contains(CLASS_NAME_BLURRED) && !pattern.test(target.textContent)) { + if (!Array.from(target.classList).some((className) => className.startsWith(CLASS_PREFIX_BLURRED_GROUP))) unblurCore(target); + else { + const groupedClass = Array.from(target.classList).filter((className) => className.startsWith(CLASS_PREFIX_BLURRED_GROUP))[0]; + const blurredGroup = document.querySelectorAll(`.${groupedClass}`); + !pattern.test(Array.from(blurredGroup).map((blurred) => blurred.textContent).join('')) && blurredGroup.forEach((blurred) => { + unblurCore(blurred); + }); + } } const targetObjects = getElementsByNodeValue(pattern, target || document.body, options).filter((o) => { return (Array.prototype.filter.call(o.node.childNodes, (c) => { From 81abc60b2d89183b89f5444dc2d97d45586fd51b Mon Sep 17 00:00:00 2001 From: horihiro Date: Wed, 10 Apr 2024 19:54:05 +0900 Subject: [PATCH 07/29] Fix RegExp for accepting assersions --- popup/js/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/popup/js/main.js b/popup/js/main.js index b3248dc..47d3f85 100644 --- a/popup/js/main.js +++ b/popup/js/main.js @@ -35,7 +35,7 @@ document.addEventListener('DOMContentLoaded', async (e) => { if (regexStr === '') resolve(false); try { const simpler = new RegExp(regexStr).source.replace(/\\.|\[[^\]]*\]/g, "x"); - resolve(/\([^?]/.test(simpler) || /\(\?]+>))/.test(simpler)); } catch { resolve(true); } From bae396fbecb87f2834734e670cfb34767d64a6cf Mon Sep 17 00:00:00 2001 From: horihiro Date: Fri, 12 Apr 2024 12:37:55 +0900 Subject: [PATCH 08/29] Refactor blurring function for improved readability and maintainability --- content/js/main.js | 329 +++++++++++++++++++++------------------------ 1 file changed, 157 insertions(+), 172 deletions(-) diff --git a/content/js/main.js b/content/js/main.js index 5223549..70eeeea 100644 --- a/content/js/main.js +++ b/content/js/main.js @@ -34,49 +34,6 @@ }; const inputs = []; - const getTextContentRecursive = (target, options) => { - const textContent = !target.childNodes ? target.textContent : Array.prototype.reduce.call(target.childNodes, (allTextContent, node) => { - if (options?.exclusives?.nodeNames?.includes(node.nodeName.toLowerCase()) || options?.exclusives?.nodes?.includes(node)) return allTextContent; - if (node.nodeName === '#text') return `${allTextContent}${node.textContent}`; - return `${allTextContent}${getTextContentRecursive(node, options)}`; - }, ''); - return textContent; - }; - - const getElementsByNodeValue = (pattern, target, options) => { - return Array.prototype.filter.call((target || document.body).childNodes, (n) => { - return !exElmList.includes(n.nodeName.toLowerCase()) && (n.nodeName.toLowerCase() !== 'span' || !(n.classList.contains(CLASS_NAME_BLURRED))); - }).reduce((array, n) => { - if (n.nodeName !== "#text") { - if (n.shadowRoot) { - blur(pattern, options, n.shadowRoot); - } - array.push(...getElementsByNodeValue(pattern, n, options)); - const nodearray = array.map(o => o.node); - const textContent = getTextContentRecursive(n, { exclusives: { nodes: nodearray, nodeNames: exElmList } }); - if (pattern.source.length <= 1 || /^(?:\.|(?:\\[^\\])|(?:\[[^\]]+\]))(?:\?|\*|\+|\{,?1\}|\{1,(?:\d+)?\})?$/.test(pattern.source)) return array; - const result = inlineFormatting(textContent).match(pattern); - if (result) { - !nodearray.includes(n) && array.push({ - keyword: result[0], - splitted: true, - node: n - }); - } - return array; - } - const result = inlineFormatting(n.textContent).match(pattern); - if (result) { - array.push({ - keyword: result[0], - exact: result[result.index] === result.input, - node: n.parentNode - }); - } - return array; - }, []); - }; - const getNextTextNode = (e, root) => { if (!e) return null; if (e.firstChild) return e.firstChild.nodeName === '#text' ? e.firstChild : getNextTextNode(e.firstChild, root); @@ -113,79 +70,143 @@ : '' } - const inchworm = (e, pattern, keyword, options) => { - let tail = e.firstChild.nodeName === '#text' ? e.firstChild : getNextTextNode(e.firstChild, e), head = getNextTextNode(tail, e); - let result; + const getElementsToBeBlurred = (pattern, target, options) => { + let textNode = getNextTextNode(target, target); + if (!textNode) return; + let keywordPosition = 0, pos = textNode.textContent.length; do { - let str = ''; - let pos = tail; - do { - str = `${str}${pos.parentNode.classList.contains(CLASS_NAME_BLURRED) ? '' : pos.textContent}`; - result = inlineFormatting(str).match(pattern); - if (result) break; - pos = getNextTextNode(pos, e); - } while (!result && pos); - head = pos; - if (!head) { - tail = getNextTextNode(head, e); - head = getNextTextNode(tail, e); - continue; + const matchDetail = target.textContent.slice(keywordPosition).match(pattern); + if (!matchDetail) break; + // console.log(matchDetail[0]) + keywordPosition += matchDetail.index; + while (pos <= keywordPosition) { + textNode = getNextTextNode(textNode, target); + // console.log( location.href, `"${target.textContent.slice(pos, pos + textNode.textContent.length).replace(/\n/g, '\\n')}"`); + if (!textNode) break; + pos += textNode.textContent.length; } + if (pos <= keywordPosition) { + matchDetail = target.textContent.slice(keywordPosition).match(pattern); + return; + }; - str = ''; - pos = head; - do { - str = `${pos.parentNode.classList.contains(CLASS_NAME_BLURRED) ? '' : pos.textContent}${str}`; - result = inlineFormatting(str).match(pattern); - if (result) break; - pos = getPreviousTextNode(pos, e); - } while (pos); - tail = pos; - if (!tail) { - tail = getNextTextNode(head, e); - head = getNextTextNode(tail, e); + keywordPosition += matchDetail[0].length; + const textNodeArray = []; + textNodeArray.push(textNode); + + while (!textNodeArray.map(t => t.textContent).join('').match(matchDetail[0])) { + textNode = getNextTextNode(textNode, target); + if (!textNode) break; + textNodeArray.push(textNode); + pos += textNode.textContent.length; + } + if (!textNodeArray.map(t => t.textContent).join('').match(pattern)) { + continue + }; + + while (textNodeArray.slice(1).map(t => t.textContent).join('').match(matchDetail[0])) { + textNodeArray.shift(); + } + if (exElmList.includes(textNodeArray.at(0).parentNode.nodeName.toLowerCase()) || exElmList.includes(textNodeArray.at(-1).parentNode.nodeName.toLowerCase())) { continue; } + const value = textNodeArray.map(t => t.textContent).join(''); + const from = { + node: textNodeArray.at(0), + startIndex: (value.match(pattern)?.index || 0) + }; + const to = { + node: textNodeArray.at(-1), + endIndex: textNodeArray.at(-1).length + (value.match(pattern)?.index || 0) + matchDetail[0].length - value.length - 1 + }; + const isBlurred = (node) => { + do { + if (node.classList?.contains(CLASS_NAME_BLURRED)) return true; + node = node.parentNode; + } while (node); + return false; + } + const nodeBeforeBlurred = document.createTextNode(from.node.textContent.slice(0, from.startIndex)); + const nodeAfterBlurred = document.createTextNode(to.node.textContent.slice(to.endIndex + 1)); + const insertNodes = []; + const removeNodes = []; + if (from.node == to.node) { + let prevTextNode = textNode; + textNode = getNextTextNode(textNode, target); + if (!from.node.parentNode + || exElmList.includes(from.node.parentNode.nodeName.toLowerCase()) + || isBlurred(from.node.parentNode)) { + if (!textNode) break; + pos += textNode.textContent.length; + continue; + } - const reStartWithSpaces = /^(\s+).*/; - const blurred1 = document.createElement('span'); - const numOfLeftSpacesInTail = reStartWithSpaces.test(tail.textContent) ? tail.textContent.replace(reStartWithSpaces, '$1').length : 0; - const groupedClass = `${CLASS_PREFIX_BLURRED_GROUP}${Date.now()}`; - blurred1.classList.add(CLASS_NAME_BLURRED); - blurred1.classList.add(groupedClass); - blurred1.textContent = tail.textContent.slice(result.index + numOfLeftSpacesInTail); - options?.showValue && blurred1.setAttribute('title', keyword); - tail.textContent = tail.textContent.slice(0, result.index + numOfLeftSpacesInTail); - tail.parentNode.insertBefore(document.createTextNode(''), tail.nextSibling); - tail.parentNode.insertBefore(blurred1, tail.nextSibling); - pos = getNextTextNode(blurred1.firstChild, e); - while (pos && pos != head) { - if (pos.textContent !== '') { - const span = document.createElement('span'); - span.classList.add(CLASS_NAME_BLURRED); - span.classList.add(groupedClass); - options?.showValue && span.setAttribute('title', keyword); - pos.parentNode.insertBefore(document.createTextNode(''), pos); - pos.parentNode.insertBefore(span, pos); - span.appendChild(pos); + const computedStyle = getComputedStyle(from.node.parentNode); + const size = Math.floor(parseFloat(computedStyle.fontSize) / 4); + if (to.node.textContent === matchDetail[0] && computedStyle.filter === 'none') { + from.node.parentNode.classList.add(CLASS_NAME_BLURRED); + from.node.parentNode.classList.add(CLASS_NAME_KEEP); + if (options?.showValue) { + const originalTitle = from.node.parentNode.getAttribute('title'); + if (originalTitle) { + from.node.parentNode.setAttribute('data-tb-original-title', originalTitle); + } + from.node.parentNode.setAttribute('title', matchDetail[0]); + } + if (size > 5) from.node.parentNode.style.filter += ` blur(${size}px)`; + if (!textNode) break; + pos += textNode.textContent.length; + continue; + } + const nodeBlurred = document.createElement('span'); + nodeBlurred.classList.add(CLASS_NAME_BLURRED); + nodeBlurred.textContent = from.node.textContent.slice(from.startIndex, to.endIndex + 1); + options?.showValue && nodeBlurred.setAttribute('title', matchDetail[0]); + insertNodes.push({ node: nodeBeforeBlurred, refNode: from.node, target: from.node.parentNode }); + insertNodes.push({ node: nodeBlurred, refNode: from.node, target: from.node.parentNode }); + insertNodes.push({ node: nodeAfterBlurred, refNode: from.node, target: from.node.parentNode }); + removeNodes.push(from.node); + } else { + const now = Date.now(); + if (!from.node.parentNode || !to.node.parentNode + || exElmList.includes(from.node.parentNode.nodeName.toLowerCase()) || exElmList.includes(from.node.parentNode.nodeName.toLowerCase()) + || isBlurred(from.node.parentNode) || isBlurred(to.node.parentNode)) return; + const nodeBlurredFrom = document.createElement('span'); + nodeBlurredFrom.classList.add(CLASS_NAME_BLURRED); + nodeBlurredFrom.classList.add(`${CLASS_PREFIX_BLURRED_GROUP}${now}`); + nodeBlurredFrom.textContent = from.node.textContent.slice(from.startIndex); + insertNodes.push({ node: nodeBeforeBlurred, refNode: from.node, target: from.node.parentNode }); + insertNodes.push({ node: nodeBlurredFrom, refNode: from.node, target: from.node.parentNode }); + + let workingTextNode = getNextTextNode(from.node, target); + removeNodes.push(from.node); + while (workingTextNode != to.node) { + const nodeBlurred = document.createElement('span'); + nodeBlurred.textContent = workingTextNode.textContent; + nodeBlurred.classList.add(CLASS_NAME_BLURRED); + nodeBlurred.classList.add(`${CLASS_PREFIX_BLURRED_GROUP}${now}`); + insertNodes.push({ node: nodeBlurred, refNode: workingTextNode, target: workingTextNode.parentNode }); + removeNodes.push(workingTextNode); + workingTextNode = getNextTextNode(workingTextNode, target); } - pos = getNextTextNode(pos, e); + + const nodeBlurredTo = document.createElement('span'); + nodeBlurredTo.classList.add(CLASS_NAME_BLURRED); + nodeBlurredTo.classList.add(`${CLASS_PREFIX_BLURRED_GROUP}${now}`); + nodeBlurredTo.textContent = to.node.textContent.slice(0, to.endIndex + 1); + insertNodes.push({ node: nodeBlurredTo, refNode: to.node, target: to.node.parentNode }); + insertNodes.push({ node: nodeAfterBlurred, refNode: to.node, target: to.node.parentNode }); + removeNodes.push(to.node); } - const blurred2 = document.createElement('span'); - const numOfLeftSpacesInHead = reStartWithSpaces.test(head.textContent) ? head.textContent.replace(reStartWithSpaces, '$1').length : 0; - const p = head.textContent.trim().length - inlineFormatting(str).length + result.index + result[0].length + numOfLeftSpacesInHead; - blurred2.classList.add(CLASS_NAME_BLURRED); - blurred2.classList.add(groupedClass); - blurred2.textContent = head.textContent.slice(0, p); - options?.showValue && blurred2.setAttribute('title', keyword); - head.textContent = head.textContent.slice(p); - head.parentNode.insertBefore(document.createTextNode(''), head); - head.parentNode.insertBefore(blurred2, head); - - tail = getNextTextNode(head, e); - head = getNextTextNode(tail, e); - } while (head && tail); - } + insertNodes.forEach((n) => { + n.target.insertBefore(n.node, n.refNode); + }); + removeNodes.forEach((n) => { + n.parentNode.removeChild(n); + }); + textNode = nodeAfterBlurred; + } while (true); + }; const blurByRegExpPattern = (pattern, options, target) => { const now = Date.now(); @@ -200,62 +221,15 @@ }); } } - const targetObjects = getElementsByNodeValue(pattern, target || document.body, options).filter((o) => { - return (Array.prototype.filter.call(o.node.childNodes, (c) => { - return c.nodeName === '#text' && pattern.test(inlineFormatting(c.textContent)); - }).length > 0 || pattern.test(inlineFormatting(o.node.textContent))) - && getStateOfContentEditable(o.node) !== 'true' - }); - [...new Set(targetObjects)].sort((a) => { - return !a.exact ? 1 : a.splitted ? 1 : -1; - }).forEach((o) => { - const n = o.node; - if (n.classList.contains(CLASS_NAME_BLURRED)) return; - - const computedStyle = getComputedStyle(n); - const size = Math.floor(parseFloat(computedStyle.fontSize) / 4); - - // case of that the element doesn't contain nodes except the matched keyword, - if (o.exact - && Array.prototype.every.call(n.childNodes, c => c.nodeName === '#text') - && computedStyle.filter === 'none' - ) { - n.classList.add(CLASS_NAME_BLURRED); - n.classList.add(CLASS_NAME_KEEP); - if (options?.showValue) { - const originalTitle = n.getAttribute('title'); - if (originalTitle) { - n.setAttribute('data-tb-original-title', originalTitle); - } - n.setAttribute('title', o.keyword); - } - if (size > 5) n.style.filter += ` blur(${size}px)`; - return; - } - if (o.splitted) { - inchworm(n, pattern, o.keyword, options); - return; - } + getElementsToBeBlurred(pattern, target || document.body, options); - const reKeyword = new RegExp(escapeRegExp(o.keyword).replace(/ +/, '\\s+')); - n.childNodes.forEach((c) => { - if (c.nodeName !== "#text" || !reKeyword.test(c.textContent)) return; - const textArray = c.textContent.split(reKeyword); - const referenceNode = c.nextSibling; - const matched = c.textContent.match(new RegExp(reKeyword.source, `g${reKeyword.flags}`)); - c.textContent = textArray.shift(); - - textArray.forEach((t) => { - const blurredSpan = document.createElement('span'); - blurredSpan.classList.add(CLASS_NAME_BLURRED); - blurredSpan.textContent = matched.shift(); - options?.showValue && blurredSpan.setAttribute('title', o.keyword); - if (size > 5) blurredSpan.style.filter = `blur(${size}px)`; - c.parentNode.insertBefore(blurredSpan, referenceNode); - c.parentNode.insertBefore(document.createTextNode(t), referenceNode); - }); + const blurInShadowRoot = (target) => { + target.shadowRoot && blur(pattern, options, target.shadowRoot); + target.childNodes.forEach((n) => { + n.nodeName !== '#text' && blurInShadowRoot(n); }); - }) + }; + blurInShadowRoot(target); options?.blurInput && ['HTMLBodyElement', 'ShadowRoot'].includes(Object.prototype.toString.call(target).slice(8, -1)) && [...target.querySelectorAll('input')].reduce((inputs, input) => { const inputObj = (() => { @@ -369,6 +343,7 @@ mask.style.setProperty('border', 'none'); mask.style.setProperty('background-color', getBackgroundColorAlongDOMTree(input)); + mask.lastChild.classList.add(CLASS_NAME_BLURRED); e.isTrusted && mask.style.setProperty('display', 'none'); inputObj.masks[patternStr].push(mask); @@ -423,15 +398,17 @@ array.push(record.target); return array; }, []); + w.__observer.disconnect(); targets.forEach(target => blurByRegExpPattern(pattern, options, target)); + observedNodes.forEach((target) => { + w.__observer.observe(target, { + childList: true, + subtree: true, + characterData: true + }); + }); }); } - w.__observer.observe(observed, { - childList: true, - subtree: true, - characterData: true - }); - const inputClone = (() => { return document.querySelector(`#${ID_INPUT_CLONE}`) || document.createElement('div'); })(); @@ -441,15 +418,23 @@ } blurByRegExpPattern(pattern, options, observed); + w.__observer.observe(observed, { + childList: true, + subtree: true, + characterData: true + }); }; const unblur = () => { if (!w.__observer) return; w.__observer.disconnect(); delete w.__observer - inputs.map(i => i.masks).forEach((masks) => { - for (let pattern in masks) { - masks[pattern].forEach((mask) => { + inputs.forEach((inputObj) => { + inputObj.element.removeEventListener('input', inputObj.inputHandler); + inputObj.element.removeEventListener('focus', inputOnFocus); + inputObj.element.removeEventListener('blur', inputOnBlur); + for (let pattern in inputObj.masks) { + inputObj.masks[pattern].forEach((mask) => { mask.parentNode.removeChild(mask); }) } @@ -475,7 +460,7 @@ }); console.debug(`Took ${Date.now() - now} ms`) }; - + const unblurCore = (n) => { if (n.classList.contains(CLASS_NAME_BLURRED) && n.classList.contains(CLASS_NAME_KEEP)) { // restore title From 0eea76d46ba62cbdeeb02fbc45b671a7bc7cfba8 Mon Sep 17 00:00:00 2001 From: horihiro Date: Fri, 12 Apr 2024 20:33:18 +0900 Subject: [PATCH 09/29] Add feature for setting exclusion URL list --- background/service-worker.js | 48 ++++------- content/js/main.js | 32 +++++++- manifest.json | 5 +- popup/css/main.css | 96 ++++++++++++++++++++-- popup/js/main.js | 153 ++++++++++++++++++++++------------- popup/main.html | 29 ++++--- 6 files changed, 256 insertions(+), 107 deletions(-) diff --git a/background/service-worker.js b/background/service-worker.js index 60c5c35..a779aca 100644 --- a/background/service-worker.js +++ b/background/service-worker.js @@ -1,35 +1,21 @@ -// console.log('Start service-worker.js'); +console.log('Start service-worker.js'); -// chrome.runtime.onConnect.addListener((port) => { -// if (port.name !== 'updateKeywords') return; +const escapeRegExp = (str) => { + return str.replace(/([\(\)\{\}\+\*\?\[\]\.\^\$\|\\\/])/g, '\\$1'); +}; -// chrome.storage.onChanged.addListener(async (changes, area) => { -// if (area !== 'local' || !changes.keywords && !changes.status) return; +chrome.runtime.onConnect.addListener((port) => { + console.debug(`onConnect: port ${JSON.stringify(port)}`); -// try { -// port.postMessage({ -// keywords: changes.keywords?.newValue || undefined, -// status: changes.status?.newValue || undefined -// }); -// } catch (e) { -// console.error(e); -// } -// }); -// }); + port.onMessage.addListener(async (message/* , sender, sendResponse */) => { + console.log(message); + const storageValue = await chrome.storage.local.get(['urlsInCurrentTab']); + const urls = (storageValue).urlsInCurrentTab || []; + await chrome.storage.local.set({ urlsInCurrentTab: [...urls, `^${escapeRegExp(message)}$`] }); -// chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { -// if (!sender.tab || request.request !== "getKeywordsToBeBlurred") return; - -// chrome.storage.local.get(["keywords"], (k) => { -// chrome.storage.local.get(["status"], (s) => { -// sendResponse({ keywords: k.keywords, status: s.status }); -// }); -// }); -// return true; -// }); - -// // Workaround for keeping service worker active -// // `Bug exploit` in https://stackoverflow.com/questions/66618136/persistent-service-worker-in-chrome-extension#answer-66618269 -// const keepAlive = () => setInterval(chrome.runtime.getPlatformInfo, 20e3); -// chrome.runtime.onStartup.addListener(keepAlive); -// keepAlive(); \ No newline at end of file + }); + port.onDisconnect.addListener((port) => { + console.debug(JSON.stringify(port)); + }); + port.postMessage({ 'type': 'connected', tab: port.sender.tab }); +}); diff --git a/content/js/main.js b/content/js/main.js index 5223549..f357fee 100644 --- a/content/js/main.js +++ b/content/js/main.js @@ -28,6 +28,27 @@ visibility: hidden!important; white-space-collapse: preserve!important; }`; + + let port = null; + const messageQueue = []; + const onMessage = async (message/* , sender, sendResponse */) => { + if (messageQueue.length > 0) await send2Background(); + } + const send2Background = async (message) => { + const msg = messageQueue.shift() || message; + try { + await port.postMessage(msg); + } catch { + messageQueue.push(msg); + port = chrome.runtime.connect({ name: 'text-blurrer' }); + port.onMessage.addListener(onMessage); + } + }; + + chrome.runtime.onMessage.addListener(async (request) => { + await send2Background(location.href); + }); + const getStateOfContentEditable = (element) => { if (element.contentEditable && element.contentEditable !== 'inherit') return element.contentEditable; return element.parentNode ? getStateOfContentEditable(element.parentNode) : ''; @@ -475,7 +496,7 @@ }); console.debug(`Took ${Date.now() - now} ms`) }; - + const unblurCore = (n) => { if (n.classList.contains(CLASS_NAME_BLURRED) && n.classList.contains(CLASS_NAME_KEEP)) { // restore title @@ -590,16 +611,19 @@ chrome.storage.onChanged.addListener(async (changes, area) => { if (area !== 'local') return; - const { status, keywords, mode, matchCase, showValue, blurInput, blurTitle } = (await chrome.storage.local.get(['status', 'keywords', 'mode', 'matchCase', 'showValue', 'blurInput', 'blurTitle'])); + const { status, keywords, mode, matchCase, showValue, blurInput, blurTitle, exclusionUrls } = (await chrome.storage.local.get(['status', 'keywords', 'mode', 'matchCase', 'showValue', 'blurInput', 'blurTitle', 'exclusionUrls'])); unblur(); unblurTabTitle(); if (status === 'disabled' || !keywords || keywords.trim() === '') return; + if (exclusionUrls && exclusionUrls.split(/\n/).length > 0 && exclusionUrls.split(/\n/).some((url) => new RegExp(url).test(location.href))) return; + const pattern = keywords2RegExp(keywords, mode, !!matchCase); blur(pattern, { showValue, blurInput }); blurTitle && blurTabTitle(pattern); }); - const { status, keywords, mode, matchCase, showValue, blurInput, blurTitle } = (await chrome.storage.local.get(['status', 'keywords', 'mode', 'matchCase', 'showValue', 'blurInput', 'blurTitle'])); + const { status, keywords, mode, matchCase, showValue, blurInput, blurTitle, exclusionUrls } = (await chrome.storage.local.get(['status', 'keywords', 'mode', 'matchCase', 'showValue', 'blurInput', 'blurTitle', 'exclusionUrls'])); if (status === 'disabled' || !keywords || keywords.trim() === '') return; + if (exclusionUrls && exclusionUrls.split(/\n/).length > 0 && exclusionUrls.split(/\n/).some((url) => new RegExp(url).test(location.href))) return; window.addEventListener('resize', () => { inputs.forEach((input) => { input.element.dispatchEvent(new InputEvent('input', { data: input.value })); @@ -607,5 +631,5 @@ }) const pattern = keywords2RegExp(keywords, mode, !!matchCase); blur(pattern, { showValue, blurInput }); - blurTitle && blurTabTitle(pattern) + blurTitle && blurTabTitle(pattern); })(); \ No newline at end of file diff --git a/manifest.json b/manifest.json index eb5749d..cdac053 100644 --- a/manifest.json +++ b/manifest.json @@ -11,6 +11,9 @@ "default_icon": "img/icon128.png", "default_popup": "popup/main.html" }, + "background": { + "service_worker": "background/service-worker.js" + }, "content_scripts": [ { "all_frames": true, @@ -23,4 +26,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/popup/css/main.css b/popup/css/main.css index 101a93a..833c6ad 100644 --- a/popup/css/main.css +++ b/popup/css/main.css @@ -17,7 +17,7 @@ textarea { border: none; color: #fff; resize: vertical; - max-height: 490px; + max-height: 420px; border: 1px solid rgb(170, 170, 170); border-radius: 3px; @@ -81,7 +81,7 @@ input[type="checkbox"] { } .checkbox-container { - margin-top: -30px; + /* margin-top: -30px; */ } input[type="checkbox"].custom-checkbox { @@ -167,7 +167,6 @@ input[type="checkbox"]:checked.blur-title+span::before { } #applyButton { - margin-top: 10px; display: flex; justify-content: flex-end; align-self: flex-end; @@ -185,17 +184,104 @@ input[type="checkbox"]:checked.blur-title+span::before { } div#linkContainer { - position: absolute; bottom: 10px; left: 10px; font-size: small; + width: inherit; } + div#linkContainer a { color: #1191ff; - text-decoration:underline; + text-decoration: underline; } div#linkContainer a>img { filter: invert(44%) sepia(75%) saturate(3284%) hue-rotate(191deg) brightness(102%) contrast(100%); height: 20px; } + +textarea#exclusionInput { + margin-top: 7px; +} + +div.footer { + display: flex; + justify-content: space-between; + width: 100%; + margin-top: 10px; +} + +.tab-label-heading:not(:last-of-type) .tab-label { + margin-right: 2px !important; +} + +input[name="tab-radio"] { + display: none; +} + +.tab-label { + display: block; + top: 2px; + position: relative; + float: left; + color: #525252 !important; + cursor: pointer !important; + padding: 4px 8px 1px 8px !important; + border-radius: 6px 6px 0 0; + border-style: solid; + border-color: #525252 #525252 white #525252 !important; + border-bottom-width: 1px; + border-top-width: 1px; + border-left-width: 1px; + border-right-width: 1px; + z-index: 0; + background-color: #333; +} + +input:checked+h4>.tab-label { + color: unset !important; + border-color: unset !important; + border-bottom-width: 0px; + padding-bottom: 4px !important; + top: 0px; + z-index: 2; +} + +.tab-panel { + padding: 30px 5px 5px 5px; + display: none; + z-index: -1; +} + +.tab-panel::after { + position: absolute; + top: 71px; + bottom: 38px; + left: 8px; + right: 8px; + border: 1px solid white; + content: ''; + z-index: 1; + border-radius: 0px 0px 5px 5px; + pointer-events: none; +} + +#tab-keywords:checked~#tab-panel-keywords, +#tab-exclusion:checked~#tab-panel-exclusion { + display: block; +} + +div.footer { + display: flex; + justify-content: space-between; + width: 100%; +} + +span#addUrlsInCurrentTab { + color: #1191ff; + display: flex; + justify-content: flex-end; + width: 100%; + margin-top: -10px; + cursor: pointer; +} \ No newline at end of file diff --git a/popup/js/main.js b/popup/js/main.js index b3248dc..6d80cfa 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, blurTitle } = (await chrome.storage.local.get(['status', 'keywords', 'mode', 'matchCase', 'showValue', 'blurInput', 'blurTitle'])); + const { status, keywords, mode, matchCase, showValue, blurInput, blurTitle, exclusionUrls } = (await chrome.storage.local.get(['status', 'keywords', 'mode', 'matchCase', 'showValue', 'blurInput', 'blurTitle', 'exclusionUrls'])); const applyButton = document.querySelector('#applyButton'); const patternInput = document.querySelector('#patternInput'); @@ -10,6 +10,8 @@ document.addEventListener('DOMContentLoaded', async (e) => { const blurInputCheckbox = document.querySelector('#blurInputCheckbox'); const blurTitleCheckbox = document.querySelector('#blurTitleCheckbox'); const _bufferTextArea = document.querySelector('#_bufferTextArea'); + const addUrlsInCurrentTab = document.querySelector('#addUrlsInCurrentTab'); + const exclusionInput = document.querySelector('#exclusionInput'); const COLOR_DEFAULT = getComputedStyle(_bufferTextArea).getPropertyValue('background-color'); const COLOR_WARNING = '#FFA500'; @@ -22,14 +24,32 @@ document.addEventListener('DOMContentLoaded', async (e) => { const textAreaLineHeight = parseInt(styleTextArea.getPropertyValue('line-height')); let savedKeywords = ''; + let savedExclusionUrls = '' let savedMatchCase = false; let savedMode = false; let savedShowValue = false; let savedBlurInput = false; let savedBlurTitle = false; - let validationResults = []; - let pointedRow = -1; + const validationResults = {}; + const onStorageChanged = async (changes, area) => { + if (area !== 'local' || !changes?.urlsInCurrentTab) return; + + const oldValue = exclusionInput.value.split(/\n/); + const newValue = Array.from(new Set(oldValue.concat([...changes.urlsInCurrentTab.newValue.sort()]).filter((l, i, a) => l !== ''))); + if (oldValue.sort().join('\n') === newValue.concat().sort().join('\n')) return; + exclusionInput.value = newValue.join('\n'); + await renderBackground({ target: exclusionInput }); + applyButton.disabled = false; + }; + chrome.storage.onChanged.addListener(onStorageChanged); + addUrlsInCurrentTab.addEventListener('click', async (e) => { + chrome.storage.local.set({ + 'urlsInCurrentTab': [] + }); + const tabs = await chrome.tabs.query({ active: true, currentWindow: true }); + await chrome.tabs.sendMessage(tabs[0].id, { message: 'requestUrl' }); + }); const hasCaptureGroups = (regexStr) => { return new Promise((resolve) => { if (regexStr === '') resolve(false); @@ -79,14 +99,13 @@ document.addEventListener('DOMContentLoaded', async (e) => { _bufferTextArea.value = text; const result = Math.floor(_bufferTextArea.scrollHeight / lh); - if (result == 0) result = 1; - resolve(result); + resolve(result == 0 ? 1 : result); }); }; - const validateLines = async (lines, onlyLineCounting) => { + const validateLines = async (textarea, lines, onlyLineCounting) => { return await lines.reduce(async (prev, curr) => { - const numOfLine = await getLineCountForRenderedText(patternInput, curr); + const numOfLine = await getLineCountForRenderedText(textarea, curr); const array = await prev; const result = { numOfLine, @@ -108,8 +127,8 @@ document.addEventListener('DOMContentLoaded', async (e) => { }, []); } - const renderBackground = async () => { - const lines = patternInput.value.split(/\n/); + const renderBackground = async (e) => { + const lines = e.target.value.split(/\n/); if (lines.length == 0) { applyButton.disabled = false; return; @@ -117,21 +136,22 @@ document.addEventListener('DOMContentLoaded', async (e) => { if (regexpCheckbox.checked) { applyButton.disabled = false; } - validationResults = await validateLines(lines, !regexpCheckbox.checked); - applyButton.disabled = !validationResults.every(r => r.isValid) || - ( - patternInput.value === savedKeywords && - caseCheckbox.checked === savedMatchCase && - showValueCheckbox.checked === savedShowValue && - regexpCheckbox.checked === savedMode && - blurInputCheckbox.checked === savedBlurInput && - blurTitleCheckbox.checked === savedBlurTitle - ); + validationResults[e.target.id] = await validateLines(e.target, lines, !regexpCheckbox.checked); + applyButton.disabled = !validationResults[e.target.id].every(r => r.isValid) || + ( + patternInput.value === savedKeywords && + exclusionInput.value === savedExclusionUrls && + caseCheckbox.checked === savedMatchCase && + showValueCheckbox.checked === savedShowValue && + regexpCheckbox.checked === savedMode && + blurInputCheckbox.checked === savedBlurInput && + blurTitleCheckbox.checked === savedBlurTitle + ); const re = /\*(\d+)( - [\d.]+px\))$/; - const bgColors = validationResults.reduce((prev, curr, pos, array) => { + const bgColors = validationResults[e.target.id].reduce((prev, curr, pos, array) => { const backgroundColor = curr.isValid ? (!curr.reason ? COLOR_DEFAULT : COLOR_WARNING) : COLOR_ERROR; if (pos == 0) { - prev.push(`${backgroundColor} calc(var(--l)*0 - ${patternInput.scrollTop}px) calc(var(--l)*${curr.numOfLine} - ${patternInput.scrollTop}px)`); + prev.push(`${backgroundColor} calc(var(--l)*0 - ${e.target.scrollTop}px) calc(var(--l)*${curr.numOfLine} - ${e.target.scrollTop}px)`); return prev; } const start = parseInt(prev[prev.length - 1].match(re)[1]); @@ -139,17 +159,17 @@ document.addEventListener('DOMContentLoaded', async (e) => { prev[prev.length - 1] = prev[prev.length - 1].replace(re, `*${start + curr.numOfLine}$2`); return prev; } - prev.push(`${backgroundColor} calc(var(--l)*${start} - ${patternInput.scrollTop}px) calc(var(--l)*${start + curr.numOfLine} - ${patternInput.scrollTop}px)`); + prev.push(`${backgroundColor} calc(var(--l)*${start} - ${e.target.scrollTop}px) calc(var(--l)*${start + curr.numOfLine} - ${e.target.scrollTop}px)`); return prev; }, []); if (bgColors.length > 0) { const start = parseInt(bgColors[bgColors.length - 1].match(re)[1]); - bgColors.push(`${COLOR_DEFAULT} calc(var(--l)*${start} - ${patternInput.scrollTop}px) calc(var(--l)*${start + 1} - ${patternInput.scrollTop}px)`); - patternInput.setAttribute('rows', start > minRowTextArea ? start : minRowTextArea); + bgColors.push(`${COLOR_DEFAULT} calc(var(--l)*${start} - ${e.target.scrollTop}px) calc(var(--l)*${start + 1} - ${e.target.scrollTop}px)`); + e.target.setAttribute('rows', start > minRowTextArea ? start : minRowTextArea); } - document.querySelector('head > style').innerHTML = ` -textarea#${patternInput.id} { + document.querySelector(`head > style#style-${e.target.id}`).innerHTML = ` +textarea#${e.target.id} { background: linear-gradient( ${bgColors.join(',\n ')} ) 0 8px no-repeat, ${COLOR_DEFAULT}; @@ -160,6 +180,7 @@ textarea#${patternInput.id} { await chrome.storage.local.set({ 'status': !statusCheckbox.checked ? 'disabled' : '', 'keywords': patternInput.value, + 'exclusionUrls': exclusionInput.value.split(/\n/).filter(l => l !== '').join('\n'), 'mode': regexpCheckbox.checked ? 'regexp' : 'text', 'matchCase': caseCheckbox.checked, 'showValue': showValueCheckbox.checked, @@ -168,6 +189,7 @@ textarea#${patternInput.id} { }); patternInput.focus(); savedKeywords = patternInput.value; + savedExclusionUrls = exclusionInput.value.split(/\n/).filter(l => l !== '').join('\n'); savedMode = regexpCheckbox.checked; savedMatchCase = caseCheckbox.checked; savedShowValue = showValueCheckbox.checked; @@ -180,20 +202,23 @@ textarea#${patternInput.id} { caseCheckbox.disabled = showValueCheckbox.disabled = blurInputCheckbox.disabled = - blurTitleCheckbox.disabled = + blurTitleCheckbox.disabled = regexpCheckbox.disabled = - patternInput.disabled = !e.target.checked; - applyButton.disabled = !e.target.checked || !validationResults.every(r => r.isValid); + patternInput.disabled = + exclusionInput.disabled = !e.target.checked; + applyButton.disabled = !e.target.checked || !validationResults[e.target.id].every(r => r.isValid); await chrome.storage.local.set({ "status": !e.target.checked ? 'disabled' : '' }); if (!e.target.checked) { - document.querySelector('head > style').innerHTML = ''; + document.querySelector(`head > style#style-${patternInput.id}`).innerHTML = ''; + document.querySelector(`head > style#style-${exclusionInput.id}`).innerHTML = ''; return; } - await renderBackground(); + await renderBackground({ target: patternInput }); + await renderBackground({ target: exclusionInput }); patternInput.focus(); }); @@ -202,53 +227,65 @@ textarea#${patternInput.id} { patternInput.style.background = COLOR_DEFAULT; patternInput.style.background = ''; - await renderBackground(); + await renderBackground({ target: patternInput }); }); caseCheckbox.addEventListener('change', async (e) => { - await renderBackground(); - patternInput.focus(); + await renderBackground({ target: patternInput }); }); showValueCheckbox.addEventListener('change', async (e) => { - await renderBackground(); + await renderBackground({ target: patternInput }); patternInput.focus(); }); blurInputCheckbox.addEventListener('change', async (e) => { - await renderBackground(); + await renderBackground({ target: patternInput }); patternInput.focus(); }); blurTitleCheckbox.addEventListener('change', async (e) => { - await renderBackground(); + await renderBackground({ target: patternInput }); patternInput.focus(); }); - patternInput.addEventListener('scroll', renderBackground); - patternInput.addEventListener('scroll', () => { - if (patternInput.scrollLeft < textAreaPaddingLeft) patternInput.scrollLeft = 0; - if (patternInput.scrollTop < textAreaPaddingTop) patternInput.scrollTop = 0; - }); - patternInput.addEventListener('input', renderBackground); - patternInput.addEventListener('mousemove', (e) => { - if (e.offsetY + patternInput.scrollTop - textAreaPaddingTop < 0) return; - const row = parseInt((e.offsetY + patternInput.scrollTop - textAreaPaddingTop) / textAreaLineHeight) + 1; - if (pointedRow == row) return; - pointedRow = row; - validationResults.reduce((prev, curr) => { + const onScroll = (e) => { + if (e.target.scrollLeft < textAreaPaddingLeft) e.target.scrollLeft = 0; + if (e.target.scrollTop < textAreaPaddingTop) e.target.scrollTop = 0; + } + const onMouseMove = (e) => { + if (e.offsetY + e.target.scrollTop - textAreaPaddingTop < 0) return; + const row = parseInt((e.offsetY + e.target.scrollTop - textAreaPaddingTop) / textAreaLineHeight) + 1; + if (e.target.pointedRow == row) return; + e.target.pointedRow = row; + validationResults[e.target.id].reduce((prev, curr) => { if (prev < 0) return -1; prev -= curr.numOfLine; if (prev > 0) { - patternInput.title = ''; + e.target.title = ''; return prev; } - patternInput.setAttribute('title', curr.reason || ''); + e.target.setAttribute('title', curr.reason || ''); return -1; }, row); - }, false); - patternInput.addEventListener('mouseout', () => { - pointedRow = -1; - }); + }; + const onMouseOut = (e) => { + e.target.pointedRow = -1; + }; + document.head.appendChild(document.createElement('style')); + document.head.lastChild.setAttribute('id', `style-${patternInput.id}`); + patternInput.addEventListener('scroll', renderBackground); + patternInput.addEventListener('scroll', onScroll); + patternInput.addEventListener('input', renderBackground); + patternInput.addEventListener('mousemove', onMouseMove, false); + patternInput.addEventListener('mouseout', onMouseOut); + + document.head.appendChild(document.createElement('style')); + document.head.lastChild.setAttribute('id', `style-${exclusionInput.id}`); + exclusionInput.addEventListener('scroll', renderBackground); + exclusionInput.addEventListener('scroll', onScroll); + exclusionInput.addEventListener('input', renderBackground); + exclusionInput.addEventListener('mousemove', onMouseMove, false); + exclusionInput.addEventListener('mouseout', onMouseOut); statusCheckbox.checked = status !== 'disabled'; savedMatchCase = caseCheckbox.checked = matchCase; @@ -257,7 +294,7 @@ textarea#${patternInput.id} { savedBlurTitle = blurTitleCheckbox.checked = blurTitle; savedMode = regexpCheckbox.checked = mode === 'regexp'; savedKeywords = patternInput.value = keywords || ''; - + savedExclusionUrls = exclusionInput.value = exclusionUrls || ''; caseCheckbox.disabled = blurInputCheckbox.disabled = @@ -265,11 +302,13 @@ textarea#${patternInput.id} { showValueCheckbox.disabled = regexpCheckbox.disabled = patternInput.disabled = + exclusionInput.disabled = applyButton.disabled = !statusCheckbox.checked; patternInput.focus(); if (statusCheckbox.checked) { - await renderBackground(patternInput.value.split(/\n/)); + await renderBackground({ target: patternInput }); + await renderBackground({ target: exclusionInput }); } applyButton.disabled = true; }); diff --git a/popup/main.html b/popup/main.html index ad21938..a28b68c 100644 --- a/popup/main.html +++ b/popup/main.html @@ -5,17 +5,9 @@ Text Blurrer Popup - -
-
+ +

+ +

+ +
+
+ + +
+ - +

- +

From 524903c40b90d0fa0fdd0890a70f783385870ba5 Mon Sep 17 00:00:00 2001 From: horihiro Date: Sun, 14 Apr 2024 11:55:07 +0900 Subject: [PATCH 22/29] Update for Context Munu feature --- README.md | 15 ++++++++++++++- img/icon16.png | Bin 0 -> 562 bytes manifest.json | 4 ++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 img/icon16.png diff --git a/README.md b/README.md index 3187348..a82f75f 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,18 @@ If you want NOT to blur keywords in specific sites/frames, you can specify URLs You can add URLs on the current tab to the list by clicking `+ Add URLs in the current tab` on the popup. +## Context menu for adding keyword (v0.2.0 or later) + +Simple way to add the blurring keyword. + +![image](https://github.com/horihiro/TextBlurrer-ChromeExtension/assets/4566555/7b26db8b-efa9-422d-8750-10fd71550318) + + +## Shortcut keys on popup (v0.2.0 or later) + + - Ctrl / + s: applying keywords/url patterns change (i.e. pressing `Apply` button) + - Shift + Alt + f: removing empty lines in active textarea + # Try this This extension can be installed from [Chrome Web Store](https://chrome.google.com/webstore/detail/text-blurrer/mbikojdgkmpjfackcmiliemgmkdkbbcl). @@ -50,8 +62,9 @@ Refactoring blurring logic to improve performance and maintainability. - New features - Disable blurring on listed sites on [Exclusion URL pattern list](#exclusion-url-list-v020-or-later) - - Add shortcut key on popup : + - Add shortcut keys on popup : - Ctrl / + s: applying keywords/url patterns change + - Shift + Alt + f: removing empty lines in active textarea - Bug fixes - Improve title masking ([#38](https://github.com/horihiro/TextBlurrer-ChromeExtension/issues/38), [#39](https://github.com/horihiro/TextBlurrer-ChromeExtension/issues/39)) - Improve unblurring on updating text node by javascript ([#40](https://github.com/horihiro/TextBlurrer-ChromeExtension/issues/40)) diff --git a/img/icon16.png b/img/icon16.png new file mode 100644 index 0000000000000000000000000000000000000000..f1c0f6bdda87ce0f5280c2af88aae90ac8883733 GIT binary patch literal 562 zcmV-20?qx2P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vG`zyJU;zyT;^TH62s0mn&1K~y+TrBh2% z6hRPu)&F6JVFV;F6bX#boo8Xo5xjs$@e*#h0xK?nD;64|h9L%m%rHG&-PPT`SB(rT zY!>89&2(i|zI-n;+q5)1!g>K39d%zAIrMY0ml^JR0T#AKRkx_?24P?jg#ldC!a43k zgDehVe*P}nlP}Yokfp7UW4zoNATMfMFPsO+k`P5%BPa0cN`$AA6z25Xto?BK*$3?~ z;Ox@ER$a#T2d^>9E1X?e6jkjREUW_w;rZqOyW1nl)G(#h7p< zQP2V0nlJUzB!h&=#`wxBdmI@IvychH?!3F&nHq8zLv1Wy8jyrg8UhHUNu z7@uj`Rp(k<%`05aO7ERedq!ER#gu-_cHRT9B~kB8g?g^G8Gh8Vy=OyguV Date: Sun, 14 Apr 2024 12:00:16 +0900 Subject: [PATCH 23/29] Fix typo in README.md and service-worker.js --- README.md | 3 ++- background/service-worker.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a82f75f..cfec595 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ You can add URLs on the current tab to the list by clicking `+ Add URLs in the c ## Context menu for adding keyword (v0.2.0 or later) -Simple way to add the blurring keyword. +Simple way to add the blurry keyword. ![image](https://github.com/horihiro/TextBlurrer-ChromeExtension/assets/4566555/7b26db8b-efa9-422d-8750-10fd71550318) @@ -62,6 +62,7 @@ Refactoring blurring logic to improve performance and maintainability. - New features - Disable blurring on listed sites on [Exclusion URL pattern list](#exclusion-url-list-v020-or-later) + - Add [Context Menu for adding the blurry keywords](#context-menu-for-adding-keyword-v020-or-later) - Add shortcut keys on popup : - Ctrl / + s: applying keywords/url patterns change - Shift + Alt + f: removing empty lines in active textarea diff --git a/background/service-worker.js b/background/service-worker.js index d5e5a33..8c635d9 100644 --- a/background/service-worker.js +++ b/background/service-worker.js @@ -5,7 +5,7 @@ const escapeRegExp = (str) => { chrome.runtime.onInstalled.addListener(() => { chrome.contextMenus.create({ id: 'add_keyword', - title: 'Add this as blurring keyword', + title: 'Add this as blurry keyword', contexts: ['selection'] }); }); From ff51280682e53332034b5d603a0183acae4d6563 Mon Sep 17 00:00:00 2001 From: horihiro Date: Wed, 17 Apr 2024 13:26:42 +0900 Subject: [PATCH 24/29] Separate and commonize some functions --- background/service-worker.js | 4 +--- content/js/diff.min.js | 1 + content/js/main.js | 6 ++---- manifest.json | 10 ++++++++++ popup/js/main.js | 11 +++++++---- popup/main.html | 2 +- util/common.js | 3 +++ 7 files changed, 25 insertions(+), 12 deletions(-) create mode 100644 content/js/diff.min.js create mode 100644 util/common.js diff --git a/background/service-worker.js b/background/service-worker.js index 8c635d9..3d0ce27 100644 --- a/background/service-worker.js +++ b/background/service-worker.js @@ -1,6 +1,4 @@ -const escapeRegExp = (str) => { - return str.replace(/([\(\)\{\}\+\*\?\[\]\.\^\$\|\\])/g, '\\$1'); -}; +import { escapeRegExp } from '../util/common.js'; chrome.runtime.onInstalled.addListener(() => { chrome.contextMenus.create({ diff --git a/content/js/diff.min.js b/content/js/diff.min.js new file mode 100644 index 0000000..078bcc5 --- /dev/null +++ b/content/js/diff.min.js @@ -0,0 +1 @@ +!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((e=e||self).Diff={})}(this,function(e){"use strict";function t(){}t.prototype={diff:function(s,a,e){var n,t=2=c&&f<=v+1)return d([{value:this.join(a),count:a.length}]);var m=-1/0,g=1/0;function w(){for(var e=Math.max(m,-p);e<=Math.min(g,p);e+=2){var n=void 0,t=h[e-1],r=h[e+1];t&&(h[e-1]=void 0);var i,o=!1;r&&(i=r.oldPos-e,o=r&&0<=i&&i=c&&f<=v+1)return d(function(e,n,t,r,i){var o,l=[];for(;n;)l.push(n),o=n.previousComponent,delete n.previousComponent,n=o;l.reverse();for(var s=0,a=l.length,u=0,d=0;se.length?t:e}),p.value=e.join(c)):p.value=e.join(t.slice(u,u+p.count)),u+=p.count,p.added||(d+=p.count))}var h=l[a-1];1=c&&(g=Math.min(g,e-1)),f<=v+1&&(m=Math.max(m,e+1))}else h[e]=void 0}p++}if(r)!function e(){setTimeout(function(){return il?r():void(w()||e())},0)}();else for(;p<=i&&Date.now()<=l;){var y=w();if(y)return y}},addToPath:function(e,n,t,r){var i=e.lastComponent;return i&&i.added===n&&i.removed===t?{oldPos:e.oldPos+r,lastComponent:{count:i.count+1,added:n,removed:t,previousComponent:i.previousComponent}}:{oldPos:e.oldPos+r,lastComponent:{count:1,added:n,removed:t,previousComponent:i}}},extractCommon:function(e,n,t,r){for(var i=n.length,o=t.length,l=e.oldPos,s=l-r,a=0;s+1e.length)&&(n=e.length);for(var t=0,r=new Array(n);t=c.length-2&&a.length<=f.context&&(i=/\n$/.test(u),o=/\n$/.test(d),l=0==a.length&&m.length>r.oldLines,!i&&l&&0e.length)return!1;for(var t=0;t"):i.removed&&t.push(""),t.push((n=i.value,n.replace(/&/g,"&").replace(//g,">").replace(/"/g,"""))),i.added?t.push(""):i.removed&&t.push("")}return t.join("")},e.createPatch=function(e,n,t,r,i,o){return b(e,e,n,t,r,i,o)},e.createTwoFilesPatch=b,e.diffArrays=function(e,n,t){return g.diff(e,n,t)},e.diffChars=function(e,n,t){return r.diff(e,n,t)},e.diffCss=function(e,n,t){return d.diff(e,n,t)},e.diffJson=function(e,n,t){return v.diff(e,n,t)},e.diffLines=L,e.diffSentences=function(e,n,t){return u.diff(e,n,t)},e.diffTrimmedLines=function(e,n,t){var r=i(t,{ignoreWhitespace:!0});return a.diff(e,n,r)},e.diffWords=function(e,n,t){return t=i(t,{ignoreWhitespace:!0}),s.diff(e,n,t)},e.diffWordsWithSpace=function(e,n,t){return s.diff(e,n,t)},e.formatPatch=S,e.merge=function(e,n,t){e=N(e,t),n=N(n,t);var r={};(e.index||n.index)&&(r.index=e.index||n.index),(e.newFileName||n.newFileName)&&(P(e)?P(n)?(r.oldFileName=j(r,e.oldFileName,n.oldFileName),r.newFileName=j(r,e.newFileName,n.newFileName),r.oldHeader=j(r,e.oldHeader,n.oldHeader),r.newHeader=j(r,e.newHeader,n.newHeader)):(r.oldFileName=e.oldFileName,r.newFileName=e.newFileName,r.oldHeader=e.oldHeader,r.newHeader=e.newHeader):(r.oldFileName=n.oldFileName||e.oldFileName,r.newFileName=n.newFileName||e.newFileName,r.oldHeader=n.oldHeader||e.oldHeader,r.newHeader=n.newHeader||e.newHeader)),r.hunks=[];for(var i=0,o=0,l=0,s=0;i { + const src = chrome.runtime.getURL('util/common.js'); + const { escapeRegExp } = await import(src); const w = window; const exElmList = ['html', 'title', 'script', 'noscript', 'style', 'meta', 'link', 'head', 'textarea', '#comment']; const CLASS_NAME_BLURRED = 'tb-blurred'; @@ -577,10 +579,6 @@ }) }; - const escapeRegExp = (str) => { - return str.replace(/([\(\)\{\}\+\*\?\[\]\.\^\$\|\\])/g, '\\$1'); - }; - const keywords2RegExp = (keywords, mode, matchCase) => { return new RegExp( (keywords || '').split(/\n/).filter(k => !!k.trim()).map(k => `(?:${mode === 'regexp' ? k.trim() : escapeRegExp(k.trim())})`).join('|'), diff --git a/manifest.json b/manifest.json index e3f66d1..ea8ec6e 100644 --- a/manifest.json +++ b/manifest.json @@ -9,6 +9,7 @@ "contextMenus" ], "background": { + "type": "module", "service_worker": "background/service-worker.js" }, "action": { @@ -27,8 +28,17 @@ "http://*/*" ], "js": [ + "content/js/diff.min.js", "content/js/main.js" ] } + ], + "web_accessible_resources": [ + { + "resources": [ + "util/common.js" + ], + "matches": [""] + } ] } \ No newline at end of file diff --git a/popup/js/main.js b/popup/js/main.js index b5e18f7..27d0dc9 100644 --- a/popup/js/main.js +++ b/popup/js/main.js @@ -1,3 +1,5 @@ +import { escapeRegExp } from '../../util/common.js'; + document.addEventListener('DOMContentLoaded', async (e) => { const { status, keywords, mode, matchCase, showValue, blurInput, blurTitle, exclusionUrls } = (await chrome.storage.local.get(['status', 'keywords', 'mode', 'matchCase', 'showValue', 'blurInput', 'blurTitle', 'exclusionUrls'])); @@ -32,10 +34,6 @@ document.addEventListener('DOMContentLoaded', async (e) => { let savedBlurTitle = false; const validationResults = {}; - const escapeRegExp = (str) => { - return str.replace(/([\(\)\{\}\+\*\?\[\]\.\^\$\|\\])/g, '\\$1'); - }; - window.addEventListener('keydown', (e) => { if (e.key === 's' && ((e.ctrlKey && !e.metaKey) || (!e.ctrlKey && e.metaKey))) { e.preventDefault(); @@ -52,6 +50,11 @@ document.addEventListener('DOMContentLoaded', async (e) => { } }); + chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (message.method !== 'reload') return; + window.location.reload(); + }); + addUrlsInCurrentTab.addEventListener('click', async (e) => { const urlInfo = { returnFromTop: false, diff --git a/popup/main.html b/popup/main.html index 6a21c76..6dc8ea5 100644 --- a/popup/main.html +++ b/popup/main.html @@ -4,7 +4,7 @@ Text Blurrer Popup - + diff --git a/util/common.js b/util/common.js new file mode 100644 index 0000000..7a18dd4 --- /dev/null +++ b/util/common.js @@ -0,0 +1,3 @@ +export function escapeRegExp(str) { + return str.replace(/([\(\)\{\}\+\*\?\[\]\.\^\$\|\\])/g, '\\$1'); +}; \ No newline at end of file From 3f91ce32110e733be13d7b5c424e923868fc8313 Mon Sep 17 00:00:00 2001 From: horihiro Date: Wed, 17 Apr 2024 19:22:13 +0900 Subject: [PATCH 25/29] Refactor for inline formatting --- content/js/main.js | 291 +++++++++++++++++++++++++++------------------ 1 file changed, 174 insertions(+), 117 deletions(-) diff --git a/content/js/main.js b/content/js/main.js index 1578b02..b1e97ad 100644 --- a/content/js/main.js +++ b/content/js/main.js @@ -88,142 +88,199 @@ : '' } - const getElementsToBeBlurred = (pattern, target, options) => { - let textNode = getNextTextNode(target, target); - if (!textNode) return; - let keywordPosition = 0, pos = textNode.textContent.length; - do { - const matchDetail = target.textContent.slice(keywordPosition).match(pattern); - if (!matchDetail) break; - // console.log(matchDetail[0]) - keywordPosition += matchDetail.index; - while (pos <= keywordPosition) { - textNode = getNextTextNode(textNode, target); - // console.log( location.href, `"${target.textContent.slice(pos, pos + textNode.textContent.length).replace(/\n/g, '\\n')}"`); - if (!textNode) break; - pos += textNode.textContent.length; + const BLOCK_ELEMENT_NAMES = ['ADDRESS', 'BLOCKQUOTE', 'DIV', 'DL', 'FIELDSET', 'FORM', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HR', 'NOSCRIPT', 'SCRIPT', 'PL', 'P', 'PRE', 'TABLE', 'UL']; + const blockContents = (node) => { + return Array.from(node.childNodes).reduce((lines, child) => { + if (child.nodeType == 8) { + return lines; + } if (child.nodeType >= 3) { + lines[lines.length - 1] += child.textContent; + } else { + const childText = blockContents(child); + !BLOCK_ELEMENT_NAMES.includes(child.nodeName) && (lines[lines.length - 1] += childText.shift()); + lines.push(...childText); + BLOCK_ELEMENT_NAMES.includes(child.nodeName) && lines.push(''); } - if (pos <= keywordPosition) { - matchDetail = target.textContent.slice(keywordPosition).match(pattern); - return; - }; + return lines; + }, [""]); + } + + const getPositionFromDiff = (diff, positions) => { + let posPre = 0, posPost = 0, index = 0; + const ret = []; - keywordPosition += matchDetail[0].length; - const textNodeArray = []; - textNodeArray.push(textNode); + diff.some((df) => { + if (!df.added) posPre += df.count; + if (!df.removed) posPost += df.count; + while (true) { + if (posPost <= positions[index] - 1 || index >= positions.length) break; - while (!textNodeArray.map(t => t.textContent).join('').match(escapeRegExp(matchDetail[0]))) { - textNode = getNextTextNode(textNode, target); - if (!textNode) break; - textNodeArray.push(textNode); - pos += textNode.textContent.length; + ret.push(posPre - posPost + positions[index]); + index++; } - if (!textNodeArray.map(t => t.textContent).join('').match(pattern)) { - continue - }; + return index >= positions.length; + }); + return ret; + } - while (textNodeArray.slice(1).map(t => t.textContent).join('').match(escapeRegExp(matchDetail[0]))) { - textNodeArray.shift(); + const isBlurred = (node) => { + do { + if (node.classList?.contains(CLASS_NAME_BLURRED)) return true; + node = node.parentNode; + } while (node); + return false; + } + + const getElementsToBeBlurred = (pattern, target, options) => { + let textNode = getNextTextNode(target, target), pos = 0; + if (!textNode) return; + let _startsFrom = 0; + const blockedContents = blockContents(target).map((l, index) => { + const startsFrom = _startsFrom; + _startsFrom += l.length; + return { + index, + contents: l, + startsFrom, } - if (exElmList.includes(textNodeArray.at(0).parentNode.nodeName.toLowerCase()) || exElmList.includes(textNodeArray.at(-1).parentNode.nodeName.toLowerCase())) { - continue; + }); + _startsFrom = 0; + const formattedBlockedContents = blockedContents.map(l => { + const contents = inlineFormatting(l.contents) + const startsFrom = _startsFrom; + const matches = []; + let start = 0; + while (true) { + const match = contents.slice(start).match(pattern); + if (!match) break; + matches.push({ + keyword: match[0], + index: start + match.index, + }); + start += match.index + match[0].length; } - const value = textNodeArray.map(t => t.textContent).join(''); - const from = { - node: textNodeArray.at(0), - startIndex: (value.match(pattern)?.index || 0) - }; - const to = { - node: textNodeArray.at(-1), - endIndex: textNodeArray.at(-1).length + (value.match(pattern)?.index || 0) + matchDetail[0].length - value.length - 1 - }; - const isBlurred = (node) => { - do { - if (node.classList?.contains(CLASS_NAME_BLURRED)) return true; - node = node.parentNode; - } while (node); - return false; + _startsFrom += contents.length; + return { + index: l.index, + contents, + matches: matches.length > 0 ? matches : null, + startsFrom, } - const nodeBeforeBlurred = document.createTextNode(from.node.textContent.slice(0, from.startIndex)); - const nodeAfterBlurred = document.createTextNode(to.node.textContent.slice(to.endIndex + 1)); - const insertNodes = []; - const removeNodes = []; - if (from.node == to.node) { - let prevTextNode = textNode; - textNode = getNextTextNode(textNode, target); - if (!from.node.parentNode - || exElmList.includes(from.node.parentNode.nodeName.toLowerCase()) - || isBlurred(from.node.parentNode)) { - if (!textNode) break; + }); + textNode = target; + formattedBlockedContents.filter(l => !!l.matches).forEach((block) => { + const formatted = block.contents; + const original = blockedContents[block.index].contents; + const diff = Diff.diffChars(original, formatted); + block.matches && block.matches.forEach((match) => { + const positions = getPositionFromDiff(diff, [match.index, match.index + match.keyword.length - 1]); + + const startIndex = blockedContents[block.index].startsFrom + positions[0]; + while (pos <= startIndex) { + textNode = getNextTextNode(textNode, target); pos += textNode.textContent.length; - continue; } - - const computedStyle = getComputedStyle(from.node.parentNode); - const size = Math.floor(parseFloat(computedStyle.fontSize) / 4); - if (to.node.textContent === matchDetail[0] && computedStyle.filter === 'none') { - from.node.parentNode.classList.add(CLASS_NAME_BLURRED); - from.node.parentNode.classList.add(CLASS_NAME_KEEP); - if (options?.showValue) { - const originalTitle = from.node.parentNode.getAttribute('title'); - if (originalTitle) { - from.node.parentNode.setAttribute('data-tb-original-title', originalTitle); - } - from.node.parentNode.setAttribute('title', matchDetail[0]); - } - if (size > 5) from.node.parentNode.style.filter += ` blur(${size}px)`; - if (!textNode) break; + const from = { + node: textNode, + index: 0, + }; + const textNodeArray = [textNode]; + + const endIndex = blockedContents[block.index].startsFrom + positions[1]; + while (pos <= endIndex) { + textNode = getNextTextNode(textNode, target); + textNodeArray.push(textNode); pos += textNode.textContent.length; - continue; } - const nodeBlurred = document.createElement('span'); - nodeBlurred.classList.add(CLASS_NAME_BLURRED); - nodeBlurred.textContent = from.node.textContent.slice(from.startIndex, to.endIndex + 1); - options?.showValue && nodeBlurred.setAttribute('title', matchDetail[0]); - insertNodes.push({ node: nodeBeforeBlurred, refNode: from.node, target: from.node.parentNode }); - insertNodes.push({ node: nodeBlurred, refNode: from.node, target: from.node.parentNode }); - insertNodes.push({ node: nodeAfterBlurred, refNode: from.node, target: from.node.parentNode }); - removeNodes.push(from.node); - } else { - const now = Date.now(); + const to = { + node: textNode, + index: 0, + }; + const str1 = textNodeArray.map(t => t.textContent).join(''); + const str2 = inlineFormatting(str1); + const partialDiff = Diff.diffChars(str1, str2); + const partialMatch = str2.match(pattern) || str2.match(match.keyword); + const partialPositions = getPositionFromDiff(partialDiff, [partialMatch.index, partialMatch.index + partialMatch[0].length - 1]); + from.index = partialPositions[0]; + to.index = to.node.textContent.length - (str1.length - partialPositions[1]); + + const nodeBeforeBlurred = document.createTextNode(from.node.textContent.slice(0, from.index)); + const nodeAfterBlurred = document.createTextNode(to.node.textContent.slice(to.index + 1)); + const insertNodes = []; + const removeNodes = []; if (!from.node.parentNode || !to.node.parentNode || exElmList.includes(from.node.parentNode.nodeName.toLowerCase()) || exElmList.includes(from.node.parentNode.nodeName.toLowerCase()) || isBlurred(from.node.parentNode) || isBlurred(to.node.parentNode)) return; - const nodeBlurredFrom = document.createElement('span'); - nodeBlurredFrom.classList.add(CLASS_NAME_BLURRED); - nodeBlurredFrom.classList.add(`${CLASS_PREFIX_BLURRED_GROUP}${now}`); - nodeBlurredFrom.textContent = from.node.textContent.slice(from.startIndex); - insertNodes.push({ node: nodeBeforeBlurred, refNode: from.node, target: from.node.parentNode }); - insertNodes.push({ node: nodeBlurredFrom, refNode: from.node, target: from.node.parentNode }); - - let workingTextNode = getNextTextNode(from.node, target); - removeNodes.push(from.node); - while (workingTextNode != to.node) { + + if (from.node == to.node) { + const computedStyle = getComputedStyle(from.node.parentNode); + const size = Math.floor(parseFloat(computedStyle.fontSize) / 4); + if (from.node.textContent === match.keyword && computedStyle.filter === 'none') { + from.node.parentNode.classList.add(CLASS_NAME_BLURRED); + from.node.parentNode.classList.add(CLASS_NAME_KEEP); + if (options?.showValue) { + const originalTitle = from.node.parentNode.getAttribute('title'); + if (originalTitle) { + from.node.parentNode.setAttribute('data-tb-original-title', originalTitle); + } + from.node.parentNode.setAttribute('title', match.keyword); + } + if (size > 5) from.node.parentNode.style.filter += ` blur(${size}px)`; + return; + } const nodeBlurred = document.createElement('span'); - nodeBlurred.textContent = workingTextNode.textContent; nodeBlurred.classList.add(CLASS_NAME_BLURRED); - nodeBlurred.classList.add(`${CLASS_PREFIX_BLURRED_GROUP}${now}`); - insertNodes.push({ node: nodeBlurred, refNode: workingTextNode, target: workingTextNode.parentNode }); - removeNodes.push(workingTextNode); - workingTextNode = getNextTextNode(workingTextNode, target); - } + nodeBlurred.textContent = from.node.textContent.slice(from.index, to.index + 1); + options?.showValue && nodeBlurred.setAttribute('title', match.keyword); + insertNodes.push({ node: nodeBeforeBlurred, refNode: from.node, target: from.node.parentNode }); + insertNodes.push({ node: nodeBlurred, refNode: from.node, target: from.node.parentNode }); + insertNodes.push({ node: nodeAfterBlurred, refNode: from.node, target: from.node.parentNode }); + removeNodes.push(from.node); + } else { + const now = Date.now(); + + const nodeBlurredFrom = document.createElement('span'); + nodeBlurredFrom.classList.add(CLASS_NAME_BLURRED); + nodeBlurredFrom.classList.add(`${CLASS_PREFIX_BLURRED_GROUP}${now}`); + nodeBlurredFrom.textContent = from.node.textContent.slice(from.index); + insertNodes.push({ node: nodeBeforeBlurred, refNode: from.node, target: from.node.parentNode }); + insertNodes.push({ node: nodeBlurredFrom, refNode: from.node, target: from.node.parentNode }); + + let workingTextNode = getNextTextNode(from.node, target); + removeNodes.push(from.node); + while (workingTextNode != to.node) { + const nodeBlurred = document.createElement('span'); + nodeBlurred.textContent = workingTextNode.textContent; + nodeBlurred.classList.add(CLASS_NAME_BLURRED); + nodeBlurred.classList.add(`${CLASS_PREFIX_BLURRED_GROUP}${now}`); + insertNodes.push({ node: nodeBlurred, refNode: workingTextNode, target: workingTextNode.parentNode }); + removeNodes.push(workingTextNode); + workingTextNode = getNextTextNode(workingTextNode, target); + } - const nodeBlurredTo = document.createElement('span'); - nodeBlurredTo.classList.add(CLASS_NAME_BLURRED); - nodeBlurredTo.classList.add(`${CLASS_PREFIX_BLURRED_GROUP}${now}`); - nodeBlurredTo.textContent = to.node.textContent.slice(0, to.endIndex + 1); - insertNodes.push({ node: nodeBlurredTo, refNode: to.node, target: to.node.parentNode }); - insertNodes.push({ node: nodeAfterBlurred, refNode: to.node, target: to.node.parentNode }); - removeNodes.push(to.node); - } - insertNodes.forEach((n) => { - n.target.insertBefore(n.node, n.refNode); - }); - removeNodes.forEach((n) => { - n.parentNode.removeChild(n); + const nodeBlurredTo = document.createElement('span'); + nodeBlurredTo.classList.add(CLASS_NAME_BLURRED); + nodeBlurredTo.classList.add(`${CLASS_PREFIX_BLURRED_GROUP}${now}`); + nodeBlurredTo.textContent = to.node.textContent.slice(0, to.index + 1); + insertNodes.push({ node: nodeBlurredTo, refNode: to.node, target: to.node.parentNode }); + insertNodes.push({ node: nodeAfterBlurred, refNode: to.node, target: to.node.parentNode }); + removeNodes.push(to.node); + } + insertNodes.forEach((n) => { + n.target.insertBefore(n.node, n.refNode); + if (!isBlurred(n.node)) return; + const node = n.node.nodeName === '#text' ? n.node.parentNode : n.node; + options?.showValue && node.setAttribute('title', match.keyword); + const computedStyle = getComputedStyle(node); + const size = Math.floor(parseFloat(computedStyle.fontSize) / 4); + if (size > 5) node.style.filter += ` blur(${size}px)`; + }); + removeNodes.forEach((n) => { + n.parentNode.removeChild(n); + }); + textNode = nodeAfterBlurred; }); - textNode = nodeAfterBlurred; - } while (true); + }); }; const blurByRegExpPattern = (pattern, options, target) => { From 04e36e065f809e670d93c8989a5fa9d241e6eaab Mon Sep 17 00:00:00 2001 From: horihiro Date: Wed, 17 Apr 2024 20:36:17 +0900 Subject: [PATCH 26/29] Add jsdiff as a third-party dependency --- NOTICE.md | 37 +++++++++++++++++++++++++++++++++++++ README.md | 7 ++++++- 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 NOTICE.md diff --git a/NOTICE.md b/NOTICE.md new file mode 100644 index 0000000..e1a1a22 --- /dev/null +++ b/NOTICE.md @@ -0,0 +1,37 @@ +# Third-Party Notices + +## jsdiff +** Source **: [https://github.com/kpdecker/jsdiff](https://github.com/kpdecker/jsdiff) +kpdecker/jsdiff 5.2.0 BSD 3-Clause License + +### License + +``` +Copyright (c) 2009-2015, Kevin Decker +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` \ No newline at end of file diff --git a/README.md b/README.md index cfec595..1f35b54 100644 --- a/README.md +++ b/README.md @@ -54,11 +54,15 @@ If you can try a development version, the following steps are needed. ![image](https://github.com/horihiro/TextBlurrer-ChromeExtension/assets/4566555/44e7f896-9e82-4af1-ae1b-f864097b44c7) 1. select the directory created by cloning at step 1. +# Dependencies + - **[jsdiff](https://github.com/kpdecker/jsdiff)**: A JavaScript text differencing implementation (BSD 3-Clause License). + # Change logs ## [0.2.0](https://github.com/horihiro/TextBlurrer-ChromeExtension/releases/tag/0.2.0) -Refactoring blurring logic to improve performance and maintainability. +Refactoring blurring logic to improve performance and maintainability. +From this version, this extension includes [jsdiff](https://github.com/kpdecker/jsdiff) - New features - Disable blurring on listed sites on [Exclusion URL pattern list](#exclusion-url-list-v020-or-later) @@ -152,3 +156,4 @@ First release on [Chrome Web Store](https://chrome.google.com/webstore/detail/te ## [0.0.1-alpha](https://github.com/horihiro/TextBlurrer-ChromeExtension/releases/tag/0.0.1) First release on GitHub + From 35e89d3ab745e0ac8c65120ba9fbad3f68ce0c53 Mon Sep 17 00:00:00 2001 From: horihiro Date: Thu, 18 Apr 2024 10:05:50 +0900 Subject: [PATCH 27/29] Refactor MutationObserver callback to improve readability and maintainability --- content/js/main.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/content/js/main.js b/content/js/main.js index b1e97ad..2b6d53f 100644 --- a/content/js/main.js +++ b/content/js/main.js @@ -459,6 +459,11 @@ observedNodes.push(observed); if (!w.__observer) { w.__observer = new MutationObserver((records) => { + if (!records.some(record => { + return record.removedNodes.length > 0 || Array.from(record.addedNodes).some(node => { + return !['SCRIPT', 'STYLE', '#comment'].includes(node.nodeName); + }); + })) return; const targets = records.reduce((targets, record) => { const isContained = targets.some((target) => { return target.contains(record.target); From e950d0691bb49e87674cb1295f615bd86be93f3d Mon Sep 17 00:00:00 2001 From: Hirofumi Horikawa Date: Thu, 18 Apr 2024 11:39:06 +0900 Subject: [PATCH 28/29] Update screenshot of context menu --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 1f35b54..2e0e828 100644 --- a/README.md +++ b/README.md @@ -29,8 +29,7 @@ You can add URLs on the current tab to the list by clicking `+ Add URLs in the c Simple way to add the blurry keyword. -![image](https://github.com/horihiro/TextBlurrer-ChromeExtension/assets/4566555/7b26db8b-efa9-422d-8750-10fd71550318) - +![image](https://github.com/horihiro/TextBlurrer-ChromeExtension/assets/4566555/a1182e7f-5462-493b-b561-618863d29fc9) ## Shortcut keys on popup (v0.2.0 or later) From 0678c09d7aafc44c004259b69026681cb275883d Mon Sep 17 00:00:00 2001 From: horihiro Date: Thu, 18 Apr 2024 13:33:40 +0900 Subject: [PATCH 29/29] Fix bug in addUrlsInCurrentTab event listener --- popup/js/main.js | 1 + 1 file changed, 1 insertion(+) diff --git a/popup/js/main.js b/popup/js/main.js index 27d0dc9..7d74462 100644 --- a/popup/js/main.js +++ b/popup/js/main.js @@ -56,6 +56,7 @@ document.addEventListener('DOMContentLoaded', async (e) => { }); addUrlsInCurrentTab.addEventListener('click', async (e) => { + if (!statusCheckbox.checked) return; const urlInfo = { returnFromTop: false, numOfChildren: 0,