3124 words
16 minutes
Chrome Extensions

إيه هي إضافات كروم دي أصلاً؟#

بص يا سيدي، إضافات كروم دي زي برامج صغيرة كده بتركبها على متصفح كروم بتاعك عشان تضيفله حاجات جديدة. يعني مثلاً:

  • تخلي الإعلانات المزعجة دي تختفي خالص
  • تترجملك الصفحات اللي بتتفرج عليها لو مش فاهم حاجة
  • تحفظلك الباسوردات بتاعتك عشان متنساهاش
  • تغير شكل المواقع اللي بتدخل عليها
  • تعملك شغل روتيني كل شوية بدالك

طب إيه هستفيده من المقالة دي؟#

هنتعلم كل حاجة من الألف للياء:

  • هنعرف إزاي الإضافة متركبة من جوه
  • هنفهم الملف الأساسي اللي اسمه manifest.json ده
  • هنتكلم عن السكريبتات اللي بتشتغل في الباك جراوند
  • وكمان السكريبتات اللي بتشتغل جوه صفحات الويب نفسها
  • هنشوف إزاي الحتت دي بتكلم بعضها
  • هنعمل صفحة للإعدادات بتاعة الإضافة
  • هنخزن الداتا بتاعتنا فين وإزاي
  • هنعمل شكل حلو للإضافة
  • هنشوف إزاي نختبرها ونصلح الأخطاء
  • وأخيراً هنرفعها على المتجر بتاع جوجل

الـstructure الخاص بالإضافة#

الإضافة بتاعتنا دي متركبة من شوية ملفات، تعالى نشوف أهمهم:

  extension-folder/
  ├── manifest.json     # البطاقة الشخصية للإضافة
  ├── background.js     # سكريبت الخلفية
  ├── content.js        # سكريبت المحتوى
  ├── popup/
  │   ├── popup.html    # واجهة الإضافة
  │   ├── popup.js      # منطق واجهة الإضافة
  │   └── popup.css     # تنسيق واجهة الإضافة
  ├── options/
  │   ├── options.html  # صفحة الإعدادات
  │   ├── options.js    # منطق الإعدادات
  │   └── options.css   # تنسيق الإعدادات
  └── images/
      ├── icon16.png    # أيقونة صغيرة
      ├── icon48.png    # أيقونة متوسطة
      └── icon128.png   # أيقونة كبيرة
  • manifest.json: ده زي البطاقة الشخصية بتاعة الإضافة، بيقول كل حاجة عنها يعني:

    • اسمها وإصدارها وبتعمل إيه
    • المسموح ليها تعمله وإيه لأ
    • السكريبتات بتاعتها هتشتغل فين وإمتى
    • إيه الملفات اللي المتصفح هيعرف يوصلها
  • ملفات الجافاسكريبت:

    • background.js: ده بيشتغل ورا الكواليس طول ما المتصفح شغال
    • content.js: ده بيشتغل جوه صفحات النت نفسها
    • popup.js: ده بيتحكم في الشكل اللي بيظهر لما تدوس على الإضافة
    • options.js: ده بيتحكم في صفحة الإعدادات بتاعتك
  • ملفات HTML و CSS:

    • popup.html/css: دول مسؤولين عن شكل وتنسيق الويندو اللي بتطلعلك لما تدوس على أيقونة الإضافة
    • options.html/css: دول مسؤولين عن شكل وتنسيق صفحة الإعدادات
  • الصور والأيقونات:

    • أيقونات بمقاسات مختلفة (16×16, 48×48, 128×128)
    • أي صور تانية الإضافة محتاجاها

الملف الأساسي manifest.json ؟#

ده أهم ملف في الموضوع كله، لازم يكون موجود في أول الفولدر بتاع الإضافة. تعالى نشوف مثال عليه:

