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

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

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

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

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

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

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

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

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

صفحه وب.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>

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

اسکریپت محتوا.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 یا هر دو باشند. همه اسکریپت‌های محتوای خودکار باید الگوهای منطبق را مشخص کنند.

مانیفست.json

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

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

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

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

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

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

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

سرویس-ورکر.js

browser.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))

سرویس-ورکر.js

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

سرویس-ورکر.js

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

سرویس-ورکر.js

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

تزریق به صورت برنامه‌نویسی‌شده

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

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

در ادامه نسخه‌های مختلف یک افزونه مبتنی بر activeTab آمده است.

مانیفست.json:

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

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

اسکریپت محتوا.js


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

سرویس-ورکر.js:

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

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

سرویس-ورکر.js:

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

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

توجه داشته باشید که تابع تزریق‌شده یک کپی از تابعی است که در فراخوانی browser.scripting.executeScript() به آن ارجاع داده شده است، نه خود تابع اصلی. در نتیجه، بدنه تابع باید مستقل باشد؛ ارجاع به متغیرهای خارج از تابع باعث می‌شود که اسکریپت محتوا ReferenceError را نمایش دهد.

هنگام تزریق به عنوان یک تابع، می‌توانید آرگومان‌هایی را نیز به تابع ارسال کنید.

سرویس-ورکر.js

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

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

حذف تطابق‌ها و جانشین‌ها

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

نام نوع توضیحات
exclude_matches آرایه‌ای از رشته‌ها اختیاری. صفحاتی را که این اسکریپت محتوا در غیر این صورت به آنها تزریق می‌شد، شامل نمی‌شود. برای جزئیات بیشتر در مورد نحو این رشته‌ها، به الگوهای تطبیق مراجعه کنید.
include_globs آرایه‌ای از رشته‌ها اختیاری. بعد از matches اعمال می‌شود تا فقط URLهایی را که با این glob مطابقت دارند، شامل شود. این برای شبیه‌سازی کلمه کلیدی @include Greasemonkey در نظر گرفته شده است.
exclude_globs آرایه‌ای از رشته اختیاری. پس از matches اعمال می‌شود تا URLهایی که با این glob مطابقت دارند، حذف شوند. هدف از این کار شبیه‌سازی کلمه کلیدی @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 تزریق نمی‌کند.

مانیفست.json

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

سرویس-ورکر.js

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

ویژگی‌های جانشین (glob) از سینتکس متفاوت و انعطاف‌پذیرتری نسبت به الگوهای تطبیق پیروی می‌کنند. رشته‌های جانشین قابل قبول، URLهایی هستند که ممکن است حاوی ستاره‌های "wildcard" و علامت سوال باشند. ستاره ( * ) با هر رشته‌ای با هر طولی، از جمله رشته خالی، مطابقت دارد، در حالی که علامت سوال ( ? ) با هر کاراکتر واحدی مطابقت دارد.

برای مثال، 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 تزریق نمی‌کند:

مانیفست.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 تزریق نمی‌کند:

مانیفست.json

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

برای دستیابی به محدوده صحیح، می‌توان یکی، همه یا برخی از این موارد را در نظر گرفت.

مانیفست.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 مراجعه کنید.

مانیفست.json

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

سرویس-ورکر.js

browser.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 مشخص‌شده مطابقت دارند تزریق شوند یا فقط به بالاترین فریم در یک تب:

مانیفست.json

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

هنگام ثبت اسکریپت‌های محتوا از طریق برنامه‌نویسی با استفاده از browser.scripting.registerContentScripts(...) ، می‌توان از پارامتر allFrames برای تعیین اینکه آیا اسکریپت محتوا باید به تمام فریم‌هایی که با الزامات URL مشخص شده مطابقت دارند تزریق شود یا فقط به بالاترین فریم در یک تب، استفاده کرد. این فقط با tabId قابل استفاده است و در صورت مشخص بودن frameIds یا documentIds قابل استفاده نیست:

سرویس-ورکر.js

browser.scripting.registerContentScripts([{
  id: "test",
  matches : [ "https://*.nytimes.com/*" ],
  allFrames : true,
  js : [ "contentScript.js" ],
}]);

