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

النصوص البرمجية للمحتوى هي ملفات يتم تشغيلها في سياق صفحات الويب. وباستخدام نموذج كائن المستند (DOM) القياسي، يمكنهم قراءة تفاصيل صفحات الويب التي يزورها المتصفح وإجراء تغييرات عليها وتمرير المعلومات إلى إضافتهم الرئيسية.

التعرّف على إمكانيات النصوص البرمجية للمحتوى

يمكن للنصوص البرمجية للمحتوى الوصول إلى ملفات الإضافات بعد إعلانها باعتبارها موارد يمكن الوصول إليها على الويب. ويمكنهم الوصول إلى واجهات برمجة التطبيقات للإضافات التالية مباشرةً:

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

العمل في عوالم منعزلة

تعيش النصوص البرمجية للمحتوى في عالم معزول، ما يسمح للنص البرمجي للمحتوى بإجراء تغييرات على بيئة JavaScript بدون التعارض مع النصوص البرمجية للمحتوى الخاصة بالصفحة أو الإضافات الأخرى.

يمكن تشغيل إضافة في صفحة ويب باستخدام رمز يشبه المثال التالي.

webPage.html

<html>
  <button id="mybutton">click me</button>
  <script>
    var greeting = "hello, ";
    var button = document.getElementById("mybutton");
    button.person_name = "Bob";
    button.addEventListener(
        "click", () => alert(greeting + button.person_name + "."), false);
  </script>
</html>

يمكن لتلك الإضافة إدخال النص البرمجي التالي للمحتوى باستخدام أحد الأساليب الموضّحة في قسم إدخال النصوص البرمجية.

content-script.js

var greeting = "hola, ";
var button = document.getElementById("mybutton");
button.person_name = "Roberto";
button.addEventListener(
    "click", () => alert(greeting + button.person_name + "."), false);

مع هذا التغيير، يظهر كلا التنبيهين بالتسلسل عند النقر على الزر.

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

يمكن الإعلان بشكل ثابت عن النصوص البرمجية للمحتوى أو الإعلان عنها بشكل ديناميكي أو إدراجها آليًا.

الإدخال باستخدام تعريفات ثابتة

استخدِم نماذج تعريف النص البرمجي للمحتوى الثابت في release.json للنصوص البرمجية التي يجب تشغيلها تلقائيًا على مجموعة معروفة من الصفحات.

ويتم تسجيل النصوص البرمجية المعلَن عنها بشكلٍ ثابت في ملف البيان ضمن مفتاح "content_scripts". ويمكن أن تتضمن ملفات JavaScript أو ملفات CSS أو كليهما. يجب أن تحدّد جميع النصوص البرمجية للمحتوى التي يتم تشغيلها تلقائيًا أنماط مطابقة.

manifest.json

{
 "name": "My extension",
 ...
 "content_scripts": [
   {
     "matches": ["https://*.nytimes.com/*"],
     "css": ["my-styles.css"],
     "js": ["content-script.js"]
   }
 ],
 ...
}

الاسم النوع الوصف
matches صفيفة من السلاسل مطلوبة. تحدّد الصفحات التي سيتم إدخال هذا النص البرمجي للمحتوى إليها. اطّلِع على أنماط المطابقة للحصول على تفاصيل حول بنية هذه السلاسل، واطّلِع على مطابقة الأنماط والجداول الفائقة للحصول على معلومات عن كيفية استبعاد عناوين URL.
css صفيفة من السلاسل اختيارية. قائمة ملفات CSS المطلوب إدخالها في الصفحات المطابقة. يتم إدخال هذه العناصر بالترتيب الذي تظهر به في هذه المصفوفة، قبل إنشاء أي نموذج العناصر في المستند (DOM) أو عرضه للصفحة.
js مصفوفة من السلاسل اختيارية. قائمة ملفات JavaScript التي يتم إدخالها في الصفحات المطابقة. يتم إدخال الملفات بالترتيب الذي تظهر به في هذه المصفوفة. يجب أن تحتوي كل سلسلة في هذه القائمة على مسار نسبي إلى مورد في الدليل الجذري للإضافة. يتم اقتطاع الشرطات المائلة البادئة (`/`) تلقائيًا.
run_at RunAt اختيارية. تُحدِّد هذه السمة الوقت الذي يجب فيه إدخال النص البرمجي في الصفحة. وتكون الإعدادات التلقائية document_idle.
match_about_blank boolean اختيارية. تحدّد هذه السمة ما إذا كان يجب إدخال النص البرمجي في إطار about:blank حيث يتطابق الإطار الرئيسي أو الإطار المفتوح مع أحد الأنماط المحدّدة في matches. يكون الإعداد التلقائي بالقيمة "خطأ".
match_origin_as_fallback boolean اختيارية. تُستخدَم لتحديد ما إذا كان يجب إدخال النص البرمجي في الإطارات التي تم إنشاؤها من خلال مصدر مطابق، ولكن قد لا يتطابق عنوان URL أو الأصل مع النمط مباشرةً. ويشمل ذلك الإطارات ذات المخططات المختلفة، مثل about: وdata: وblob: وfilesystem:. راجِع أيضًا إدخال إطارات ذات صلة.
world ExecutionWorld اختيارية. عالم JavaScript لتنفيذ النص البرمجي بداخله. وتكون الإعدادات التلقائية ISOLATED. راجِع أيضًا العمل في عوالم منعزلة.