{
  "manifest_version": 3, // لازم يكون 3 في كروم الجديد لأن الإصدار 2 مش مدعوم بعد 2023
  "name": "Awesome Extension", // اسم الإضافة - لازم يكون واضح ومعبر وأقل من 45 حرف
  "version": "1.0", // رقم الإصدار - يفضل يتبع Semantic Versioning (MAJOR.MINOR.PATCH)
  "description": "This is a cool extension you'll love", // وصف مختصر - لازم يكون واضح وأقل من 132 حرف
  "icons": { // الأيقونات - لازم تكون PNG وبأحجام محددة
    "16": "images/icon16.png", // للتابات والفافيكون
    "48": "images/icon48.png", // للقائمة وصفحة الإضافات
    "128": "images/icon128.png" // لمتجر كروم وصفحة التثبيت
  },
  "action": { // إعدادات زر شريط الأدوات (حل محل browser_action في MV2)
    "default_popup": "popup.html", // الصفحة اللي بتظهر - مينفعش يكون حجمها كبير
    "default_title": "Click here!" // تلميح الماوس - يفضل يكون قصير ومفيد
  },
  "background": { // سكريبت الخلفية - بيتم تحميله وإيقافه حسب الحاجة
    "service_worker": "background.js" // لازم يكون Service Worker في MV3
  },
  "content_scripts": [ // سكريبتات بتتحقن في صفحات الويب
    {
      "matches": ["https://www.facebook.com/*"], // نمط URL - يدعم wildcards
      "js": ["content.js"], // ملفات JS - بتتنفذ بالترتيب
      "css": ["style.css"], // ملفات CSS - بتتطبق فوراً
      "run_at": "document_end" // توقيت التنفيذ - في options: document_start/end/idle
    }
  ],
  "permissions": [ // الصلاحيات - اطلب أقل عدد ممكن
    "activeTab", // للوصول للتاب الحالي فقط
    "storage", // للتخزين المحلي والسحابي
    "scripting", // لتنفيذ JS في التابات
    "tabs", // للتحكم في كل التابات
    "notifications" // لإظهار إشعارات سطح المكتب
  ],
  "host_permissions": [ // أذونات المواقع - حددها بدقة
    "https://www.google.com/" // يدعم wildcards و match patterns
  ],
  "options_page": "options.html", // صفحة الإعدادات - اختيارية
  "web_accessible_resources": [ // موارد متاحة لصفحات الويب
    {
      "resources": ["style.css", "images/"], // الملفات المسموحة - يدعم wildcards
      "matches": ["https://www.facebook.com/*"] // المواقع المصرح لها
    }
  ]
} 

كل سطر في الملف ده ليه معنى:

  • “manifest_version”: ده رقم الإصدار بتاع الملف نفسه، لازم يبقى 3 دلوقتي (مينفعش نستخدم 2 لأن جوجل بطلت تدعمه)
  • “name” و “version”: دول اسم الإضافة ورقم الإصدار بتاعها (لازم يكونوا واضحين ومعبرين)
  • “description”: ده وصف قصير للإضافة (مهم يكون دقيق وبيشرح الفايدة الرئيسية)
  • “icons”: دي الأيقونات بتاعة الإضافة بأحجام مختلفة (16 للتابات، 48 للقائمة، 128 للمتجر)
  • “action”: ده بيحدد إيه اللي هيحصل لما تدوس على أيقونة الإضافة (زي فتح بوب أب أو تنفيذ أمر معين)
  • “background”: ده السكريبت اللي هيشتغل في الخلفية طول الوقت (بيتحكم في الإضافة وبيعمل المهام المستمرة)
  • “content_scripts”: دول السكريبتات اللي هتشتغل جوه صفحات الويب نفسها (بتقدر تعدل في المحتوى وتضيف ميزات)
  • “permissions”: دي الصلاحيات اللي الإضافة محتاجاها (زي التخزين والتنبيهات، اطلب بس اللي محتاجه فعلاً)
  • “host_permissions”: دي المواقع اللي الإضافة هتقدر تشتغل عليها (حددها بدقة عشان الأمان)
  • “options_page”: دي صفحة الإعدادات بتاعة الإضافة (خليها سهلة وواضحة للمستخدم)
  • “web_accessible_resources”: دي الملفات اللي ممكن الويب يوصلها من الإضافة (زي الصور والستايلات، حددها كويس)
  • “commands”: ده بيحدد الشورت كتس بتاعة الإضافة (اختار مفاتيح سهل المستخدم يفتكرها)
  • “default_locale”: ده بيحدد اللغة الأساسية واللغات المدعومة (مهم للإضافات العالمية)
  • “offline_enabled”: ده بيحدد إذا كانت الإضافة بتشتغل من غير نت ولا لأ
  • “update_url”: ده لينك التحديثات التلقائية (لو هتنشر الإضافة برا متجر كروم)

سكريبتات الباك جراوند (Service Workers)#

