Skip to content

Commit

Permalink
Fixed #24: unblur if hierarchy changes
Browse files Browse the repository at this point in the history
  • Loading branch information
getvictor committed Aug 9, 2024
1 parent e8e3c5f commit 420269d
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 39 deletions.
57 changes: 56 additions & 1 deletion src/content.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @jest-environment jsdom
* @jest-environment-options {"url": "https://example.org:8080/posts/index.html"}
*/
import { blurFilter, handleMessage, setEnabled } from "./content"
import { blurFilter, handleMessage, processNode, setEnabled } from "./content"
import { Message } from "./constants"

beforeEach(() => {
Expand Down Expand Up @@ -64,4 +64,59 @@ describe("blur", () => {
expect(testDiv).toBeDefined()
expect(testDiv.style.filter).not.toBe(blurFilter)
})

test("unblur with text change", () => {
document.body.innerHTML = `
<div id="testDiv">
"My secret"
</div>`
// Set value to blur as a message
const message: Message = {
literals: ["secret"],
}
handleMessage(message)
let testDiv = document.getElementById("testDiv") as HTMLInputElement
expect(testDiv).toBeDefined()
expect(testDiv.style.filter).toBe(blurFilter)

// Run again and make sure it didn't get unblurred.
processNode(document.body, new Set<HTMLElement>())
testDiv = document.getElementById("testDiv") as HTMLInputElement
expect(testDiv).toBeDefined()
expect(testDiv.style.filter).toBe(blurFilter)

// Now we change the text, and expect the blur to be removed
if (testDiv.firstChild) {
testDiv.firstChild.nodeValue = "Change"
}
processNode(document.body, new Set<HTMLElement>())

testDiv = document.getElementById("testDiv") as HTMLInputElement
expect(testDiv).toBeDefined()
expect(testDiv.style.filter).not.toBe(blurFilter)
})

test("unblur with text change to div", () => {
document.body.innerHTML = `
<div id="testDiv">
"My secret"
</div>`
// Set value to blur as a message
const message: Message = {
literals: ["secret"],
}
handleMessage(message)
let testDiv = document.getElementById("testDiv") as HTMLInputElement
expect(testDiv).toBeDefined()
expect(testDiv.style.filter).toBe(blurFilter)

// Now we change the text to div, and expect the blur to be removed
testDiv.removeChild(testDiv.firstChild as Node)
testDiv.appendChild(document.createElement("div"))
processNode(document.body, new Set<HTMLElement>())

testDiv = document.getElementById("testDiv") as HTMLInputElement
expect(testDiv).toBeDefined()
expect(testDiv.style.filter).not.toBe(blurFilter)
})
})
78 changes: 40 additions & 38 deletions src/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ const tagsNotToBlur = ["HEAD", "SCRIPT", "STYLE", "loc"]
const contentToBlur: string[] = []
let enabled = true
let bodyHidden = true
let doFullScan = false
const localConfig: StoredConfig = {
cssSelectors: [],
}
Expand Down Expand Up @@ -88,21 +87,14 @@ function processNodeWithParent(node: Node) {
processNode(target, new Set<HTMLElement>())
}