الإدخال باستخدام التعريفات الديناميكية

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

تتشابه التعريفات الديناميكية مع الإقرارات الثابتة في Chrome 96، إلا أنّ كائن النص البرمجي للمحتوى مسجَّل في Chrome باستخدام طُرق في مساحة الاسم chrome.scripting بدلاً من طُرق manifest.json. تسمح واجهة برمجة تطبيقات Scripting API أيضًا لمطوّري الإضافات بإجراء ما يلي:

  • تسجيل النصوص البرمجية للمحتوى
  • الحصول على قائمة بالنصوص البرمجية للمحتوى المسجّلة
  • تعديل قائمة النصوص البرمجية للمحتوى المسجّلة.
  • إزالة النصوص البرمجية للمحتوى المسجّلة

مثل التعريفات الثابتة، يمكن أن تتضمّن التعريفات الديناميكية ملفات JavaScript أو ملفات CSS أو كليهما.

service-work.js

chrome.scripting
  .registerContentScripts([{
    id: "session-script",
    js: ["content.js"],
    persistAcrossSessions: false,
    matches: ["*://example.com/*"],
    runAt: "document_start",
  }])
  .then(() => console.log("registration complete"))
  .catch((err) => console.warn("unexpected error", err))

service-work.js

chrome.scripting
  .updateContentScripts([{
    id: "session-script",
    excludeMatches: ["*://admin.example.com/*"],
  }])
  .then(() => console.log("registration updated"));

service-work.js

chrome.scripting
  .getRegisteredContentScripts()
  .then(scripts => console.log("registered content scripts", scripts));

service-work.js

chrome.scripting
  .unregisterContentScripts({ ids: ["session-script"] })
  .then(() => console.log("un-registration complete"));

الإدخال آليًا

استخدِم الحقن الآلي للنصوص البرمجية للمحتوى التي يجب تشغيلها استجابةً للأحداث أو في مناسبات معيّنة.

لإدخال نص برمجي للمحتوى بطريقة آلية، تحتاج إضافتك إلى أذونات المضيف للصفحة التي تحاول إدخال نصوص برمجية فيها. يمكنك منح أذونات المضيف إمّا من خلال طلبها كجزء من بيان الإضافة أو باستخدام "activeTab" مؤقتًا.

في ما يلي إصدارات مختلفة من إضافة تستند إلى علامة تبويب نشطة.

manifest.json:

{
  "name": "My extension",
  ...
  "permissions": [
    "activeTab",
    "scripting"
  ],
  "background": {
    "service_worker": "background.js"
  },
  "action": {
    "default_title": "Action Button"
  }
}

يمكن إدخال النصوص البرمجية للمحتوى كملفات.

content-script.js


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

service-work.js:

chrome.action.onClicked.addListener((tab) => {
  chrome.scripting.executeScript({
    target: { tabId: tab.id },
    files: ["content-script.js"]
  });
});

أو يمكن إدخال نص دالة وتنفيذه كنص برمجي للمحتوى.

service-work.js:

function injectedFunction() {
  document.body.style.backgroundColor = "orange";
}

chrome.action.onClicked.addListener((tab) => {
  chrome.scripting.executeScript({
    target : {tabId : tab.id},
    func : injectedFunction,
  });
});

يُرجى العِلم أنّ الدالة التي تم إدخالها هي نسخة من الدالة المُشار إليها في استدعاء chrome.scripting.executeScript()، وليست الدالة الأصلية نفسها. ونتيجة لذلك، يجب أن يكون نص الدالة محصورًا ذاتيًا، أي أن الإشارات إلى المتغيرات خارج الدالة ستؤدي إلى عرض النص البرمجي للدالة ReferenceError.

عند الإدخال كدالة، يمكنك أيضًا تمرير الوسيطات إلى الدالة.