تخيل معايا إن سكريبتات الباك جراوند دي زي المخ بتاع الإضافة - بتفضل شغالة في الخلفية وبتتحكم في كل حاجة. يعني هي اللي:

  • بتحفظ كل البيانات وبتفتكر إعدادات المستخدم
  • بتعرف لما حد يثبت الإضافة أو يعمل تحديث وتتصرف
  • بتتواصل مع كل أجزاء الإضافة وتنسق بينهم
  • بتعمل شغل مهم في الباك جراوند زي إنها تبعت تنبيهات

خلينا نشوف مثال عملي يوضح الكلام ده:

  // background.js
  // لما حد يثبت الإضافة أو يحدثها
  chrome.runtime.onInstalled.addListener(({ reason, previousVersion }) => {
    if (reason === 'install') {
      console.log("أهلاً بيك! الإضافة اتثبتت لأول مرة");
      // نظبط الإعدادات الأساسية
      chrome.storage.sync.set({ theme: 'light', notifications: true });
    } else if (reason === 'update') {
      console.log(`تم التحديث من نسخة ${previousVersion}`);
      // نعمل اللي محتاجينه للنسخة الجديدة
    }
  });

  // لما المستخدم يدوس على أيقونة الإضافة
  chrome.action.onClicked.addListener(async (tab) => {
    try {
      // نتأكد إن التاب مسموح نشتغل عليه
      if (!tab.url.startsWith('chrome://')) {
        // نضيف واجهة المستخدم في الصفحة
        await chrome.scripting.executeScript({
          target: { tabId: tab.id },
          function: injectUI,
        });
        
        // نغير شكل الأيقونة عشان نعرف إنها شغالة
        await chrome.action.setIcon({
          tabId: tab.id,
          path: {
            16: 'icons/active-16.png',
            48: 'icons/active-48.png'
          }
        });
      }
    } catch (error) {
      console.error("في مشكلة حصلت:", error);
      // نقول للمستخدم إن في مشكلة
      chrome.notifications.create({
        type: 'basic',
        iconUrl: 'icons/error.png',
        title: 'عندنا مشكلة',
        message: error.message
      });
    }
  });

  // الواجهة اللي هتظهر للمستخدم
  function injectUI() {
    const dialog = document.createElement('dialog');
    dialog.innerHTML = `
      <h2>أهلاً بيك!</h2>
      <button onclick="this.parentElement.close()">قفل</button>
    `;
    document.body.appendChild(dialog);
    dialog.showModal();
    
    // نستنى لو المستخدم عمل حاجة في الصفحة
    window.addEventListener('message', (event) => {
      if (event.source === window && event.data.type === 'EXTENSION_ACTION') {
        // نتصرف بناءً على اللي المستخدم عمله
      }
    });
  }

  // نعمل مزامنة كل شوية
  chrome.alarms.create('sync', { periodInMinutes: 30 });
  chrome.alarms.onAlarm.addListener((alarm) => {
    if (alarm.name === 'sync') {
      // نزامن البيانات
      syncData();
    }
  });

  // نراقب لو حد غير في الإعدادات
  chrome.storage.onChanged.addListener((changes, namespace) => {
    for (let [key, { oldValue, newValue }] of Object.entries(changes)) {
      console.log(`الإعداد ${key} اتغير من ${oldValue} لـ ${newValue}`);
    }
  });

وفي كمان حاجات تانية ممكن نعملها زي:

  • التحكم في طلبات النت اللي بتطلع من الإضافة
  • نشغل عمليات تقيلة في الباك جراوند
  • نربط الإضافة بخدمات على النت
  • نخلي الإضافة خفيفة وسريعة
  • نتعامل مع أي مشاكل بشكل محترف
  • نخلي الإضافة تشتغل حتى من غير نت

سكريبتات المحتوى (Content Scripts)#

سكريبتات المحتوى (Content Scripts) هي جزء أساسي في تطوير إضافات المتصفح. هذه السكريبتات تعمل داخل سياق صفحات الويب مباشرةً، مما يتيح لك التفاعل مع محتوى الصفحة وتعديله.

التكوين في manifest.json#

لتفعيل سكريبت المحتوى، يجب أولاً تعريفه في ملف manifest.json:

{
  "content_scripts": [
    {
      "matches": ["*://*.example.com/*"],
      "js": ["content.js"],
      "css": ["styles.css"],
      "run_at": "document_idle"
    }
  ]
}

