feat,fix: document observation, popup refinement
* Implement document recheck on text content change * Review extension config * Split text/image popup sections into tabs * Fix popup setting not being properly reactive
This commit is contained in:
parent
f2b8bdca98
commit
b84eaaac82
1 changed files with 366 additions and 284 deletions
|
|
@ -1,19 +1,8 @@
|
|||
/// Extracts meaningful portions of text from DOM, yielding the top matched nodes
|
||||
///
|
||||
/// e.g.
|
||||
///
|
||||
/// ```html
|
||||
/// <div>
|
||||
/// my hair are NOT
|
||||
/// <span>
|
||||
/// blue!
|
||||
/// </span>
|
||||
/// </div>
|
||||
/// ```
|
||||
///
|
||||
/// Shall yield a reference to the `div` with text "my hair are NOT blue!"
|
||||
|
||||
const IS_DEV = import.meta.env.MODE == "development";
|
||||
const console = ExtConsole("domtraverse");
|
||||
|
||||
export const TRAVERSE_BEHAVOUR_IGNORE_MAGIC = Math.floor(Math.random() * 1000000).toString();
|
||||
export const TRAVERSE_BEHAVOUR_ATTR = "data-firaid-moz-traverse-behavour";
|
||||
|
||||
const TRAVERSE_STACK_BANS: Set<string | null | undefined> = new Set([
|
||||
"IMG",
|
||||
|
|
@ -50,28 +39,56 @@ const TRAVERSE_ALLOWED_ELEMENT_TAGS: Set<string | null | undefined> = new Set([
|
|||
"I",
|
||||
"STRONG",
|
||||
"EM",
|
||||
"DEL",
|
||||
]);
|
||||
const KNOWN_STACK_ROOTS: WeakMap<
|
||||
Node,
|
||||
{
|
||||
csum: number;
|
||||
}
|
||||
> = new WeakMap();
|
||||
|
||||
export function* traverse_text(soft_max_words: number, hard_min_words: number) {
|
||||
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, {
|
||||
acceptNode: (node) => {
|
||||
// sanity check
|
||||
if (!node.isConnected) {
|
||||
if (IS_DEV) {
|
||||
node.parentElement?.setAttribute("data-firaid-moz-traverse", "REJECT-!IS_CONNECTED");
|
||||
}
|
||||
if (node.parentNode?.nodeType != Node.ELEMENT_NODE || !node.parentElement) {
|
||||
return NodeFilter.FILTER_REJECT;
|
||||
}
|
||||
if (node.parentNode?.nodeType != Node.ELEMENT_NODE) {
|
||||
if (!node.isConnected) {
|
||||
if (IS_DEV) {
|
||||
node.parentElement?.setAttribute("data-firaid-moz-traverse", "REJECT-!ELEMENT_NODE");
|
||||
node.parentElement.setAttribute("data-firaid-moz-traverse", "REJECT:disconnected");
|
||||
}
|
||||
return NodeFilter.FILTER_REJECT;
|
||||
}
|
||||
|
||||
// filter elements
|
||||
if (!TRAVERSE_ALLOWED_ELEMENT_TAGS.has(node.parentElement?.tagName)) {
|
||||
if (!TRAVERSE_ALLOWED_ELEMENT_TAGS.has(node.parentElement.tagName)) {
|
||||
if (IS_DEV) {
|
||||
node.parentElement?.setAttribute("data-firaid-moz-traverse", "REJECT-!ALLOWED_TAG");
|
||||
node.parentElement.setAttribute("data-firaid-moz-traverse", "REJECT:tag_not_allowed");
|
||||
}
|
||||
return NodeFilter.FILTER_REJECT;
|
||||
}
|
||||
|
||||
// ignored elements
|
||||
if (node.parentElement.getAttribute(TRAVERSE_BEHAVOUR_ATTR) == TRAVERSE_BEHAVOUR_IGNORE_MAGIC) {
|
||||
if (IS_DEV) {
|
||||
node.parentElement.setAttribute("data-firaid-moz-traverse", "REJECT:behavour_magic");
|
||||
}
|
||||
return NodeFilter.FILTER_REJECT;
|
||||
}
|
||||
|
||||
// check visibility (my best effort, still mistakes sometimes)
|
||||
if (
|
||||
!node.parentElement.checkVisibility({
|
||||
visibilityProperty: true,
|
||||
}) ||
|
||||
!node.parentElement.offsetParent ||
|
||||
!node.parentElement.getBoundingClientRect().height
|
||||
) {
|
||||
// check visibility
|
||||
if (IS_DEV) {
|
||||
node.parentElement.setAttribute("data-firaid-moz-traverse", "REJECT:invisible");
|
||||
}
|
||||
return NodeFilter.FILTER_REJECT;
|
||||
}
|
||||
|
|
@ -80,17 +97,14 @@ export function* traverse_text(soft_max_words: number, hard_min_words: number) {
|
|||
// Ignore anything inside of banned elements.
|
||||
//
|
||||
// For now I'll just limit the depth to 16
|
||||
let checking = node.parentElement;
|
||||
let checking: Element | null = node.parentElement;
|
||||
for (let i = 0; i < 16; i++) {
|
||||
if (!checking || checking.tagName == "BODY") {
|
||||
break;
|
||||
}
|
||||
if (TRAVERSE_STACK_BANS.has(checking.tagName)) {
|
||||
if (IS_DEV) {
|
||||
node.parentElement?.setAttribute(
|
||||
"data-firaid-moz-traverse",
|
||||
"REJECT-BANNED_ANCESTOR",
|
||||
);
|
||||
node.parentElement.setAttribute("data-firaid-moz-traverse", "REJECT:banned_ancestor");
|
||||
}
|
||||
return NodeFilter.FILTER_REJECT;
|
||||
}
|
||||
|
|
@ -98,16 +112,16 @@ export function* traverse_text(soft_max_words: number, hard_min_words: number) {
|
|||
}
|
||||
|
||||
// drop with nonsense content
|
||||
if ((node.textContent?.trim()?.length || 0) < 8) {
|
||||
if (IS_DEV) {
|
||||
node.parentElement?.setAttribute("data-firaid-moz-traverse", "REJECT-SHORT_TEXT");
|
||||
}
|
||||
return NodeFilter.FILTER_REJECT;
|
||||
}
|
||||
// if (!node.textContent?.trim()) {
|
||||
// if (IS_DEV) {
|
||||
// node.parentElement?.setAttribute("data-firaid-moz-traverse", "REJECT-NO_TEXT");
|
||||
// }
|
||||
// return NodeFilter.FILTER_REJECT;
|
||||
// }
|
||||
|
||||
// finally good ones
|
||||
if (IS_DEV) {
|
||||
node.parentElement?.setAttribute("data-firaid-moz-traverse", "ACCEPT");
|
||||
node.parentElement.setAttribute("data-firaid-moz-traverse", "ACCEPT");
|
||||
}
|
||||
return NodeFilter.FILTER_ACCEPT;
|
||||
},
|
||||
|
|
@ -127,25 +141,15 @@ export function* traverse_text(soft_max_words: number, hard_min_words: number) {
|
|||
rootable_parent = rootable_parent.parentElement;
|
||||
}
|
||||
if (!rootable_parent) {
|
||||
if (IS_DEV) {
|
||||
node.parentElement?.setAttribute("data-firaid-moz-stack", "REJECTED-!ROOTABLE_PARENT");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
let nodeText = node.textContent?.trim();
|
||||
if (!nodeText) {
|
||||
if (IS_DEV) {
|
||||
node.parentElement?.setAttribute("data-firaid-moz-stack", "REJECTED-!TEXT");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let nodeTextWords = count_words_naive(nodeText);
|
||||
|
||||
if (IS_DEV) {
|
||||
node.parentElement?.setAttribute("data-firaid-moz-stack", "VISITED");
|
||||
}
|
||||
|
||||
if (!stack) {
|
||||
stack = {
|
||||
root: rootable_parent,
|
||||
|
|
@ -155,20 +159,19 @@ export function* traverse_text(soft_max_words: number, hard_min_words: number) {
|
|||
words: nodeTextWords,
|
||||
};
|
||||
} else if (stack.root.contains(rootable_parent)) {
|
||||
if (IS_DEV) {
|
||||
node.parentElement?.setAttribute("data-firaid-moz-stack", "MEMBER");
|
||||
}
|
||||
stack.text += " " + nodeText;
|
||||
stack.words += nodeTextWords;
|
||||
stack.last_node = node;
|
||||
} else {
|
||||
if (stack.words >= hard_min_words) {
|
||||
yield stack;
|
||||
if (IS_DEV) {
|
||||
stack.root.parentElement?.setAttribute("data-firaid-moz-stack", "ROOT");
|
||||
const known = KNOWN_STACK_ROOTS.get(stack.root);
|
||||
const csum = text_csum(stack.text);
|
||||
if (csum !== known?.csum) {
|
||||
KNOWN_STACK_ROOTS.set(stack.root, { csum, ...known });
|
||||
yield stack;
|
||||
} else {
|
||||
console.debug("Refuse to yield stack as matching csum exists");
|
||||
}
|
||||
} else if (IS_DEV) {
|
||||
stack.root.parentElement?.setAttribute("data-firaid-moz-stack", "SKIP-SHORT_TEXT");
|
||||
}
|
||||
stack = {
|
||||
root: rootable_parent,
|
||||
|
|
@ -180,13 +183,16 @@ export function* traverse_text(soft_max_words: number, hard_min_words: number) {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (stack.words > soft_max_words) {
|
||||
if (stack && stack.words > soft_max_words) {
|
||||
if (stack.words >= hard_min_words) {
|
||||
if (IS_DEV) {
|
||||
stack.root.parentElement?.setAttribute("data-firaid-moz-stack", "ROOT");
|
||||
const known = KNOWN_STACK_ROOTS.get(stack.root);
|
||||
const csum = text_csum(stack.text);
|
||||
if (csum !== known?.csum) {
|
||||
KNOWN_STACK_ROOTS.set(stack.root, { csum, ...known });
|
||||
yield stack;
|
||||
} else {
|
||||
console.debug("Refuse to yield stack as matching csum exists");
|
||||
}
|
||||
} else if (IS_DEV) {
|
||||
stack.root.parentElement?.setAttribute("data-firaid-moz-stack", "SKIP-SHORT_TEXT");
|
||||
}
|
||||
stack = null;
|
||||
}
|
||||
|
|
@ -209,3 +215,13 @@ function count_words_naive(text: string): number {
|
|||
|
||||
return count;
|
||||
}
|
||||
|
||||
function text_csum(text: string): number {
|
||||
let csum = 5381;
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
csum += (csum << 5) + text.charCodeAt(i);
|
||||
csum &= csum;
|
||||
}
|
||||
|
||||
return csum >>> 0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue