תפריט הקשר לתוספות דפדפן: בעיות אופייניות ופתרונות
בעת פיתוח תפריט הקשר באופן עצמאי, נוצרים לעיתים קרובות כפילויות: בכל פעם ש-service worker מופעל, הפריטים נוצרים מחדש. או שהתפריט לא מופיע על האלמנטים המיועדים. אנחנו פותרים בעיות אלה כבר למעלה מחמש שנים — עם יותר מ-20 פרויקטים הכוללים תפריטי לחיצה ימנית עבור Chrome, Firefox ו-Edge. לדוגמה, לקוח אחד התלונן שאחרי עדכון התוסף, המשתמשים ראו חמישה פריטים זהים. הבדיקה הראתה שהמפתח לא קרא ל-removeAll() לפני יצירת הפריטים. תיקנו את זה תוך שעה, והתפריט הפסיק להכפיל את עצמו. לפי Context Menus API, ניקוי כזה הוא חובה לפעולה יציבה.
יתרונות של פיתוח מקצועי
טעויות בניהול מחזור החיים של פריטי התפריט הן הגורם העיקרי לדליפות זיכרון וכפילויות. טיפול לא נכון בהקשר מוביל לכך שפריטים לא מופיעים על קישורים כשהם אמורים להופיע. מפתח מנוסה יטפל בניקוי עם removeAll(), יגביל את הנראות באמצעות documentUrlPatterns, ויעדכן את המצב באופן דינמי באמצעות storage.onChanged. אנחנו מבטיחים פעולה יציבה ללא חזרות ועמידה במדיניות חנות התוספים. התבניות המוכנות שלנו מאיצות את הפיתוח פי 3 בהשוואה לבנייה מאפס — כלומר הפחתה של 60% בזמן הפיתוח. בפרויקטים שלנו, אנחנו מפחיתים באגים בכ-80% באמצעות בדיקות מקיפות בכל דפדפני היעד. יישום טיפוסי עולה בין 500 ל-2,000 דולר בהתאם למורכבות.
מה כלול בעבודה
- ניתוח דרישות ועיצוב מבנה התפריט תוך התחשבות ב-UX;
- יישום פריטים עם קינון ודינמיקה (בכל עומק);
- אינטגרציה עם API של תרגום, שירותי סימניות, CRM (לדוגמה, Telegram, Notion);
- בדיקות על Chrome, Firefox, Edge (כולל דפדפנים ניידים);
- תיעוד והעברת קוד מקור עם הערות;
- תמיכה לאחר ההשקה (תיקוני באגים, שיפורים);
- הדרכה לצוות שלך על תחזוקת התוסף;
- גישה למאגר הפרטי שלנו עם רכיבים לשימוש חוזר.
מדריך שלב אחר שלב ליצירת תפריט הקשר
שלב 1: הגדרה בסיסית — manifest ויצירת פריטים
הוסף את ההרשאה contextMenus ל-manifest וצור פריטים בתוך מאזין onInstalled. קריטי לקרוא ל-removeAll() לפני היצירה, אחרת יופיעו כפילויות כאשר התוסף מתעדכן.
{ "permissions": ["contextMenus"] } chrome.runtime.onInstalled.addListener(() => {
chrome.contextMenus.removeAll(() => {
chrome.contextMenus.create({
id: 'translate-selection',
title: 'Перевести "%s"',
contexts: ['selection'],
});
chrome.contextMenus.create({
id: 'save-link',
title: 'Сохранить ссылку в список',
contexts: ['link'],
});
chrome.contextMenus.create({
id: 'search-image',
title: 'Поиск по изображению',
contexts: ['image'],
});
});
}); סוגי הקשר שבהם ניתן להשתמש
| הקשר | מופעל על |
|---|---|
{ "permissions": ["contextMenus"] } | טקסט נבחר |
chrome.runtime.onInstalled.addListener(() => { chrome.contextMenus.removeAll(() => { chrome.contextMenus.create({ id: 'translate-selection', title: 'Перевести "%s"', contexts: ['selection'], }); chrome.contextMenus.create({ id: 'save-link', title: 'Сохранить ссылку в список', contexts: ['link'], }); chrome.contextMenus.create({ id: 'search-image', title: 'Поиск по изображению', contexts: ['image'], }); }); }); | לחיצה ימנית על קישור |
selection | לחיצה ימנית על תמונה |
link / image | אלמנטים של מדיה |
video | שדות קלט, textarea |
audio | בכל מקום בדף |
editable | בכל מקום |
טיפול בלחיצות ותפריטים מקוננים
לטיפול בלחיצות, השתמש ב-page על all. תפריטים מקוננים נוצרים על ידי ציון switch.
chrome.contextMenus.onClicked.addListener(async (info, tab) => {
switch (info.menuItemId) {
case 'translate-selection':
await handleTranslate(info.selectionText, tab);
break;
case 'save-link':
await handleSaveLink(info.linkUrl, info.pageUrl, tab);
break;
case 'search-image':
await handleImageSearch(info.srcUrl, tab);
break;
}
});
async function handleTranslate(text, tab) {
const { targetLang = 'ru' } = await chrome.storage.sync.get('targetLang');
const response = await fetch(
`https://api.mymemory.translated.net/get?q=${encodeURIComponent(text)}&langpair=auto|${targetLang}`
);
const data = await response.json();
await chrome.tabs.sendMessage(tab.id, {
type: 'SHOW_TRANSLATION',
original: text,
translated: data.responseData.translatedText,
});
}דוגמה לתפריט מקונן:
chrome.contextMenus.create({
id: 'parent-send-to',
title: 'Отправить в...',
contexts: ['selection', 'link'],
});
chrome.contextMenus.create({
id: 'send-to-telegram',
parentId: 'parent-send-to',
title: 'Telegram',
contexts: ['selection', 'link'],
});
chrome.contextMenus.create({
id: 'send-to-notion',
parentId: 'parent-send-to',
title: 'Notion',
contexts: ['selection', 'link'],
}); עדכונים דינמיים של התפריט
ניתן לעדכן פריט מבלי ליצור אותו מחדש באמצעות info.menuItemId והרשמה לשינויים ב-parentId:
async function updateMenuItemState() {
const { enabled } = await chrome.storage.sync.get('enabled');
chrome.contextMenus.update('toggle-feature', {
title: enabled ? 'Отключить подсветку' : 'Включить подсветку',
});
}
chrome.storage.onChanged.addListener((changes) => {
if ('enabled' in changes) updateMenuItemState();
}); הגבלת נראות לפי URL
הפרמטר chrome.contextMenus.onClicked.addListener(async (info, tab) => { switch (info.menuItemId) { case 'translate-selection': await handleTranslate(info.selectionText, tab); break; case 'save-link': await handleSaveLink(info.linkUrl, info.pageUrl, tab); break; case 'search-image': await handleImageSearch(info.srcUrl, tab); break; } }); async function handleTranslate(text, tab) { const { targetLang = 'ru' } = await chrome.storage.sync.get('targetLang'); const response = await fetch( `https://api.mymemory.translated.net/get?q=${encodeURIComponent(text)}&langpair=auto|${targetLang}` ); const data = await response.json(); await chrome.tabs.sendMessage(tab.id, { type: 'SHOW_TRANSLATION', original: text, translated: data.responseData.translatedText, }); } מאפשר להציג פריט רק בדומיינים ספציפיים. מסכות חייבות להיות בפורמט chrome.contextMenus.create({ id: 'parent-send-to', title: 'Отправить в...', contexts: ['selection', 'link'], }); chrome.contextMenus.create({ id: 'send-to-telegram', parentId: 'parent-send-to', title: 'Telegram', contexts: ['selection', 'link'], }); chrome.contextMenus.create({ id: 'send-to-notion', parentId: 'parent-send-to', title: 'Notion', contexts: ['selection', 'link'], }); . אם תציין update() ללא כוכביות, הפריט לא יופיע. storage ו-async function updateMenuItemState() { const { enabled } = await chrome.storage.sync.get('enabled'); chrome.contextMenus.update('toggle-feature', { title: enabled ? 'Отключить подсветку' : 'Включить подсветку', }); } chrome.storage.onChanged.addListener((changes) => { if ('enabled' in changes) updateMenuItemState(); }); פועלים באופן עצמאי; לא ניתן לשלב אותם.
השוואה לפיתוח פנימי
| קריטריון | יישום פנימי | הפתרון שלנו |
|---|---|---|
| לוח זמנים | 2–4 שבועות | 5–10 ימי עבודה |
| עלות פיתוח | 2,000–5,000 דולר | 500–2,000 דולר |
| בעיות כפילויות בתפריט | נפוצות | מבוטלות |
| תמיכה בדפדפנים | רק Chrome | Chrome, Firefox, Edge |
| תיעוד | אין | מלא |
טעויות אופייניות בפיתוח תפריט הקשר
עם יותר מ-20 פרויקטים מאחורינו, אנחנו יכולים למנוע בעיות בשלב התכנון. שלוש הטעויות הנפוצות ביותר:
-
יצירת פריטים ללא
documentUrlPatterns— בעת הפעלה מחדש של service worker או עדכון תוסף, הפריטים משתכפלים. משתמשים רואים 3–5 ערכים זהים. פתרון: תמיד קרא ל-*://*.example.com/*בתוך מאזיןexample.com. -
תבניות
documentUrlPatternsשגויות — מסכהtargetUrlPatternsללא כוכביות לא עובדת. פורמט נכון:removeAll(). הטעות לא נראית מיד וקשה לאבחן אותה. -
דליפות זיכרון עם תפריטים דינמיים — אם אתה יוצר פריטים חדשים בכל אירוע מבלי להסיר ישנים, הזיכרון מתבזבז. השתמש ב-
removeAll()במקוםonInstalled+documentUrlPatterns.
אנחנו בודקים כל פתרון על שלושה דפדפנים: Chrome, Firefox ו-Edge. זה מבטל פערים בין-דפדפנים שלעתים קרובות מופיעים רק אצל משתמשי קצה. במהלך הבדיקות, אנו מוודאים התנהגות על מערכות הפעלה שונות (Windows, macOS, Linux) ומתחשבים במוזרויות של כל דפדפן בטיפול באירועי תפריט הקשר. אנחנו גם בודקים תאימות ל-Manifest V3, שבו Chrome משתמש כברירת מחדל.
איך להזמין פיתוח תפריט הקשר
צור איתנו קשר לייעוץ — נלמד את המשימה שלך ונציע את הפתרון האופטימלי. הזמן יישום של תפריט לחיצה ימנית לצרכים שלך וקבל תוסף יציב תוך 5–10 ימי עבודה. המחיר מתחיל ב-500 דולר לתפריטים פשוטים.