مثال أساسي لسكريبت محتوى#

هذا مثال بسيط لسكريبت محتوى يغير لون خلفية الصفحة:

console.log("تم تفعيل سكريبت المحتوى");

document.body.style.backgroundColor = "lightblue";

// تغيير اللون كل 5 ثواني
setInterval(() => {
  const colors = ["lightblue", "lightgreen", "lightpink", "lightyellow"];
  const randomColor = colors[Math.floor(Math.random() * colors.length)];
  document.body.style.backgroundColor = randomColor;
}, 5000);

هديك نبذة عن إمكانياته#

1. الوصول للصفحة على طول#

  • تقدر تستخدم كل حاجة في الصفحة زي querySelector و getElementById
  • تتعامل مع العناصر في الصفحة - تضيف وتشيل وتعدل براحتك
  • تقدر تسمع لما حد يدوس أو يسكرول في الصفحة
  • تغير الشكل والألوان بتاعة أي حاجة في الصفحة

2. بيشتغل لوحده#

  • السكريبت بيشتغل على طول لما الصفحة تفتح من غير ما تعمل حاجة
  • تقدر تتحكم امتى يشتغل من خلال run_at:
    • document_start: قبل ما الصفحة تحمل
    • document_end: بعد ما الصفحة تحمل
    • document_idle: بعد ما كل حاجة تخلص
  • ممكن تخليه يشتغل في صفحات معينة بس

3. تحديد المواقع اللي هيشتغل عليها#

  • تحط المواقع اللي عايزها في matches جوة manifest.json
  • في طرق كتير زي:
    • *://*.example.com/*: كل الصفحات في موقع معين
    • http://*/*: كل المواقع العادية
    • https://*/*: كل المواقع المؤمنة
  • ممكن تستثني مواقع معينة بـ exclude_matches
  • تقدر تحط قواعد معقدة عشان تحدد الصفحات بالظبط
  • استخدم chrome.runtime.sendMessage عشان تبعت رسايل للباك جراوند

متنساش تحط الصلاحيات المطلوبة في manifest.json

مثال متقدم: مراقبة تغييرات الصفحة#

const observer = new MutationObserver((mutations) => {
  for (const mutation of mutations) {
    if (mutation.type === 'childList') {
      // معالجة العناصر الجديدة
      mutation.addedNodes.forEach(node => {
        if (node.nodeType === Node.ELEMENT_NODE) {
          // تطبيق التغييرات على العناصر الجديدة
          customizeElement(node);
        }
      });
    }
  }
});

observer.observe(document.body, {
  childList: true,
  subtree: true
});

function customizeElement(element) {
  // تطبيق التخصيصات المطلوبة
  element.style.border = '1px solid blue';
}

نظام الرسايل (Message Passing)#

هو إيه نظام الرسايل ده؟#

ببساطة، ده نظام بيخلي أجزاء الإضافة بتاعتك تتكلم مع بعض. زي ما لو عندك جروب واتساب، كل حد فيه بيبعت رسايل للتاني 😄

ليه محتاجين نظام الرسايل؟#

عشان:

  • نخلي الكونتنت سكريبت يكلم الباك جراوند
  • نبعت داتا من البوب أب للباك جراوند
  • نعمل أي اتصال بين أي جزئين في الإضافة

أنواع الرسايل#

النوع الأول: رسالة واحدة (One-Time Message)#

ده زي لما تبعت رسالة لصاحبك وتستنى رده. بص على المثال ده:

// في الكونتنت سكريبت
// في الكونتنت سكريبت - بنبعت رسالة للباك جراوند عشان نجيب داتا
chrome.runtime.sendMessage({
  type: "getData",
  request: "get statistics"
}, (response) => {
  console.log("Got response:", response);
});

// في الباك جراوند - بنستقبل الرسالة ونرد عليها
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  // بنشوف لو الرسالة نوعها getData
  if (message.type === "getData") {
    // بنرد بأوبجكت فيه الداتا اللي عايزينها
    sendResponse({
      status: "success",
      data: { visits: 100, views: 500 }
    });
  }
  return true; // لازم نرجع true عشان نقدر نبعت الرد بعدين
});

النوع التاني: محادثة مستمرة (Connection)#

ده زي ما تفتح شات مع صاحبك وتفضلوا تتكلموا. شوف كده:

// في الكونتنت سكريبت - بنفتح اتصال مع الباك جراوند
const port = chrome.runtime.connect({name: "chat"});