افزونه‌ها ممکن است بخواهند اسکریپت‌هایی را در فریم‌هایی اجرا کنند که به یک فریم منطبق مرتبط هستند، اما خودشان با آن فریم مطابقت ندارند. یک سناریوی رایج در این مورد، فریم‌هایی با 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" را در مشخصات اسکریپت محتوا در مانیفست مشخص کنند.

مانیفست.json

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

وقتی مشخص شده و روی true تنظیم شده باشد، کروم برای تعیین اینکه آیا فریم مطابقت دارد یا خیر، به جای URL خود فریم، به مبدأ آغازگر فریم نگاه می‌کند. توجه داشته باشید که این ممکن است با مبدأ فریم هدف نیز متفاوت باشد (مثلاً data: URLها مبدأ تهی دارند).

آغازگر فریم، فریمی است که فریم هدف را ایجاد یا پیمایش کرده است. اگرچه این معمولاً والد مستقیم یا آغازگر است، اما ممکن است اینطور نباشد (مانند موردی که یک فریم، یک iframe را درون یک iframe دیگر پیمایش می‌کند).

از آنجا که این مقایسه، مبدأ فریم آغازگر را نشان می‌دهد، فریم آغازگر می‌تواند در هر مسیری از آن مبدأ روشن باشد. برای روشن شدن این مفهوم، کروم از هر اسکریپت محتوایی که با "match_origin_as_fallback" روی true تنظیم شده باشد، می‌خواهد که مسیر * را نیز مشخص کند.

وقتی هر دو "match_origin_as_fallback" و "match_about_blank" مشخص شده باشند، "match_origin_as_fallback" اولویت پیدا می‌کند.

ارتباط با صفحه جاسازی

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

یک مثال را می‌توان با استفاده از window.postMessage() انجام داد:

اسکریپت محتوا.js

var port = browser.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);

مثال.js

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

صفحه غیر افزونه، example.html، پیام‌هایی را برای خودش ارسال می‌کند. این پیام توسط اسکریپت محتوا رهگیری و بررسی شده و سپس به فرآیند افزونه ارسال می‌شود. به این ترتیب، صفحه یک خط ارتباطی با فرآیند افزونه ایجاد می‌کند. عکس این عمل نیز از طریق روش‌های مشابه امکان‌پذیر است.

دسترسی به فایل‌های افزونه

برای دسترسی به یک فایل افزونه از طریق اسکریپت محتوا، می‌توانید تابع browser.runtime.getURL() را فراخوانی کنید تا URL مطلق فایل افزونه خود را همانطور که در مثال زیر نشان داده شده است ( content.js ) دریافت کنید:

اسکریپت محتوا.js

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

برای استفاده از فونت‌ها یا تصاویر در یک فایل CSS، می‌توانید @@extension_id برای ساخت یک URL مانند مثال زیر ( content.css ) استفاده کنید:

محتوا.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 تعریف شوند:

مانیفست.json

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

سیاست امنیت محتوا

اسکریپت‌های محتوایی که در جهان‌های ایزوله اجرا می‌شوند، سیاست امنیتی محتوای (CSP) زیر را دارند:

script-src 'self' 'wasm-unsafe-eval' 'inline-speculation-rules' chrome-extension://abcdefghijklmopqrstuvwxyz/; object-src 'self';

مشابه محدودیت‌های اعمال‌شده بر سایر زمینه‌های افزونه، این مورد از استفاده از eval() و همچنین بارگیری اسکریپت‌های خارجی جلوگیری می‌کند.

برای افزونه‌های بدون بسته‌بندی، CSP شامل localhost نیز می‌شود:

script-src 'self' 'wasm-unsafe-eval' 'inline-speculation-rules' http://localhost:* http://127.0.0.1:* chrome-extension://abcdefghijklmopqrstuvwxyz/; object-src 'self';

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

ایمن بمانید

در حالی که جهان‌های ایزوله یک لایه محافظتی ایجاد می‌کنند، استفاده از اسکریپت‌های محتوا می‌تواند باعث ایجاد آسیب‌پذیری در یک افزونه و صفحه وب شود. اگر اسکریپت محتوا، محتوا را از یک وب‌سایت جداگانه دریافت می‌کند، مثلاً با فراخوانی fetch() ، مراقب باشید که قبل از تزریق، محتوا را در برابر حملات اسکریپت‌نویسی بین‌سایتی فیلتر کنید. برای جلوگیری از حملات "مرد میانی"، فقط از طریق HTTPS ارتباط برقرار کنید.

