שליטה בהזרקת סקריפטים לתוכן בתוספי דפדפן
תארו לעצמכם: פיתחתם תוסף Chrome שמדגיש מחירים באתר מסחר אלקטרוני. הסקריפט המוזרק עובד בטעינה הראשונית, אבל כשהמשתמש מנווט לעגלת הקניות דרך pushState, הסקריפט מפסיק להגיב—MutationObserver לא מופעל. זהו מלכוד קלאסי של SPA. אנחנו נתקלים במקרים כאלה כמעט בכל פרויקט שני. במאמר זה, נפרק כיצד להזריק נכון סקריפט תוסף, לטפל בניווט SPA, לארגן חילופי נתונים עם סקריפט הרקע, ולהימנע מטעויות אופייניות.
סקריפט תוכן (נקרא גם סקריפט מוזרק) הוא מנגנון להזרקת קוד לדף. אנו משלבים סקריפט תוכן—קובץ JavaScript שהדפדפן מזריק להקשר של דף היעד בסביבה מבודדת, כמתואר בתיעוד תוספי Chrome. זה מספק גישה ל-DOM אך לא למשתני הדף—גם הגנה וגם מגבלה. הניסיון שלנו: 5+ שנים בשוק ולמעלה מ-30 פרויקטים מוצלחים בפיתוח תוספי דפדפן. סקריפטים לתוכן נועדו לשנות את ה-DOM של דף היעד. הלקוחות שלנו בדרך כלל חוסכים 20-30% מזמן הפיתוח על ידי ניצול דפוסי ההזרקה המבוססים שלנו.
כיצד הדפדפן טוען סקריפט תוכן
הנה תהליך שלב-אחר-שלב להזרקת סקריפט תוכן:
- הצהירו על סקריפטים ותנאי ההזרקה שלהם ב-manifest.json (MV3).
- הגדירו את הפרמטר
run_atכדי לשלוט בתזמון ההזרקה. - אופציונלית, השתמשו בהזרקה דינמית דרך
document_startמ-service worker.
ב-manifest.json, הצהירו על סקריפטים ותנאי ההזרקה שלהם. הפרמטר document_end קובע מתי הסקריפט מוזרק. להלן השוואה:
| run_at | רגע ההזרקה | מתי להשתמש |
|---|---|---|
document_idle |
לפני בניית ה-DOM | כדי ליירט בקשות מוקדמות |
{ "manifest_version": 3, "content_scripts": [ { "matches": ["https://*.example.com/*"], "js": ["content/injected.js"], "css": ["content/injected.css"], "run_at": "document_idle", "world": "ISOLATED" } ] } |
ה-DOM מוכן, משאבים עדיין נטענים | כדי לשנות מבנה לפני רינדור |
chrome.scripting.executeScript |
אחרי DOMContentLoaded | ברירת מחדל בטוחה לרוב המשימות |
{
"manifest_version": 3,
"content_scripts": [
{
"matches": ["https://*.example.com/*"],
"js": ["content/injected.js"],
"css": ["content/injected.css"],
"run_at": "document_idle",
"world": "ISOLATED"
}
]
}להזרקה דינמית (מ-service worker או לפי דרישה), השתמשו ב-// background/service-worker.js chrome.action.onClicked.addListener(async (tab) => { await chrome.scripting.executeScript({ target: { tabId: tab.id, allFrames: false }, files: ['content/injected.js'], world: 'ISOLATED' }); }); :
// background/service-worker.js
chrome.action.onClicked.addListener(async (tab) => {
await chrome.scripting.executeScript({
target: { tabId: tab.id, allFrames: false },
files: ['content/injected.js'],
world: 'ISOLATED'
});
}); השוואה בין עולם ISOLATED ו-MAIN
| world | גישה ל-DOM | גישה ל-JS של הדף | בידוד | מתי להשתמש |
|---|---|---|---|---|
| ISOLATED | מלאה | אין | גבוה | ברירת מחדל |
| MAIN | מלאה | מלאה | נמוך | Monkey-patching, יירוט API |
שימוש בעולם ISOLATED בטוח פי 10 מעולם MAIN לרוב המשימות, מכיוון שהוא מונע התנגשויות משתנים מקריות. world: 'MAIN' מעניק גישה למשתני הדף אך מקריב בידוד—השתמשו רק כשבאמת נחוץ (לדוגמה, יירוט קריאות API מקוריות).
למה סקריפט תוכן נכשל בדפי SPA?
בניווט בצד הלקוח, הדפדפן אינו טוען מחדש את סקריפט התוכן. MutationObserver על אלמנט ה-
let lastUrl = location.href;
const urlObserver = new MutationObserver(() => {
if (location.href !== lastUrl) {
lastUrl = location.href;
onNavigate(location.href);
}
});
urlObserver.observe(document.querySelector('title') ?? document.head, {
subtree: true,
characterData: true,
childList: true
}); עבודה עם ה-DOM
סקריפט מוזרק רואה את כל ה-DOM, כולל Shadow DOM. לתוכן דינמי (SPA), MutationObserver הוא חובה. דוגמה: הדגשת כל המחירים בדף:
function highlightPrices() {
const walker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_TEXT,
{
acceptNode(node) {
return /\$[\d,]+\.?\d{0,2}/.test(node.textContent) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
}
}
);
const nodes = [];
while (walker.nextNode()) nodes.push(walker.currentNode);
nodes.forEach(node => {
const span = document.createElement('span');
span.innerHTML = node.textContent.replace(
/(\$[\d,]+\.?\d{0,2})/g,
'<mark class="ext-price-highlight">$1</mark>'
);
node.parentNode.replaceChild(span, node);
});
}
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.addedNodes.length > 0) highlightPrices();
}
});
observer.observe(document.body, { childList: true, subtree: true });
highlightPrices();
בפרויקט מסחר אלקטרוני לאחרונה, צמצמנו את זמן עיבוד ה-DOM מ-120ms ל-30ms לכל ניווט על ידי אצירת עדכונים ב-let lastUrl = location.href; const urlObserver = new MutationObserver(() => { if (location.href !== lastUrl) { lastUrl = location.href; onNavigate(location.href); } }); urlObserver.observe(document.querySelector('title') ?? document.head, { subtree: true, characterData: true, childList: true }); ושימוש ב-function highlightPrices() { const walker = document.createTreeWalker( document.body, NodeFilter.SHOW_TEXT, { acceptNode(node) { return /\$[\d,]+\.?\d{0,2}/.test(node.textContent) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP; } } ); const nodes = []; while (walker.nextNode()) nodes.push(walker.currentNode); nodes.forEach(node => { const span = document.createElement('span'); span.innerHTML = node.textContent.replace( /(\$[\d,]+\.?\d{0,2})/g, '<mark class="ext-price-highlight">$1</mark>' ); node.parentNode.replaceChild(span, node); }); } const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { if (mutation.addedNodes.length > 0) highlightPrices(); } }); observer.observe(document.body, { childList: true, subtree: true }); highlightPrices(); להליכה בעץ. אופטימיזציה זו הביאה להפחתה של 75% בזמן העיבוד.
תקשורת עם Background Service Worker
סקריפט התוכן אינו יכול לגשת ישירות ל-chrome.tabs, לכן השתמשו בשליחת הודעות. לסטרימינג דו-כיווני, הקימו פורט:
// content/injected.js
const port = chrome.runtime.connect({ name: 'content-stream' });
port.onMessage.addListener((msg) => {
if (msg.type === 'DATA_CHUNK') appendChunk(msg.data);
});
port.postMessage({ type: 'START_STREAM', url: location.href });התרחיש האופייני הוא בקשה חד-פעמית דרך // content/injected.js const port = chrome.runtime.connect({ name: 'content-stream' }); port.onMessage.addListener((msg) => { if (msg.type === 'DATA_CHUNK') appendChunk(msg.data); }); port.postMessage({ type: 'START_STREAM', url: location.href }); . ה-background חייב לקרוא ל-chrome.runtime.sendMessage עם true אם התגובה היא אסינכרונית.
איך להימנע מהתנגשויות סגנון בעת הזרקת סקריפט תוכן?
שתי גישות: Shadow DOM לבידוד UI מלא (מספק בידוד סגנון של 100%), או CSS עם ספציפיות גבוהה וקידומות מחלקות ייחודיות (לדוגמה, ext-). אם הדף אוכף CSP מחמיר, הזריקו סגנונות דרך chrome.scripting.insertCSS מה-service worker.
העברת נתונים מהדף לסקריפט התוכן
מכיוון שההקשרים של JavaScript מבודדים, השתמשו ב-window.postMessage מסקריפט הדף (או עולם MAIN) והאזינו בסקריפט התוכן עם אימות event.source === window.
מה כלול בעבודה
פיתוח סקריפט תוכן מפתח כולל:
- הגדרת manifest והצהרת סקריפט
- טיפול בניווט SPA ותוכן דינמי
- אינטגרציה עם background service worker דרך הודעות
- הבטחת בידוד סגנון ועמידה ב-CSP
- בדיקות על דפי יעד (עד 10 אתרים)
- מתן תיעוד ותמיכה
בעיות אופייניות ופתרונות
בעיה נפוצה: סקריפט תוכן מפסיק לפעול אחרי מעבר SPA. פתרון: MutationObserver על
allFrames: true. לביצועים, הריצו פעולות DOM כבדות ב-requestAnimationFrame ומשימות בזמן סרק ב-requestIdleCallback.סיבוכים נוספים
- אם הדף משתמש ב-Shadow DOM, ודאו שסקריפט התוכן שלכם חודר כראוי לשורשי צל פתוחים. שורשים סגורים אינם נגישים.
- כדי להזריק לתוך iframes, הגדירו
allFrames: trueבהגדרת הסקריפט.
עם 5+ שנים בשוק ולמעלה מ-30 פרויקטים שנמסרו, אנו מביאים מומחיות מוכחת בפיתוח תוספי דפדפן. אנו מבטיחים ביצועים אמינים של התוסף ב-Chrome, Edge ו-Opera. צרו קשר להערכת פרויקט או להזמנת סקריפט תוכן מותאם. קבלו ייעוץ—המהנדסים שלנו יעזרו ליישם סקריפט תוכן בכל מורכבות.







