סקריפטים של תוכן הם קבצים שרצים בהקשר של דפי אינטרנט. באמצעות מודל אובייקטים של מסמכים הסטנדרטי (DOM), הם יכולים לקרוא פרטים על דפי האינטרנט שבהם הדפדפן מבקר, לבצע בהם שינויים ולהעביר מידע לתוסף ההורה.
הבנת היכולות של סקריפט תוכן
סקריפטים של תוכן יכולים לגשת לקובצי תוספים לאחר שמצהירים עליהם כמשאבים נגישים לאינטרנט. הם יכולים לגשת ישירות לממשקי ה-API של התוספים הבאים:
dom
i18n
storage
runtime.connect()
runtime.getManifest()
runtime.getURL()
runtime.id
runtime.onConnect
runtime.onMessage
runtime.sendMessage()
סקריפטים של תוכן לא יכולים לגשת ישירות לממשקי API אחרים. אבל הם יכולים לגשת אליהם באופן עקיף על ידי החלפת הודעות עם חלקים אחרים של התוסף.
עבודה בעולמות מבודדים
סקריפטים של תוכן נמצאים בעולם מבודד, ומאפשרים לסקריפט תוכן לבצע שינויים בסביבת ה-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);
בעקבות שינוי זה, שתי ההתראות מופיעות ברצף כאשר לוחצים על הלחצן.
החדרת סקריפטים
אפשר להצהיר על סקריפטים של תוכן באופן סטטי, להצהיר על באופן דינמי או להחדר באופן פרוגרמטי.
החדרה עם הצהרות סטטיות
צריך להשתמש בהצהרות של סקריפט תוכן סטטי ב-מניפסט.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 . ברירת המחדל היא FALSE. |
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-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"
באופן זמני.
בהמשך מופיעות גרסאות שונות של תוסף מבוסס-Tab פעיל.
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 כדי לכלול רק את כתובות ה-URL שתואמות
גם לדומיין הזה. הטקסט נועד לחקות את מילת המפתח @include
Garemonkey. |
exclude_globs |
מערך של מחרוזת | אופציונלי. הוחל אחרי matches כדי להחריג כתובות URL שתואמות
לכדור הארץ הזה. נועד לחקות את מילת המפתח @exclude
Graeasemonkey. |
סקריפט התוכן יוחדר לדף אם שני התנאים הבאים מתקיימים:
- כתובת ה-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-worker.js
chrome.scripting.registerContentScripts([{
id : "test",
matches : [ "https://*.nytimes.com/*" ],
excludeMatches : [ "*://*/*business*" ],
js : [ "contentScript.js" ],
}]);
תחביר של מאפייני Glob יהיה שונה וגמיש יותר מתבניות התאמה. מחרוזות glob מקובלות הן כתובות URL שעשויות לכלול כוכביות וסימני שאלה עם 'תו כללי לחיפוש'. הכוכבית (*
) תואמת לכל מחרוזת בכל אורך, כולל המחרוזת הריקה, ואילו סימן השאלה (?
) תואם לכל תו בודד.
לדוגמה, כדור הארץ 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-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 , התוסף יכול לבדוק אם 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-worker.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).
מאחר שהפעולה הזו משווה בין המקור של מסגרת היוזמת, המסגרת של המפעיל יכולה להיות בכל נתיב מהמקור הזה. כדי שהמשמעות הזו תהיה ברורה, כל הסקריפטים של התוכן שצוינו ב-"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".
הקפד לסנן לפי דפי אינטרנט זדוניים. לדוגמה, הדפוסים הבאים מסוכנים, ואסור להשתמש בהם במניפסט 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);