حتماً صفحات وب مخرب را فیلتر کنید. برای مثال، الگوهای زیر خطرناک هستند و در Manifest V3 مجاز نیستند:

نکن

اسکریپت محتوا.js

const data = document.getElementById("json-data");
// WARNING! Might be evaluating an evil script!
const parsed = eval("(" + data + ")");
نکن

اسکریپت محتوا.js

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

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

انجام دهید

اسکریپت محتوا.js

const data = document.getElementById("json-data")
// JSON.parse does not evaluate the attacker's scripts.
const parsed = JSON.parse(data);
انجام دهید

اسکریپت محتوا.js

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

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

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

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

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

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

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

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

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

صفحه وب.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>

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

اسکریپت محتوا.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 یا هر دو باشند. همه اسکریپت‌های محتوای خودکار باید الگوهای منطبق را مشخص کنند.

مانیفست.json

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

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

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

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

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

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

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

سرویس-ورکر.js

browser.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))

سرویس-ورکر.js

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

سرویس-ورکر.js

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

سرویس-ورکر.js

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

تزریق به صورت برنامه‌نویسی‌شده

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

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

در ادامه نسخه‌های مختلف یک افزونه مبتنی بر activeTab آمده است.

مانیفست.json:

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

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

اسکریپت محتوا.js


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

سرویس-ورکر.js:

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

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

سرویس-ورکر.js:

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

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

توجه داشته باشید که تابع تزریق‌شده یک کپی از تابعی است که در فراخوانی browser.scripting.executeScript() به آن ارجاع داده شده است، نه خود تابع اصلی. در نتیجه، بدنه تابع باید مستقل باشد؛ ارجاع به متغیرهای خارج از تابع باعث می‌شود که اسکریپت محتوا ReferenceError را نمایش دهد.

هنگام تزریق به عنوان یک تابع، می‌توانید آرگومان‌هایی را نیز به تابع ارسال کنید.

سرویس-ورکر.js

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

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

حذف تطابق‌ها و جانشین‌ها

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

نام نوع توضیحات
exclude_matches آرایه‌ای از رشته‌ها اختیاری. صفحاتی را که این اسکریپت محتوا در غیر این صورت به آنها تزریق می‌شد، شامل نمی‌شود. برای جزئیات بیشتر در مورد نحو این رشته‌ها، به الگوهای تطبیق مراجعه کنید.
include_globs آرایه‌ای از رشته‌ها اختیاری. بعد از matches اعمال می‌شود تا فقط URLهایی را که با این glob مطابقت دارند، شامل شود. این برای شبیه‌سازی کلمه کلیدی @include Greasemonkey در نظر گرفته شده است.
exclude_globs آرایه‌ای از رشته اختیاری. پس از matches اعمال می‌شود تا URLهایی که با این glob مطابقت دارند، حذف شوند. هدف از این کار شبیه‌سازی کلمه کلیدی @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 تزریق نمی‌کند.

مانیفست.json

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

سرویس-ورکر.js

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

ویژگی‌های جانشین (glob) از سینتکس متفاوت و انعطاف‌پذیرتری نسبت به الگوهای تطبیق پیروی می‌کنند. رشته‌های جانشین قابل قبول، URLهایی هستند که ممکن است حاوی ستاره‌های "wildcard" و علامت سوال باشند. ستاره ( * ) با هر رشته‌ای با هر طولی، از جمله رشته خالی، مطابقت دارد، در حالی که علامت سوال ( ? ) با هر کاراکتر واحدی مطابقت دارد.

برای مثال، 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 تزریق نمی‌کند:

مانیفست.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 تزریق نمی‌کند:

مانیفست.json

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

برای دستیابی به محدوده صحیح، می‌توان یکی، همه یا برخی از این موارد را در نظر گرفت.

مانیفست.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 مراجعه کنید.

مانیفست.json

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

سرویس-ورکر.js

browser.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 مشخص‌شده مطابقت دارند تزریق شوند یا فقط به بالاترین فریم در یک تب:

مانیفست.json

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