function processHtmlElement(
parent: HTMLElement | null,
text: string,
blurredElements: Set<HTMLElement>,
checkContent: boolean,
) {
function processHtmlElement(parent: HTMLElement | null, text: string, blurredElements: Set<HTMLElement>) {
if (parent?.style) {
let useGrandParent = false
if (performanceOptimizationMode && parent.parentElement instanceof HTMLElement) {
// In performance optimization mode, we may blur the parent's parent.
const grandParent = parent.parentElement
if (grandParent.style.filter.includes(blurFilter)) {
// Treat the grandparent as the parent.
parent = grandParent
useGrandParent = true
}
}
if (blurredElements.has(parent)) {
Expand All @@ -118,16 +110,14 @@ function processHtmlElement(
}
// In performance optimization mode, the grandparent may have been updated to have
// completely different content.
if (checkContent || useGrandParent) {
// Double check if the blur is still needed.
const blurNeeded = contentToBlur.some((content) => {
return text.includes(content)
})
if (!blurNeeded) {
unblurElement(parent)
} else {
blurredElements.add(parent)
}
// Double check if the blur is still needed.
const blurNeeded = contentToBlur.some((content) => {
return text.includes(content)
})
if (!blurNeeded) {
unblurElement(parent)
} else {
blurredElements.add(parent)
}
return
} else if (enabled) {
Expand All @@ -141,13 +131,14 @@ function processHtmlElement(
}
}

function processNode(node: Node, blurredElements: Set<HTMLElement>) {
export function processNode(node: Node, blurredElements: Set<HTMLElement>) {
if (node instanceof HTMLElement && tagsNotToBlur.includes(node.tagName)) {
return
}
if (node.nodeType === Node.TEXT_NODE && node.textContent !== null && node.textContent.trim().length > 0) {
const text = node.textContent
processHtmlElement(node.parentElement, text, blurredElements, doFullScan)
processHtmlElement(node.parentElement, text, blurredElements)
return
} else if (node.nodeType === Node.ELEMENT_NODE) {
const elem = node as HTMLElement
switch (elem.tagName) {
Expand All @@ -157,34 +148,44 @@ function processNode(node: Node, blurredElements: Set<HTMLElement>) {
processInputElement(input, blurredElements)
input.addEventListener("input", inputEventListener)
}
break
return
}
case "TEXTAREA": {
const textarea = elem as HTMLTextAreaElement
processInputElement(textarea, blurredElements)
textarea.addEventListener("input", inputEventListener)
break
return
}
case "SELECT": {
const select = elem as HTMLSelectElement
let text = ""
if (select.selectedIndex >= 0) {
text = select.options[select.selectedIndex].text
}
processHtmlElement(select, text, blurredElements, true)
processHtmlElement(select, text, blurredElements)
select.addEventListener("change", selectOnChangeListener)
break
}
default: {
if (node.childNodes.length > 0) {
Array.from(node.childNodes).forEach((value) => {
processNode(value, blurredElements)
})
}
return
}
}
} else {
if (node.childNodes.length > 0) {
}
// We should only get here if node has not been processed
if (node.childNodes.length > 0) {
if (node instanceof HTMLElement) {
let blurred = false
const startingCount = blurredElements.size
if (node.style.filter.includes(blurFilter)) {
blurred = true
}
Array.from(node.childNodes).forEach((value) => {
processNode(value, blurredElements)
})
if (blurred && startingCount >= blurredElements.size) {
// The element was already blurred, but no children were blurred.
// So we unblur.
// This will trigger a second pass by blurStyleObserver. This is fine since this situation is rare.
unblurElement(node)
}
} else {
Array.from(node.childNodes).forEach((value) => {
processNode(value, blurredElements)
})
Expand All @@ -197,7 +198,10 @@ function processNode(node: Node, blurredElements: Set<HTMLElement>) {
const blurStyleObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === "attributes") {
processNodeWithParent(mutation.target)
const elem = mutation.target as HTMLElement
if (!elem.style.filter.includes(blurFilter)) {
processNodeWithParent(mutation.target)
}
}
})
})
Expand Down Expand Up @@ -315,7 +319,7 @@ function selectOnChangeListener(event: Event) {
if (select.selectedIndex >= 0) {
text = select.options[select.selectedIndex].text
}
processHtmlElement(select, text, new Set<HTMLElement>(), true)
processHtmlElement(select, text, new Set<HTMLElement>())
}
}

Expand Down Expand Up @@ -353,9 +357,7 @@ function setLiterals(literals: string[]) {
}
}
if (enabled) {
doFullScan = true
observe()
doFullScan = false
}
unhideBody()
}
Expand Down

0 comments on commit 420269d

Please sign in to comment.