// بنستمع للرسايل اللي جاية من الباك جراوند
port.onMessage.addListener((message) => {
  console.log("New message received:", message);
});

// بنبعت رسالة للباك جراوند
port.postMessage({
  type: "greeting",
  content: "How are you basha?"
});

// في الباك جراوند - بنستقبل الاتصال من الكونتنت سكريبت
chrome.runtime.onConnect.addListener((port) => {
  // بنستمع للرسايل اللي جاية من الكونتنت سكريبت
  port.onMessage.addListener((message) => {
    // لو الرسالة نوعها تحية، بنرد عليها
    if (message.type === "greeting") {
      port.postMessage({
        type: "reply",
        content: "El7amdullah, enta 3amel eh?"
      });
    }
  });
});

طريقة حلوة للتعامل مع الرسايل (ده ليك مخصوص عشان وصلت هنا 😎)#

class MessageManager {
  constructor() {
    // بنعمل اوبجكت فيه كل الفانكشنز اللي هتتنفذ لما نستقبل رسايل
    this.handlers = {
      'getData': this.getData,
      'saveData': this.saveData,
      'doSomething': this.doSomething
    };

    this.listenToMessages();
  }

  listenToMessages() {
    // بنسمع للرسايل اللي جاية ونشوف هننفذ ايه
    chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
      if (this.handlers[message.type]) {
        this.handlers[message.type](message).then(sendResponse);
        return true;
      }
    });
  }

  async getData(message) {
    // هنا بنجيب الداتا اللي محتاجينها
    return { status: "success", data: {} };
  }

  async saveData(message) {
    // هنا بنحفظ الداتا اللي جاية
    return { status: "saved successfully" };
  }

  async doSomething(message) {
    // هنا بنعمل اي حاجة تانية محتاجينها
    return { status: "completed" };
  }
}

نصايح مهمة (عشان متقعش في مشاكل 😅)#

  1. تأكد من الرسايل وصلت

    // طريقة آمنة لإرسال رسالة
    function sendMessageSafely(message) {
      // بنعمل وعد جديد عشان نتأكد ان الرسالة وصلت
      return new Promise((resolve, reject) => {
        // بنبعت الرسالة للباك جراوند
        chrome.runtime.sendMessage(message, response => {
          // لو في مشكلة حصلت
          if (chrome.runtime.lastError) {
            console.error("Error occurred:", chrome.runtime.lastError);
            reject(chrome.runtime.lastError);
          } else {
            // لو كله تمام نرجع الرد
            resolve(response);
          }
        });
      });
    }
    
  2. متبعتش داتا كبيرة

    • لو عندك داتا كبيرة، قسمها على أجزاء
    • أو استخدم chrome.storage بدل الرسايل
  3. اعمل تايم آوت للرسايل

    async function sendMessageWithTimeout(message, timeout = 5000) {
      // بنعمل وعد بيبعت الرسالة بطريقة آمنة
      const messagePromise = sendMessageSafely(message);
      // بنعمل وعد تاني بيرفض لو عدى الوقت المحدد
      const timeoutPromise = new Promise((_, reject) => {
        setTimeout(() => reject("Timeout exceeded"), timeout);
      });
      
      // بنشوف مين هيخلص الأول - الرسالة ولا الوقت
      return Promise.race([messagePromise, timeoutPromise]);
    }
    

مثال عملي وبسيط#

تخيل إنك عايز تعمل إضافة بتعد عدد الصور في أي صفحة وتحفظ الإحصائيات:

// في الكونتنت سكريبت
// بنجيب عدد الصور في الصفحة
const imageCount = document.querySelectorAll('img').length;

// بنبعت رسالة للباك جراوند عشان يحفظ الاحصائيات
chrome.runtime.sendMessage({
  type: "save_stats",
  data: {
    url: window.location.href,
    images_count: imageCount,
    timestamp: new Date().toISOString()
  }
});

// في الباك جراوند
const statistics = {};

// بنستمع للرسايل اللي جاية
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === "save_stats") {
    const url = message.data.url;
    statistics[url] = message.data;
    sendResponse({ status: "saved" });
  }
  return true;
});
  • الرسايل نوعين: رسالة واحدة أو محادثة مستمرة
  • لازم تتأكد إن الرسايل وصلت
  • ممكن تعمل نظام متقدم للرسايل حسب احتياجك
  • خلي بالك من حجم الداتا اللي بتبعتها

    صفحة الإعدادات (Options Page)#