هنگام ثبت اسکریپت‌های محتوا از طریق برنامه‌نویسی با استفاده از browser.scripting.registerContentScripts(...) ، می‌توان از پارامتر allFrames برای تعیین اینکه آیا اسکریپت محتوا باید به تمام فریم‌هایی که با الزامات URL مشخص شده مطابقت دارند تزریق شود یا فقط به بالاترین فریم در یک تب، استفاده کرد. این فقط با tabId قابل استفاده است و در صورت مشخص بودن frameIds یا documentIds قابل استفاده نیست:

سرویس-ورکر.js

browser.scripting.registerContentScripts([{
  id: "test",
  matches : [ "https://*.nytimes.com/*" ],
  allFrames : true,
  js : [ "contentScript.js" ],
}]);

افزونه‌ها ممکن است بخواهند اسکریپت‌هایی را در فریم‌هایی اجرا کنند که به یک فریم منطبق مرتبط هستند، اما خودشان با آن فریم مطابقت ندارند. یک سناریوی رایج در این مورد، فریم‌هایی با 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" را در مشخصات اسکریپت محتوا در مانیفست مشخص کنند.

مانیفست.json

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

وقتی مشخص شده و روی true تنظیم شده باشد، کروم برای تعیین اینکه آیا فریم مطابقت دارد یا خیر، به جای URL خود فریم، به مبدأ آغازگر فریم نگاه می‌کند. توجه داشته باشید که این ممکن است با مبدأ فریم هدف نیز متفاوت باشد (مثلاً data: URLها مبدأ تهی دارند).

آغازگر فریم، فریمی است که فریم هدف را ایجاد یا پیمایش کرده است. اگرچه این معمولاً والد مستقیم یا آغازگر است، اما ممکن است اینطور نباشد (مانند موردی که یک فریم، یک iframe را درون یک iframe دیگر پیمایش می‌کند).

از آنجا که این مقایسه، مبدأ فریم آغازگر را نشان می‌دهد، فریم آغازگر می‌تواند در هر مسیری از آن مبدأ روشن باشد. برای روشن شدن این مفهوم، کروم از هر اسکریپت محتوایی که با "match_origin_as_fallback" روی true تنظیم شده باشد، می‌خواهد که مسیر * را نیز مشخص کند.

وقتی هر دو "match_origin_as_fallback" و "match_about_blank" مشخص شده باشند، "match_origin_as_fallback" اولویت پیدا می‌کند.

ارتباط با صفحه جاسازی

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

یک مثال را می‌توان با استفاده از window.postMessage() انجام داد:

اسکریپت محتوا.js

var port = browser.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);

مثال.js

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

صفحه غیر افزونه، example.html، پیام‌هایی را برای خودش ارسال می‌کند. این پیام توسط اسکریپت محتوا رهگیری و بررسی شده و سپس به فرآیند افزونه ارسال می‌شود. به این ترتیب، صفحه یک خط ارتباطی با فرآیند افزونه ایجاد می‌کند. عکس این عمل نیز از طریق روش‌های مشابه امکان‌پذیر است.

دسترسی به فایل‌های افزونه

برای دسترسی به یک فایل افزونه از طریق اسکریپت محتوا، می‌توانید تابع browser.runtime.getURL() را فراخوانی کنید تا URL مطلق فایل افزونه خود را همانطور که در مثال زیر نشان داده شده است ( content.js ) دریافت کنید:

اسکریپت محتوا.js

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

برای استفاده از فونت‌ها یا تصاویر در یک فایل CSS، می‌توانید @@extension_id برای ساخت یک URL مانند مثال زیر ( content.css ) استفاده کنید:

محتوا.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 تعریف شوند:

مانیفست.json

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

سیاست امنیت محتوا

اسکریپت‌های محتوایی که در جهان‌های ایزوله اجرا می‌شوند، سیاست امنیتی محتوای (CSP) زیر را دارند:

script-src 'self' 'wasm-unsafe-eval' 'inline-speculation-rules' chrome-extension://abcdefghijklmopqrstuvwxyz/; object-src 'self';

مشابه محدودیت‌های اعمال‌شده بر سایر زمینه‌های افزونه، این مورد از استفاده از eval() و همچنین بارگیری اسکریپت‌های خارجی جلوگیری می‌کند.