service-work.js

function injectedFunction(color) {
  document.body.style.backgroundColor = color;
}

chrome.action.onClicked.addListener((tab) => {
  chrome.scripting.executeScript({
    target : {tabId : tab.id},
    func : injectedFunction,
    args : [ "orange" ],
  });
});

استبعاد المحتوى المطابق والغلّابه

لتخصيص مطابقة الصفحات المحدَّدة، يجب تضمين الحقول التالية في تسجيل بياني.

الاسم النوع الوصف
exclude_matches صفيفة من السلاسل اختيارية. تستبعد هذه السمة الصفحات التي تم إدخال نص المحتوى النصي هذا فيها. يُرجى الاطّلاع على أنماط المطابقة للحصول على تفاصيل حول بنية هذه السلاسل.
include_globs صفيفة من السلاسل اختيارية. يتم تطبيقها بعد matches لتضمين عناوين URL التي تتطابق أيضًا مع هذه الكرة الأرضية فقط. ويهدف هذا الإجراء إلى محاكاة الكلمة الرئيسية @include GreaseMoner.
exclude_globs صفيفة السلسلة اختيارية. تم تطبيقها بعد matches لاستبعاد عناوين URL التي تتطابق مع هذه الكرة الأرضية. مخصصة لمحاكاة الكلمة الرئيسية @exclude Greasemonkey.

وسيتم إدخال النص البرمجي للمحتوى في الصفحة إذا انطبق العبارتان التاليَين:

  • ويتطابق عنوان URL الخاص بهذه الوحدة مع أي نمط matches وأي نمط include_globs.
  • لا يتطابق عنوان URL أيضًا مع نمط exclude_matches أو exclude_globs. بما أنّ السمة matches مطلوبة، لا يمكن استخدام exclude_matches وinclude_globs وexclude_globs إلا للحدّ من الصفحات المتأثرة.

تضيف الإضافة التالية النص البرمجي للمحتوى في https://www.nytimes.com/health ولكنها لا تضيفه إلى https://www.nytimes.com/business .

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "exclude_matches": ["*://*/*business*"],
      "js": ["contentScript.js"]
    }
  ],
  ...
}

service-work.js

chrome.scripting.registerContentScripts([{
  id : "test",
  matches : [ "https://*.nytimes.com/*" ],
  excludeMatches : [ "*://*/*business*" ],
  js : [ "contentScript.js" ],
}]);

تتّبِع خصائص Glob بنية مختلفة وأكثر مرونة من أنماط المطابقة. سلاسل glob المقبولة هي عناوين URL قد تحتوي على علامات نجمية "حرف البدل" وعلامات استفهام. تتطابق علامة النجمة (*) مع أي سلسلة بأي طول، بما في ذلك السلسلة الفارغة، بينما تتطابق علامة الاستفهام (?) مع أي حرف مفرد.

على سبيل المثال، تتطابق علامة glob https://???.example.com/foo/\* مع أي مما يلي:

  • https://www.example.com/foo/bar
  • https://the.example.com/foo/

إلا أنه لا يطابق ما يلي:

  • https://my.example.com/foo/bar
  • https://example.com/foo/
  • https://www.example.com/foo

تضيف هذه الإضافة النص البرمجي للمحتوى في https://www.nytimes.com/arts/index.html وhttps://www.nytimes.com/jobs/index.htm*، ولكن ليس في https://www.nytimes.com/sports/index.html:

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "include_globs": ["*nytimes.com/???s/*"],
      "js": ["contentScript.js"]
    }
  ],
  ...
}

تضيف هذه الإضافة النص البرمجي للمحتوى في https://history.nytimes.com وhttps://.nytimes.com/history، ولكن ليس في https://science.nytimes.com أو https://www.nytimes.com/science:

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "exclude_globs": ["*science*"],
      "js": ["contentScript.js"]
    }
  ],
  ...
}

يمكن تضمين واحد أو كل أو بعض هذه لتحقيق النطاق الصحيح.

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "exclude_matches": ["*://*/*business*"],
      "include_globs": ["*nytimes.com/???s/*"],
      "exclude_globs": ["*science*"],
      "js": ["contentScript.js"]
    }
  ],
  ...
}

وقت التنفيذ

يحدّد الحقل run_at وقت إدخال ملفات JavaScript في صفحة الويب. القيمة المفضّلة والتلقائية هي "document_idle". راجِع النوع RunAt للاطّلاع على القيم المحتملة الأخرى.

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "run_at": "document_idle",
      "js": ["contentScript.js"]
    }
  ],
  ...
}

