اسکریپت های محتوا

اسکریپت های محتوا فایل هایی هستند که در متن صفحات وب اجرا می شوند. با استفاده از Document Object Model (DOM)، آنها می توانند جزئیات صفحات وب را که مرورگر بازدید می کند، بخوانند، تغییراتی در آنها ایجاد کنند و اطلاعات را به برنامه افزودنی والد خود منتقل کنند.

قابلیت های اسکریپت محتوا را درک کنید

اسکریپت‌های محتوا می‌توانند مستقیماً به APIهای افزونه زیر دسترسی داشته باشند:

اسکریپت های محتوا قادر به دسترسی مستقیم به سایر API ها نیستند. اما آنها می توانند با تبادل پیام با سایر بخش های برنامه افزودنی شما به طور غیر مستقیم به آنها دسترسی داشته باشند.

همچنین می‌توانید با استفاده از APIهایی مانند fetch() به فایل‌های دیگر در پسوند خود از یک اسکریپت محتوا دسترسی داشته باشید. برای انجام این کار، باید آنها را به عنوان منابع قابل دسترسی وب اعلام کنید. توجه داشته باشید که این کار همچنین منابع را در معرض هر اسکریپت شخص اول یا شخص ثالث در حال اجرا در همان سایت قرار می دهد.

در جهان های منزوی کار کنید

اسکریپت‌های محتوا در دنیایی منزوی زندگی می‌کنند و به یک اسکریپت محتوا اجازه می‌دهند تا بدون تضاد با صفحه یا اسکریپت‌های محتوای افزونه‌های دیگر، تغییراتی در محیط جاوا اسکریپت خود ایجاد کند.

یک برنامه افزودنی ممکن است در یک صفحه وب با کدی شبیه به مثال زیر اجرا شود.

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>

آن برنامه افزودنی می تواند اسکریپت محتوای زیر را با استفاده از یکی از تکنیک های ذکر شده در بخش Inject scripts تزریق کند.

content-script.js

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

با این تغییر، هر دو هشدار به ترتیب با کلیک روی دکمه ظاهر می شوند.

اسکریپت ها را تزریق کنید

اسکریپت های محتوا را می توان به صورت ایستا اعلان کرد , به صورت پویا , یا به صورت برنامه نویسی تزریق کرد .

با اعلانات استاتیک تزریق کنید

از اعلان‌های اسکریپت محتوای ثابت در manifest.json برای اسکریپت‌هایی که باید به‌طور خودکار در مجموعه‌ای از صفحات شناخته‌شده اجرا شوند، استفاده کنید.

اسکریپت های اعلام شده ایستا در مانیفست تحت کلید "content_scripts" ثبت می شوند. آنها می توانند شامل فایل های جاوا اسکریپت، فایل های CSS یا هر دو باشند. همه اسکریپت های محتوای اجرای خودکار باید الگوهای مطابقت را مشخص کنند.

manifest.json

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

نام تایپ کنید توضیحات
matches آرایه ای از رشته ها مورد نیاز. مشخص می کند که این اسکریپت محتوا به چه صفحاتی تزریق شود. برای اطلاعات بیشتر در مورد نحوه حذف نشانی‌های وب ، الگوهای مطابقت را برای جزئیات بیشتر در مورد نحو این رشته‌ها و الگوها و گلوب‌ها را مطابقت دهید.
css آرایه ای از رشته ها اختیاری. لیستی از فایل های CSS که باید به صفحات منطبق تزریق شوند. اینها به ترتیبی که در این آرایه ظاهر می شوند تزریق می شوند، قبل از اینکه DOM برای صفحه ساخته یا نمایش داده شود.
js آرایه ای از رشته ها اختیاری. لیستی از فایل های جاوا اسکریپت برای تزریق به صفحات مطابقت. فایل ها به ترتیبی که در این آرایه ظاهر می شوند تزریق می شوند. هر رشته در این لیست باید یک مسیر نسبی به یک منبع در فهرست اصلی برنامه افزودنی داشته باشد. اسلش های اصلی (`/`) به طور خودکار بریده می شوند.
run_at RunAt اختیاری. مشخص می کند چه زمانی اسکریپت باید به صفحه تزریق شود. پیش‌فرض document_idle است.
match_about_blank بولی اختیاری. اینکه آیا اسکریپت باید در یک قاب about:blank تزریق شود که در آن قاب اصلی یا بازکننده با یکی از الگوهای اعلام شده در matches مطابقت دارد. پیش فرض به نادرست.
match_origin_as_fallback بولی اختیاری. اینکه آیا اسکریپت باید در فریم‌هایی تزریق شود که توسط یک مبدا منطبق ایجاد شده‌اند، اما URL یا مبدا آنها ممکن است مستقیماً با الگو مطابقت نداشته باشد. اینها شامل فریم‌هایی با طرح‌های مختلف، مانند about: data: blob: و filesystem: . تزریق در فریم های مرتبط را نیز ببینید.
world جهان اعدام اختیاری. دنیای جاوا اسکریپت برای اجرای یک اسکریپت در داخل. به طور پیش فرض به ISOLATED است. همچنین به کار در دنیاهای منزوی نگاه کنید.

با اعلان های پویا تزریق کنید

اسکریپت‌های محتوای پویا زمانی مفید هستند که الگوهای تطبیق برای اسکریپت‌های محتوا به خوبی شناخته نشده باشند یا زمانی که اسکریپت‌های محتوا همیشه نباید به میزبان‌های شناخته شده تزریق شوند.