برای افزونه‌های بدون بسته‌بندی، CSP شامل localhost نیز می‌شود:

script-src 'self' 'wasm-unsafe-eval' 'inline-speculation-rules' http://localhost:* http://127.0.0.1:* chrome-extension://abcdefghijklmopqrstuvwxyz/; object-src 'self';

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

ایمن بمانید

در حالی که جهان‌های ایزوله یک لایه محافظتی ایجاد می‌کنند، استفاده از اسکریپت‌های محتوا می‌تواند باعث ایجاد آسیب‌پذیری در یک افزونه و صفحه وب شود. اگر اسکریپت محتوا، محتوا را از یک وب‌سایت جداگانه دریافت می‌کند، مثلاً با فراخوانی fetch() ، مراقب باشید که قبل از تزریق، محتوا را در برابر حملات اسکریپت‌نویسی بین‌سایتی فیلتر کنید. برای جلوگیری از حملات "مرد میانی"، فقط از طریق HTTPS ارتباط برقرار کنید.

حتماً صفحات وب مخرب را فیلتر کنید. برای مثال، الگوهای زیر خطرناک هستند و در Manifest V3 مجاز نیستند:

نکن

اسکریپت محتوا.js

const data = document.getElementById("json-data");
// WARNING! Might be evaluating an evil script!
const parsed = eval("(" + data + ")");
نکن

اسکریپت محتوا.js

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

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

انجام دهید

اسکریپت محتوا.js

const data = document.getElementById("json-data")
// JSON.parse does not evaluate the attacker's scripts.
const parsed = JSON.parse(data);
انجام دهید

اسکریپت محتوا.js

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

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

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

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

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

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

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

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

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

صفحه وب.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>

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

اسکریپت محتوا.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 یا هر دو باشند. همه اسکریپت‌های محتوای خودکار باید الگوهای منطبق را مشخص کنند.

مانیفست.json

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

نام نوع توضیحات
matches آرایه‌ای از رشته‌ها الزامی. مشخص می‌کند که این اسکریپت محتوا به کدام صفحات تزریق خواهد شد. برای جزئیات بیشتر در مورد نحو این رشته‌ها به Match Patterns و برای اطلاعات در مورد نحوه حذف URLها به Match patterns and globs مراجعه کنید.
css آرایه‌ای از رشته‌ها اختیاری. فهرست فایل‌های CSS که باید به صفحات منطبق تزریق شوند. این فایل‌ها به ترتیبی که در این آرایه ظاهر می‌شوند، قبل از ساخت یا نمایش هرگونه DOM برای صفحه، تزریق می‌شوند.
js آرایه‌ای از رشته‌ها اختیاری. فهرست فایل‌های جاوا اسکریپتی که باید به صفحات منطبق تزریق شوند. فایل‌ها به ترتیبی که در این آرایه ظاهر می‌شوند تزریق می‌شوند. هر رشته در این فهرست باید حاوی یک مسیر نسبی به منبعی در دایرکتوری ریشه افزونه باشد. اسلش‌های (`/`) در ابتدای هر رشته به طور خودکار حذف می‌شوند.
run_at اجرا کن اختیاری. مشخص می‌کند که اسکریپت چه زمانی باید به صفحه تزریق شود. مقدار پیش‌فرض document_idle است.
match_about_blank بولی اختیاری. اینکه آیا اسکریپت باید در یک قاب about:blank تزریق شود که در آن قاب والد یا قاب آغازین با یکی از الگوهای اعلام شده در matches مطابقت دارد. پیش‌فرض روی false است.
match_origin_as_fallback بولی اختیاری. اینکه آیا اسکریپت باید در فریم‌هایی که توسط یک مبدا منطبق ایجاد شده‌اند، اما URL یا مبدا آنها ممکن است مستقیماً با الگو مطابقت نداشته باشد، تزریق شود یا خیر. این فریم‌ها شامل فریم‌هایی با طرح‌های مختلف، مانند about: data: blob: و filesystem: می‌شوند. همچنین به تزریق در فریم‌های مرتبط مراجعه کنید.
world دنیای اعدام Optional. The JavaScript world for a script to execute within. Defaults to ISOLATED . See also Work in isolated worlds .