service-work.js

chrome.scripting.registerContentScripts([{
  id : "test",
  matches : [ "https://*.nytimes.com/*" ],
  runAt : "document_idle",
  js : [ "contentScript.js" ],
}]);
الاسم النوع الوصف
document_idle سلسلة الخيار المفضّل. استخدِم "document_idle" كلما أمكن ذلك.

يختار المتصفّح وقتًا لإدخال نصوص برمجية بين "document_end" وبعد تنشيط حدث window.onload مباشرةً. تعتمد لحظة الحقن الدقيقة على مدى تعقيد المستند والمدة التي يستغرقها التحميل، ويتم تحسينها لتتوافق مع سرعة تحميل الصفحة.

لا تحتاج النصوص البرمجية للمحتوى التي يتم تشغيلها في "document_idle" إلى الاستماع إلى حدث window.onload، ولكنها مضمونة ليتم تشغيلها بعد اكتمال نموذج العناصر في المستند (DOM). إذا كان هناك نص برمجي بحاجة بالتأكيد إلى التشغيل بعد window.onload، يمكن للإضافة التحقّق مما إذا تم تنشيط onload باستخدام السمة document.readyState.
document_start سلسلة يتم إدخال النصوص البرمجية بعد أي ملفات من css، ولكن قبل إنشاء أي نموذج العناصر في المستند (DOM) آخر أو تشغيل أي نص برمجي آخر.
document_end سلسلة يتم إدخال النصوص البرمجية على الفور بعد اكتمال نموذج العناصر في المستند (DOM)، ولكن قبل تحميل الموارد الفرعية، مثل الصور والإطارات.

تحديد الإطارات

ويتيح الحقل "all_frames" للإضافة تحديد ما إذا كان يجب إدخال ملفات JavaScript وCSS في جميع الإطارات التي تتطابق مع متطلبات عنوان URL المحدّدة أو في الإطار العلوي فقط ضمن علامة تبويب.

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "all_frames": true,
      "js": ["contentScript.js"]
    }
  ],
  ...
}

service-work.js

chrome.scripting.registerContentScripts([{
  id: "test",
  matches : [ "https://*.nytimes.com/*" ],
  allFrames : true,
  js : [ "contentScript.js" ],
}]);
الاسم النوع الوصف
all_frames boolean اختيارية. يتم ضبط القيمة التلقائية على false، ما يعني أنه تتم مطابقة الإطار العلوي فقط.

إذا تم تحديد true، سيتم إدخال جميع الإطارات فيها، حتى إذا لم يكن الإطار هو الإطار العلوي في علامة التبويب. يتم التحقّق من كل إطار بشكل مستقل لمعرفة متطلبات عناوين URL. ولن يتم إدخاله في الإطارات الفرعية في حال عدم استيفاء متطلبات عناوين URL.

قد تريد الإضافات تشغيل نصوص برمجية في إطارات مرتبطة بإطار مطابق، ولكنها لا تتطابق في حد ذاتها. هناك سيناريو شائع عندما يكون هذا هو الحال بالنسبة للإطارات التي تحتوي على عناوين URL تم إنشاؤها بواسطة إطار مطابق، ولكن عناوين URL الخاصة بها لا تطابق الأنماط المحددة للنص البرمجي.

يحدث ذلك عندما تريد إحدى الإضافات إدخال إطارات بها عناوين URL تتضمّن المخططات about: وdata: وblob: وfilesystem:. في هذه الحالات، لن يتطابق عنوان URL مع نمط النص البرمجي للمحتوى (وفي حالة about: وdata:، لا تضمّن حتى عنوان URL أو المصدر الرئيسي في عنوان URL على الإطلاق، كما في about:blank أو data:text/html,<html>Hello, World!</html>). ومع ذلك، يمكن أن تظل هذه الإطارات مرتبطة بإطار الإنشاء.

