Skip to content

Commit 2df708f

Browse files
committed
Feat: replace DOMSubtreeModified with MutationObserver
1 parent afccb15 commit 2df708f

File tree

3 files changed

+149
-101
lines changed

3 files changed

+149
-101
lines changed

idImporter.ts

Lines changed: 73 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,78 @@
1-
(function () {
2-
// 將IDNumber改成自己的身分證
3-
const IDNumber = 'A1234567890';
1+
// 將IDNumber改成自己的身分證
2+
const IDNumber: string = "A1234567890";
43

5-
document.addEventListener('DOMSubtreeModified', (event) => {
6-
if (event.target instanceof Element) {
7-
if (event.target.getAttribute('role') === 'dialog' && event.target.classList.contains('aLF-aPX-axU')) {
8-
setTimeout(() => {
9-
let J = JSON.parse((document.querySelector('#drive-active-item-info') as HTMLElement).innerText);
10-
if (/\.pdf$/i.test(J.title)) {
11-
let inputEl = (event.target as Element).querySelector('[type="password"]') as HTMLInputElement;
12-
13-
inputEl.value = IDNumber;
14-
inputEl.dispatchEvent(new Event('change'));
15-
}
16-
}, 1000);
4+
/**
5+
* 處理 Google Drive 中的 PDF 密碼對話框。
6+
* 嘗試自動填入密碼並觸發 'change' 事件。
7+
* @param dialogElement 檢測到的密碼對話框元素。
8+
*/
9+
function handlePasswordDialog(dialogElement: Element): void {
10+
// 暫時斷開觀察器,以避免在處理程式碼中修改 DOM 時產生無限迴圈。
11+
// 這是一個最佳實踐,確保我們的 DOM 操作不會再次觸發觀察器。
12+
observer.disconnect();
1713

18-
let submitEl = (event.target as Element).querySelector('[role="button"]') as HTMLInputElement;
19-
submitEl.addEventListener('click', () => {}, false);
14+
// 設定一個延遲,給予對話框足夠的時間來渲染其內容。
15+
// 1500 毫秒(1.5 秒)的延遲應足夠。
16+
setTimeout(() => {
17+
try {
18+
// 尋找包含檔案資訊的元素。
19+
const driveInfoElement = document.querySelector<HTMLDivElement>('#drive-active-item-info');
20+
if (!driveInfoElement) {
21+
console.warn("警告:未找到元素 #drive-active-item-info。");
22+
return; // 如果找不到該元素,則退出函式。
23+
}
24+
25+
// 解析 JSON 字串以獲取檔案資訊。
26+
// 使用類型斷言 <{ title: string }> 以告知 TypeScript 期望的結構。
27+
const fileInfo: { title: string } = JSON.parse(driveInfoElement.innerText);
28+
29+
// 檢查檔案標題是否存在且為字串,並判斷是否為 PDF 檔案。
30+
if (fileInfo && typeof fileInfo.title === 'string' && /\.pdf$/i.test(fileInfo.title)) {
31+
// 在對話框元素中尋找密碼輸入框。
32+
const passwordInput = dialogElement.querySelector<HTMLInputElement>('[type="password"]');
33+
if (passwordInput) {
34+
// console.log(`偵測到 PDF 檔案:${fileInfo.title}。正在嘗試自動填入密碼。`);
35+
passwordInput.value = IDNumber; // 填入密碼。
36+
passwordInput.dispatchEvent(new Event('change')); // 觸發 'change' 事件,模擬使用者輸入。
37+
// console.log("密碼欄位已成功填入並觸發 change 事件。");
38+
} else {
39+
console.warn("警告:在 PDF 密碼對話框中未找到密碼輸入元素。");
40+
}
41+
}
42+
} catch (error) {
43+
// 捕獲並記錄處理過程中可能發生的任何錯誤。
44+
console.error("錯誤:處理 PDF 密碼對話框時發生問題。", error);
45+
} finally {
46+
// 無論是否發生錯誤,都重新連接觀察器,繼續監聽 DOM 變化。
47+
observer.observe(document.body, { childList: true, subtree: true, attributes: true });
48+
}
49+
}, 1500);
50+
}
2051

21-
setTimeout(() => {
22-
submitEl.dispatchEvent(new Event('click'));
23-
}, 3000);
52+
// 創建一個 MutationObserver 實例來監聽 DOM 樹的變化。
53+
// MutationObserver 比 DOMSubtreeModified 更加高效和推薦。
54+
const observer = new MutationObserver((mutationsList: MutationRecord[]) => {
55+
// 遍歷所有發生的 DOM 變化記錄。
56+
for (const mutation of mutationsList) {
57+
// 我們只關心 'childList' (節點被添加/移除) 或 'attributes' (元素屬性被改變) 類型的變化。
58+
if (mutation.type === 'childList' || mutation.type === 'attributes') {
59+
// 檢查被修改的目標元素本身是否為對話框。
60+
const targetElement = mutation.target instanceof Element ? mutation.target : null;
61+
if (targetElement && targetElement.getAttribute('role') === 'dialog' && targetElement.classList.contains('aLF-aPX-axU')) {
62+
handlePasswordDialog(targetElement);
63+
return; // 如果找到並處理了對話框,就停止進一步的處理。
2464
}
65+
66+
// 檢查所有新加入的節點,看是否有符合條件的對話框。
67+
mutation.addedNodes.forEach(node => {
68+
if (node instanceof Element && node.getAttribute('role') === 'dialog' && node.classList.contains('aLF-aPX-axU')) {
69+
handlePasswordDialog(node);
70+
return; // 如果找到並處理了對話框,就停止進一步的處理。
71+
}
72+
});
2573
}
26-
});
27-
})();
74+
}
75+
});
76+
77+
// 開始觀察整個 document body,監聽其子節點、所有後代子樹以及屬性的變化。
78+
observer.observe(document.body, { childList: true, subtree: true, attributes: true });

idImporter.user.js

Lines changed: 57 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,69 @@
1+
"use strict";
12
// ==UserScript==
23
// @name Auto import ID number
34
// @namespace http://tampermonkey.net/
4-
// @version 0.1
5+
// @version 1.1
56
// @description when opening PDF auto import ID number.
67
// @author Adokun
78
// @match https://mail.google.com/*
89
// @grant none
910
// ==/UserScript==
1011

12+
// 將IDNumber改成自己的身分證
13+
const IDNumber = "A1234567890";
1114

12-
(function () {
13-
// 將IDNumber改成自己的身分證
14-
const IDNumber = 'A1234567890';
15-
16-
document.addEventListener('DOMSubtreeModified', (event) => {
17-
if (event.target.getAttribute('role') === 'dialog' && event.target.classList.contains('aLF-aPX-axU')) {
18-
setTimeout(() => {
19-
let J = JSON.parse(document.querySelector('#drive-active-item-info').innerText);
20-
if (/\.pdf$/i.test(J.title)) {
21-
let inputEl = event.target.querySelector('[type="password"]');
22-
23-
inputEl.value = IDNumber;
24-
inputEl.dispatchEvent(new Event('change'));
15+
function handlePasswordDialog(dialogElement) {
16+
observer.disconnect();
17+
setTimeout(() => {
18+
try {
19+
const driveInfoElement = document.querySelector('#drive-active-item-info');
20+
if (!driveInfoElement) {
21+
console.warn("警告:未找到元素 #drive-active-item-info。");
22+
return;
23+
}
24+
const fileInfo = JSON.parse(driveInfoElement.innerText);
25+
if (fileInfo && typeof fileInfo.title === 'string' && /\.pdf$/i.test(fileInfo.title)) {
26+
const passwordInput = dialogElement.querySelector('[type="password"]');
27+
if (passwordInput) {
28+
// console.log(`偵測到 PDF 檔案:${fileInfo.title}。正在嘗試自動填入密碼。`);
29+
passwordInput.value = IDNumber; // 填入密碼。
30+
passwordInput.dispatchEvent(new Event('change')); // 觸發 'change' 事件,模擬使用者輸入。
31+
// console.log("密碼欄位已成功填入並觸發 change 事件。");
2532
}
26-
}, 1000);
27-
28-
let submitEl = event.target.querySelector('[role="button"]');
29-
30-
// submitEl.addEventListener('click',function () {
31-
// }, false);
32-
33-
setTimeout(() => {
34-
// submitEl.dispatchEvent(new Event('click'));
35-
submitEl.click();
36-
}, 3000);
33+
else {
34+
console.warn("警告:在 PDF 密碼對話框中未找到密碼輸入元素。");
35+
}
36+
}
3737
}
38-
});
39-
})();
40-
41-
/**
42-
* @description 在 inputEl 觸發 keydown event (key = 'Enter'),會因為isTrusted = false失敗
43-
*/
44-
// inputEl.addEventListener('change', function (bb) {
45-
// console.log(bb);
46-
// setTimeout(() => {
47-
// var e = new Event("keydown");
48-
// // e.key = "Enter";
49-
// // e.code = "Enter";
50-
// // e.which = 13;
51-
// // e.keyCode = 13;
52-
// // e.bubbles = true;
53-
// // e.cancelBubble = true;
54-
// // e.cancelable = true;
55-
// // e.composed = true;
56-
// // e.location = 3;
57-
// /**
58-
// * The isTrusted read-only property of the Event interface is a boolean that is true when the event was generated by a user action,
59-
// * and false when the event was created or modified by a script or dispatched via dispatchEvent.
60-
// */
61-
// // e.isTrusted = true;
62-
// inputEl.dispatchEvent(e);
63-
// }, 1000);
64-
// }, false);
65-
66-
67-
/**
68-
* @description submitEl(送出的按鈕) 是使用<div role="button"> 但沒有click event或其他event 送出密碼
69-
*/
70-
71-
// setTimeout(() => {
72-
// let submitEl = ev.target.querySelector('.aLF-aPX-a1E-JD-Zc');
73-
// // submitEl.addEventListener('click', function (e) {
74-
// // console.log(e);
75-
// // }, false);
76-
77-
// // submitEl.dispatchEvent('click');
78-
79-
// for (var key in submitEl) {
80-
// if (key.search('on') === 0) {
81-
// submitEl.addEventListener(key.slice(2), function (e) {
82-
// console.log(e);
83-
// }, false)
84-
// }
85-
// }
86-
// //console.log(submitEl);
87-
// }, 3000);
38+
catch (error) {
39+
console.error("錯誤:處理 PDF 密碼對話框時發生問題。", error);
40+
}
41+
finally {
42+
// 無論是否發生錯誤,都重新連接觀察器,繼續監聽 DOM 變化。
43+
observer.observe(document.body, { childList: true, subtree: true, attributes: true });
44+
}
45+
}, 1500);
46+
}
47+
// 創建一個 MutationObserver 實例來監聽 DOM 樹的變化。
48+
// MutationObserver 比 DOMSubtreeModified 更加高效和推薦。
49+
const observer = new MutationObserver((mutationsList) => {
50+
for (const mutation of mutationsList) {
51+
if (mutation.type === 'childList' || mutation.type === 'attributes') {
52+
// 檢查被修改的目標元素本身是否為對話框。
53+
const targetElement = mutation.target instanceof Element ? mutation.target : null;
54+
if (targetElement && targetElement.getAttribute('role') === 'dialog' && targetElement.classList.contains('aLF-aPX-axU')) {
55+
handlePasswordDialog(targetElement);
56+
return;
57+
}
58+
// 檢查所有新加入的節點,看是否有符合條件的對話框。
59+
mutation.addedNodes.forEach(node => {
60+
if (node instanceof Element && node.getAttribute('role') === 'dialog' && node.classList.contains('aLF-aPX-axU')) {
61+
handlePasswordDialog(node);
62+
return;
63+
}
64+
});
65+
}
66+
}
67+
});
68+
// 開始觀察整個 document body,監聽其子節點、所有後代子樹以及屬性的變化。
69+
observer.observe(document.body, { childList: true, subtree: true, attributes: true });

package-lock.json

Lines changed: 19 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)