Within a given stage of the document lifecycle, content scripts declared statically in the manifest are the first to be injected, before content scripts registered in any other way. They are injected in the order in which they are specified in the manifest.

Inject with dynamic declarations

Dynamic content scripts are useful when the match patterns for content scripts are not well known or when content scripts shouldn't always be injected on known hosts.

Introduced in Chrome 96, dynamic declarations are similar to static declarations , but the content script object is registered with Chrome using methods in the browser.scripting namespace rather than in manifest.json . The Scripting API also allows extension developers to:

Like static declarations, dynamic declarations can include JavaScript files, CSS files, or both.

service-worker.js

browser.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

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

service-worker.js

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

service-worker.js

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

Inject programmatically

Use programmatic injection for content scripts that need to run in response to events or on specific occasions.

To inject a content script programmatically, your extension needs host permissions for the page it's trying to inject scripts into. Host permissions can either be granted by requesting them as part of your extension's manifest or temporarily using "activeTab" .

The following are different versions of an activeTab-based extension.

manifest.json:

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

Content scripts can be injected as files.

content-script.js


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

service-worker.js:

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

Or, a function body can be injected and executed as a content script.

service-worker.js:

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

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

Be aware that the injected function is a copy of the function referenced in the browser.scripting.executeScript() call, not the original function itself. As a result, the function's body must be self contained; references to variables outside of the function will cause the content script to throw a ReferenceError .

When injecting as a function, you can also pass arguments to the function.

service-worker.js

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

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

Exclude matches and globs

To customize specified page matching, include the following fields in a declarative registration.

نام نوع توضیحات
exclude_matches آرایه‌ای از رشته‌ها Optional. Excludes pages that this content script would otherwise be injected into. See Match Patterns for details of the syntax of these strings.
include_globs آرایه‌ای از رشته‌ها Optional. Applied after matches to include only those URLs that also match this glob. This is intended to emulate the @include Greasemonkey keyword.
exclude_globs array of string Optional. Applied after matches to exclude URLs that match this glob. Intended to emulate the @exclude Greasemonkey keyword.

The content script will be injected into a page if both of the following are true:

  • Its URL matches any matches pattern and any include_globs pattern.
  • The URL doesn't also match an exclude_matches or exclude_globs pattern. Because the matches property is required, exclude_matches , include_globs , and exclude_globs can only be used to limit which pages will be affected.

The following extension injects the content script into https://www.nytimes.com/health but not into https://www.nytimes.com/business .

مانیفست.json

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

service-worker.js

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

Glob properties follow a different, more flexible syntax than match patterns . Acceptable glob strings are URLs that may contain "wildcard" asterisks and question marks. The asterisk ( * ) matches any string of any length, including the empty string, while the question mark ( ? ) matches any single character.

For example, the glob https://???.example.com/foo/\* matches any of the following:

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

However, it does not match the following:

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

This extension injects the content script into https://www.nytimes.com/arts/index.html and https://www.nytimes.com/jobs/index.htm* , but not into https://www.nytimes.com/sports/index.html :

مانیفست.json

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

This extension injects the content script into https://history.nytimes.com and https://.nytimes.com/history , but not into https://science.nytimes.com or https://www.nytimes.com/science :

مانیفست.json

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

One, all, or some of these can be included to achieve the correct scope.

مانیفست.json

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

زمان اجرا

The run_at field controls when JavaScript files are injected into the web page. The preferred and default value is "document_idle" . See the RunAt type for other possible values.

مانیفست.json

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

service-worker.js

browser.scripting.registerContentScripts([{
  id : "test",
  matches : [ "https://*.nytimes.com/*" ],
  runAt : "document_idle",
  js : [ "contentScript.js" ],
}]);
نام نوع توضیحات
document_idle رشته Preferred. Use "document_idle" whenever possible.

The browser chooses a time to inject scripts between "document_end" and immediately after the window.onload event fires. The exact moment of injection depends on how complex the document is and how long it is taking to load, and is optimized for page load speed.

Content scripts running at "document_idle" don't need to listen for the window.onload event, they are guaranteed to run after the DOM is complete. If a script definitely needs to run after window.onload , the extension can check if onload has already fired by using the document.readyState property.
document_start رشته Scripts are injected after any files from css , but before any other DOM is constructed or any other script is run.
document_end رشته Scripts are injected immediately after the DOM is complete, but before subresources like images and frames have loaded.