ولإدخال هذه الإطارات، يمكن للإضافات تحديد السمة "match_origin_as_fallback" في مواصفات النص البرمجي للمحتوى في البيان.

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.google.com/*"],
      "match_origin_as_fallback": true,
      "js": ["contentScript.js"]
    }
  ],
  ...
}

وعند التحديد والضبط على true، سيفحص Chrome أصل منشئ الإطار لتحديد ما إذا كان الإطار مطابقًا أم لا، وليس على عنوان URL للإطار نفسه. يُرجى العِلم أنّه قد يختلف هذا أيضًا عن أصل الإطار المستهدَف (مثل data: من عناوين URL تحتوي على أصل فارغ).

إن بدء الإطار هو الإطار الذي أنشأ الإطار المستهدف أو تنقله. وعلى الرغم من أن هذا عادةً ما يكون الأصل المباشر أو الافتتاحية، إلا أنه قد لا يكون كذلك (كما هو الحال في إطار يتنقل بين إطار iframe داخل إطار iframe).

وبما أنّ هذا يقارن أصل إطار المُنشئ، يمكن أن يكون إطار المُنشئ على أي مسار من ذلك المصدر. لتوضيح ذلك، يتطلب Chrome أي نصوص برمجية للمحتوى تم تحديدها مع "match_origin_as_fallback" على true، وذلك لتحديد مسار * أيضًا.

عند تحديد كل من "match_origin_as_fallback" و"match_about_blank"، تكون الأولوية للسمة "match_origin_as_fallback".

التواصل مع صفحة التضمين

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

يمكن تنفيذ مثال باستخدام window.postMessage():

content-script.js

var port = chrome.runtime.connect();

window.addEventListener("message", (event) => {
  // We only accept messages from ourselves
  if (event.source !== window) {
    return;
  }

  if (event.data.type && (event.data.type === "FROM_PAGE")) {
    console.log("Content script received: " + event.data.text);
    port.postMessage(event.data.text);
  }
}, false);

example.js

document.getElementById("theButton").addEventListener("click", () => {
  window.postMessage(
      {type : "FROM_PAGE", text : "Hello from the webpage!"}, "*");
}, false);

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

الوصول إلى ملفات الامتداد

للوصول إلى ملف الإضافة من نصّ برمجي للمحتوى، يمكنك استدعاء chrome.runtime.getURL() للحصول على عنوان URL المطلق لمادة عرض الإضافة، كما هو موضّح في المثال التالي (content.js):

content-script.js

let image = chrome.runtime.getURL("images/my_image.png")

لاستخدام الخطوط أو الصور في ملف CSS، يمكنك استخدام @@extension_id لإنشاء عنوان URL كما هو موضّح في المثال التالي (content.css):

content.css

body {
 background-image:url('chrome-extension://__MSG_@@extension_id__/background.png');
}

@font-face {
 font-family: 'Stint Ultra Expanded';
 font-style: normal;
 font-weight: 400;
 src: url('chrome-extension://__MSG_@@extension_id__/fonts/Stint Ultra Expanded.woff') format('woff');
}

يجب الإعلان عن جميع مواد العرض على أنّها موارد يمكن الوصول إليها على الويب في ملف manifest.json:

manifest.json

{
 ...
 "web_accessible_resources": [
   {
     "resources": [ "images/*.png" ],
     "matches": [ "https://example.com/*" ]
   },
   {
     "resources": [ "fonts/*.woff" ],
     "matches": [ "https://example.com/*" ]
   }
 ],
 ...
}

الحفاظ على الأمان

على الرغم من أنّ العوالم المعزولة توفّر طبقة حماية، إلّا أنّ استخدام النصوص البرمجية للمحتوى يمكن أن يؤدي إلى ظهور ثغرات أمنية في الإضافة وصفحة الويب. وإذا كان النص البرمجي للمحتوى يتلقّى محتوى من موقع إلكتروني منفصل، مثلاً من خلال استدعاء fetch()، يجب توخي الحذر لفلترة المحتوى ضد هجمات النصوص البرمجية على المواقع الإلكترونية قبل إدخاله. لا تتواصل سوى عبر HTTPS لتجنب هجمات "man-in-the-middle".

تأكد من تصفية صفحات الويب الضارة. على سبيل المثال، تكون الأنماط التالية خطيرة وغير مسموح بها في ملف Manifest V3:

الإجراءات غير المُوصى بها

content-script.js

const data = document.getElementById("json-data");
// WARNING! Might be evaluating an evil script!
const parsed = eval("(" + data + ")");
الإجراءات غير المُوصى بها

content-script.js

const elmt_id = ...
// WARNING! elmt_id might be '); ... evil script ... //'!
window.setTimeout("animate(" + elmt_id + ")", 200);

بدلاً من ذلك، يفضَّل استخدام واجهات برمجة تطبيقات أكثر أمانًا ولا تشغِّل نصوصًا برمجية:

الإجراءات التي يُنصح بها

content-script.js

const data = document.getElementById("json-data")
// JSON.parse does not evaluate the attacker's scripts.
const parsed = JSON.parse(data);
الإجراءات التي يُنصح بها

content-script.js

const elmt_id = ...
// The closure form of setTimeout does not evaluate scripts.
window.setTimeout(() => animate(elmt_id), 200);