1. تعريف الصفحة في manifest.json#

أولاً، نحتاج لتعريف صفحة الإعدادات في ملف manifest.json:

{
  "options_ui": {
    "page": "options.html",
    "open_in_tab": true,    // فتح في تاب جديد
    "chrome_style": true    // استخدام ستايل كروم
  }
}

هذا التعريف يخبر متصفح كروم:

  • أي صفحة سيتم عرضها في الإعدادات
  • كيف سيتم عرضها (في تاب أو نافذة منبثقة)
  • هل ستستخدم ستايل كروم الافتراضي

2. HTML#

نقوم بإنشاء صفحة الإعدادات الأساسية:

<!DOCTYPE html>
<html dir="rtl" lang="ar">
<head>
    <meta charset="UTF-8">
    <title>إعدادات الإضافة</title>
    <link rel="stylesheet" href="options.css">
</head>
<body>
    <div class="container">
        <!-- قسم إعدادات المظهر -->
        <section class="settings-section">
            <h2>المظهر</h2>
            <!-- إعداد لون الخلفية -->
            <div class="setting-item">
                <label>لون الخلفية:
                    <input type="color" id="backgroundColor">
                </label>
            </div>
            <!-- إعداد حجم الخط -->
            <div class="setting-item">
                <label>حجم الخط:
                    <select id="fontSize">
                        <option value="small">صغير</option>
                        <option value="medium">متوسط</option>
                        <option value="large">كبير</option>
                    </select>
                </label>
            </div>
        </section>

        <!-- قسم الإعدادات المتقدمة -->
        <section class="settings-section">
            <h2>إعدادات متقدمة</h2>
            <!-- إعداد الإشعارات -->
            <div class="setting-item">
                <label>
                    <input type="checkbox" id="notifications">
                    تفعيل الإشعارات
                </label>
            </div>
            <!-- إعداد فترة التحديث -->
            <div class="setting-item">
                <label>فترة التحديث (دقائق):
                    <input type="number" id="updateInterval" min="1" max="60">
                </label>
            </div>
        </section>

        <!-- أزرار التحكم -->
        <div class="actions">
            <button id="save" class="primary">حفظ الإعدادات</button>
            <button id="reset" class="secondary">إعادة الضبط</button>
        </div>
    </div>
    <script src="options.js"></script>
</body>
</html>

3. CSS#

نضيف التنسيقات في ملف options.css:

/* تنسيقات عامة */
body {
    font-family: system-ui, -apple-system, sans-serif;
    margin: 0;
    padding: 20px;
    background-color: #f5f5f5;
}