اعلان‌های پویا که در Chrome 96 معرفی شدند، شبیه اعلان‌های ایستا هستند، اما شی اسکریپت محتوا با استفاده از روش‌هایی در فضای نام chrome.scripting به جای manifest.json در Chrome ثبت می‌شود. Scripting API همچنین به توسعه دهندگان برنامه افزودنی اجازه می دهد:

مانند اعلان‌های استاتیک، اعلان‌های پویا می‌توانند شامل فایل‌های جاوا اسکریپت، فایل‌های CSS یا هر دو باشند.

service-worker.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-worker.js

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

service-worker.js

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

service-worker.js

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

به صورت برنامه ای تزریق کنید

از تزریق برنامه‌ای برای اسکریپت‌های محتوایی که باید در پاسخ به رویدادها یا در مناسبت‌های خاص اجرا شوند، استفاده کنید.

برای تزریق یک اسکریپت محتوا به صورت برنامه‌ای، برنامه افزودنی شما به مجوزهای میزبان برای صفحه‌ای که می‌خواهد اسکریپت‌ها را به آن تزریق کند، نیاز دارد. مجوزهای میزبان را می توان با درخواست آنها به عنوان بخشی از مانیفست برنامه افزودنی یا به طور موقت با استفاده از "activeTab" اعطا کرد.

در زیر نسخه های مختلف یک افزونه مبتنی بر 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-worker.js:

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

یا، یک تابع می تواند به عنوان یک اسکریپت محتوا تزریق و اجرا شود.

service-worker.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-worker.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 اعمال می‌شود تا فقط نشانی‌هایی را شامل شود که با این glob مطابقت دارند. این برای تقلید از کلمه کلیدی @include Greasemonkey در نظر گرفته شده است.
exclude_globs آرایه رشته اختیاری. برای حذف نشانی‌های وب که با این glob مطابقت دارند، پس از matches اعمال می‌شود. در نظر گرفته شده برای تقلید از کلمه کلیدی @exclude Greasemonkey.

در صورتی که هر دو مورد زیر درست باشد، اسکریپت محتوا به صفحه تزریق می شود:

  • URL آن با هر الگوی matches و هر الگوی include_globs مطابقت دارد.
  • نشانی وب همچنین با الگوی 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-worker.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 نه 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 زمانی را کنترل می‌کند که فایل‌های جاوا اسکریپت به صفحه وب تزریق می‌شوند. مقدار ترجیحی و پیش فرض "document_idle" است. برای سایر مقادیر ممکن به نوع RunAt مراجعه کنید.

manifest.json

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

service-worker.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 اجرا شود، برنامه افزودنی می‌تواند با استفاده از ویژگی document.readyState بررسی کند که آیا onload قبلاً اجرا شده است یا خیر.
document_start رشته اسکریپت ها بعد از هر فایلی از css تزریق می شوند، اما قبل از اینکه هر DOM دیگری ساخته شود یا هر اسکریپت دیگری اجرا شود.
document_end رشته اسکریپت ها بلافاصله پس از تکمیل DOM، اما قبل از بارگیری منابع فرعی مانند تصاویر و فریم ها، تزریق می شوند.

قاب ها را مشخص کنید

فیلد "all_frames" به برنامه افزودنی اجازه می‌دهد تا مشخص کند که فایل‌های جاوا اسکریپت و CSS باید به همه فریم‌های مطابق با الزامات URL مشخص شده تزریق شوند یا فقط به بالاترین فریم در یک برگه تزریق شوند.

manifest.json

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

service-worker.js

chrome.scripting.registerContentScripts([{
  id: "test",
  matches : [ "https://*.nytimes.com/*" ],
  allFrames : true,
  js : [ "contentScript.js" ],
}]);
نام تایپ کنید توضیحات
all_frames بولی اختیاری. پیش‌فرض به false می‌شود، به این معنی که فقط فریم بالایی مطابقت دارد.

اگر true مشخص شده باشد، همه فریم ها به داخل تزریق می شوند، حتی اگر فریم بالاترین فریم در برگه نباشد. هر فریم به طور مستقل برای الزامات URL بررسی می شود. اگر الزامات URL برآورده نشود، به فریم های فرزند تزریق نمی شود.

برنامه‌های افزودنی ممکن است بخواهند اسکریپت‌هایی را در فریم‌هایی اجرا کنند که مربوط به یک فریم منطبق است، اما خودشان مطابقت ندارند. یک سناریوی رایج در این مورد، برای فریم هایی با URL هایی است که توسط یک قاب منطبق ایجاد شده اند، اما URL های آنها خودشان با الگوهای مشخص شده اسکریپت مطابقت ندارند.

این مورد زمانی است که یک برنامه افزودنی می‌خواهد در فریم‌هایی URLهایی را تزریق کند که دارای طرح‌های about: , data: , blob: و filesystem: هستند. در این موارد، URL با الگوی اسکریپت محتوا مطابقت ندارد (و در مورد about: و data: حتی نشانی اینترنتی اصلی یا مبدا را در 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 به جای نشانی اینترنتی خود قاب، به مبدأ آغازگر قاب نگاه می‌کند تا تعیین کند آیا فریم مطابقت دارد یا خیر. توجه داشته باشید که این ممکن است با مبدا قاب هدف نیز متفاوت باشد (به عنوان مثال، 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 ارتباط برقرار کنید تا از حملات "مرد در وسط" جلوگیری کنید.

مطمئن شوید که صفحات وب مخرب را فیلتر کنید. برای مثال، الگوهای زیر خطرناک هستند و در 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);

در عوض، API های امن تری را که اسکریپت ها را اجرا نمی کنند ترجیح دهید:

انجام دهید

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);