Specify frames

For declarative content scripts specified in the manifest, the "all_frames" field allows the extension to specify if JavaScript and CSS files should be injected into all frames matching the specified URL requirements or only into the topmost frame in a tab:

مانیفست.json

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

When programmatically registering content scripts using browser.scripting.registerContentScripts(...) , the allFrames parameter can be used to specify if the content script should be injected into all frames matching the specified URL requirements or only into the topmost frame in a tab. This can only be used with tabId, and cannot be used if frameIds or documentIds are specified:

service-worker.js

browser.scripting.registerContentScripts([{
  id: "test",
  matches : [ "https://*.nytimes.com/*" ],
  allFrames : true,
  js : [ "contentScript.js" ],
}]);

Extensions may want to run scripts in frames that are related to a matching frame, but don't themselves match. A common scenario when this is the case is for frames with URLs that were created by a matching frame, but whose URLs don't themselves match the script's specified patterns.

This is the case when an extension wants to inject in frames with URLs that have about: , data: , blob: , and filesystem: schemes. In these cases, the URL won't match the content script's pattern (and, in the case of about: and data: , don't even include the parent URL or origin in the URL at all, as in about:blank or data:text/html,<html>Hello, World!</html> ). However, these frames can still be associated with the creating frame.

To inject into these frames, extensions can specify the "match_origin_as_fallback" property on a content script specification in the manifest.

مانیفست.json

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

When specified and set to true , Chrome will look at the origin of the initiator of the frame to determine whether the frame matches, rather than at the URL of the frame itself. Note that this might also be different than the target frame's origin (eg, data: URLs have a null origin).

The initiator of the frame is the frame that created or navigated the target frame. While this is commonly the direct parent or opener, it may not be (as in the case of a frame navigating an iframe within an iframe).

Because this compares the origin of the initiator frame, the initiator frame could be on at any path from that origin. To make this implication clear, Chrome requires any content scripts specified with "match_origin_as_fallback" set to true to also specify a path of * .

When both "match_origin_as_fallback" and "match_about_blank" are specified, "match_origin_as_fallback" takes priority.

Communication with the embedding page

Although the execution environments of content scripts and the pages that host them are isolated from each other, they share access to the page's DOM. If the page wishes to communicate with the content script, or with the extension through the content script, it must do so through the shared DOM.

An example can be accomplished using window.postMessage() :

content-script.js

var port = browser.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);

مثال.js

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

The non-extension page, example.html, posts messages to itself. This message is intercepted and inspected by the content script and then posted to the extension process. In this way, the page establishes a line of communication to the extension process. The reverse is possible through similar means.

Access extension files

To access an extension file from a content script, you can call browser.runtime.getURL() to get the absolute URL of your extension asset as shown in the following example ( content.js ):

content-script.js

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

To use fonts or images in a CSS file, you can use @@extension_id to construct a URL as shown in the following example ( 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');
}

All assets must be declared as web accessible resources in the manifest.json file:

مانیفست.json

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

سیاست امنیت محتوا

Content scripts running in isolated worlds have the following Content Security Policy (CSP):

script-src 'self' 'wasm-unsafe-eval' 'inline-speculation-rules' chrome-extension://abcdefghijklmopqrstuvwxyz/; object-src 'self';

Similar to the restrictions applied to other extension contexts, this prevents the use of eval() as well as loading external scripts.

For unpacked extensions, the CSP also includes localhost:

script-src 'self' 'wasm-unsafe-eval' 'inline-speculation-rules' http://localhost:* http://127.0.0.1:* chrome-extension://abcdefghijklmopqrstuvwxyz/; object-src 'self';

When a content script is injected into the main world, the CSP of the page applies.

Stay secure

While isolated worlds provide a layer of protection, using content scripts can create vulnerabilities in an extension and the web page. If the content script receives content from a separate website, such as by calling fetch() , be careful to filter content against cross-site scripting attacks before injecting it. Only communicate over HTTPS in order to avoid "man-in-the-middle" attacks.

Be sure to filter for malicious web pages. For example, the following patterns are dangerous, and disallowed in 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);

Instead, prefer safer APIs that don't run scripts:

انجام دهید

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