/* تنسيق الحاوية الرئيسية */
.container {
    max-width: 800px;
    margin: 0 auto;
    background: white;
    padding: 20px;
    border-radius: 8px;
    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

/* تنسيق أقسام الإعدادات */
.settings-section {
    margin-bottom: 24px;
    padding-bottom: 16px;
    border-bottom: 1px solid #eee;
}

/* تنسيق عناصر الإعدادات */
.setting-item {
    margin: 12px 0;
}

/* تنسيق الأزرار */
.actions {
    display: flex;
    gap: 10px;
    margin-top: 20px;
}

button {
    padding: 8px 16px;
    border-radius: 4px;
    border: none;
    cursor: pointer;
}

.primary {
    background-color: #1a73e8;
    color: white;
}

.secondary {
    background-color: #f1f3f4;
    color: #202124;
}

4. JavaScript#

نكتب المنطق البرمجي في ملف options.js:

// الإعدادات الافتراضية
const DEFAULT_SETTINGS = {
    backgroundColor: '#ffffff',
    fontSize: 'medium',
    notifications: true,
    updateInterval: 5
};

// دالة تحميل الإعدادات
function loadSettings() {
    chrome.storage.sync.get(DEFAULT_SETTINGS, (settings) => {
        // تعيين قيم الإعدادات في الواجهة
        document.getElementById('backgroundColor').value = settings.backgroundColor;
        document.getElementById('fontSize').value = settings.fontSize;
        document.getElementById('notifications').checked = settings.notifications;
        document.getElementById('updateInterval').value = settings.updateInterval;
    });
}

// دالة حفظ الإعدادات
function saveSettings() {
    // جمع القيم من الواجهة
    const settings = {
        backgroundColor: document.getElementById('backgroundColor').value,
        fontSize: document.getElementById('fontSize').value,
        notifications: document.getElementById('notifications').checked,
        updateInterval: parseInt(document.getElementById('updateInterval').value)
    };

    // حفظ الإعدادات
    chrome.storage.sync.set(settings, () => {
        showNotification('تم حفظ الإعدادات بنجاح!');
        
        // إرسال إشعار للـ background script
        chrome.runtime.sendMessage({
            type: 'SETTINGS_UPDATED',
            settings: settings
        });
    });
}

// دالة إعادة الضبط
function resetSettings() {
    if (confirm('هل أنت متأكد من إعادة ضبط جميع الإعدادات؟')) {
        chrome.storage.sync.set(DEFAULT_SETTINGS, () => {
            loadSettings();
            showNotification('تم إعادة ضبط الإعدادات');
        });
    }
}

// دالة إظهار الإشعارات
function showNotification(message) {
    const notification = document.createElement('div');
    notification.className = 'notification';
    notification.textContent = message;
    document.body.appendChild(notification);

    // إزالة الإشعار بعد 3 ثواني
    setTimeout(() => {
        notification.remove();
    }, 3000);
}

5. إعداد الأحداث Events#

نضيف الأحداث في نهاية ملف options.js:

// تحميل الإعدادات عند فتح الصفحة
document.addEventListener('DOMContentLoaded', loadSettings);

// ربط الأزرار بالدوال المناسبة
document.getElementById('save').addEventListener('click', saveSettings);
document.getElementById('reset').addEventListener('click', resetSettings);

// مراقبة التغييرات في الإعدادات
document.querySelectorAll('input, select').forEach(element => {
    element.addEventListener('change', () => {
        // إضافة class للإشارة إلى وجود تغييرات غير محفوظة
        document.getElementById('save').classList.add('modified');
    });
});

تخزين البيانات (Storage API)#

  1. مقدمة عن Storage API
  2. أنواع التخزين
  3. العمليات الأساسية
  4. أمثلة عملية

تعريف في manifest.json#

{
  "permissions": [
    "storage"
  ]
}
  • يجب إضافة صلاحية storage في ملف manifest
  • بدون هذه الصلاحية لن تستطيع استخدام API التخزين

أنواع التخزين#

1. التخزين المتزامن (chrome.storage.sync)#

// تخزين البيانات
chrome.storage.sync.set({
    userName: "أحمد",
    preferences: {
        theme: "dark",
        fontSize: 16
    }
});

// قراءة البيانات
chrome.storage.sync.get(['userName', 'preferences'], (result) => {
    console.log(result.userName);        // أحمد
    console.log(result.preferences);     // {theme: "dark", fontSize: 16}
});
  • يتزامن مع حساب جوجل للمستخدم
  • الحد الأقصى: 100 كيلوبايت
  • مناسب للإعدادات والتفضيلات

2. التخزين المحلي (chrome.storage.local)#

// تخزين البيانات
chrome.storage.local.set({
    cachedData: largeDataObject,
    lastUpdate: new Date().getTime()
});

// قراءة البيانات
chrome.storage.local.get(['cachedData'], (result) => {
    console.log(result.cachedData);
});
  • تخزين محلي على الجهاز فقط
  • الحد الأقصى: 10 ميجابايت
  • مناسب للبيانات الكبيرة والمؤقتة

العمليات الأساسية#

1. التخزين (set)#

// تخزين قيمة واحدة
chrome.storage.sync.set({ key: "value" }, () => {
    console.log("تم التخزين بنجاح");
});

// تخزين عدة قيم
chrome.storage.sync.set({
    key1: "value1",
    key2: "value2",
    key3: {
        nestedKey: "nestedValue"
    }
}, () => {
    console.log("تم تخزين كل القيم");
});

2. القراءة (get)#

// قراءة قيمة واحدة
chrome.storage.sync.get(['key'], (result) => {
    console.log(result.key);
});

// قراءة عدة قيم
chrome.storage.sync.get(['key1', 'key2'], (result) => {
    console.log(result.key1, result.key2);
});

// قراءة كل القيم
chrome.storage.sync.get(null, (result) => {
    console.log(result);
});

3. الحذف (remove)#

// حذف قيمة واحدة
chrome.storage.sync.remove('key', () => {
    console.log("تم حذف القيمة");
});

// حذف عدة قيم
chrome.storage.sync.remove(['key1', 'key2'], () => {
    console.log("تم حذف القيم");
});

4. مسح كل البيانات (clear)#

chrome.storage.sync.clear(() => {
    console.log("تم مسح كل البيانات");
});

أمثلة عملية#

1. مثال: إدارة إعدادات المستخدم#

// الإعدادات الافتراضية
const defaultSettings = {
    theme: 'light',
    fontSize: 14,
    notifications: true,
    language: 'ar'
};

// حفظ الإعدادات
function saveSettings(settings) {
    return new Promise((resolve, reject) => {
        chrome.storage.sync.set({ userSettings: settings }, () => {
            if (chrome.runtime.lastError) {
                reject(chrome.runtime.lastError);
            } else {
                resolve();
            }
        });
    });
}

// قراءة الإعدادات
function loadSettings() {
    return new Promise((resolve, reject) => {
        chrome.storage.sync.get(['userSettings'], (result) => {
            if (chrome.runtime.lastError) {
                reject(chrome.runtime.lastError);
            } else {
                resolve(result.userSettings || defaultSettings);
            }
        });
    });
}

// استخدام الدوال
async function updateSettings(newSettings) {
    try {
        await saveSettings(newSettings);
        console.log("تم حفظ الإعدادات بنجاح");
    } catch (error) {
        console.error("حدث خطأ:", error);
    }
}

2. مثال: مراقبة التغييرات#

// بنراقب التغييرات اللي بتحصل في التخزين
chrome.storage.onChanged.addListener((changes, namespace) => {
    for (let key in changes) {
        let change = changes[key];
        console.log(`
            Change Key: ${key}
            Old Value: ${change.oldValue}
            New Value: ${change.newValue}
            Storage Type: ${namespace}
        `);
    }
});

واجهات المستخدم (UI) المتقدمة#

ممكن تستخدم زي React أو Vue عشان تعمل واجهة مستخدم جامدة. مثال بسيط باستخدام React:


  // بوب_أب.js
  import React, { useState, useEffect } from 'react';
  import ReactDOM from 'react-dom';

  function PopUp() {
    const [color, setColor] = useState('');

    useEffect(() => {
      chrome.storage.sync.get(['backgroundColor'], (result) => {
        if (result.backgroundColor) {
          setColor(result.backgroundColor);
        }
      });
    }, []);

    const handleColorChange = (e) => {
      const newColor = e.target.value;
      setColor(newColor);
      chrome.storage.sync.set({ backgroundColor: newColor });
    };

    return (
      <div>
        <h1>الإضافة الجامدة</h1>
        <p>اختار لون الخلفية يا معلم:</p>
        <input type="color" value={color} onChange={handleColorChange} />
      </div>
    );
  }

  ReactDOM.render(<PopUp />, document.getElementById('root'));

(Debugging)#

  • افتح Chrome DevTools واختار الإضافة بتاعتك من القائمة.
  • استخدم console.log() كتير في الكود بتاعك.
  • اعمل breakpoints في الكود عشان توقف التنفيذ عند نقط معينة.

نشر الإضافة على متجر كروم#

  1. اعمل ملف zip للإضافة بتاعتك.
  2. روح على Chrome Web Store Developer Dashboard.
  3. ادفع 5 دولار مرة واحدة عشان تبقى developer.
  4. ارفع الملف zip بتاعك وحط كل البيانات المطلوبة.
  5. استنى لحد ما جوجل توافق على الإضافة.

نصائح مهمة جداً:#

  • استخدم Offscreen Documents عشان تتعامل مع DOM بره الصفحة الرئيسية.

  • استعمل Declarative Net Request API عشان تتحكم في طلبات الشبكة بكفاءة.

  • اتعلم Cross-origin communication عشان تتعامل مع مواقع مختلفة.

  • طبق Security Best Practices عشان تحمي الإضافة بتاعتك من الهاكرز.

  • استخدم Web Workers عشان تشغل كود معقد من غير ما تعطل الصفحة.

  • جرب Service Workers عشان تخلي الإضافة تشتغل حتى لو الإنترنت مقطوع.

  • استخدم IndexedDB عشان تخزن بيانات كبيرة بكفاءة.

  • جرب WebAssembly عشان تشغل كود سريع جداً في الإضافة.

Join our whatsapp group here
My Channel here