סקריפטים של תוכן הם קבצים שרצים בהקשר של דפי אינטרנט. באמצעות המסמך הרגיל Object Model (DOM), הוא יכול לקרוא פרטים על דפי האינטרנט שאליהם הדפדפן מבקר, ישתנה בהם, ויעביר את המידע לתוסף ההורה שלהם.
הבנת היכולות של סקריפטים של תוכן
סקריפטים של תוכן יכולים לגשת ישירות לממשקי ה-API הבאים של תוספים:
dom
i18n
storage
runtime.connect()
runtime.getManifest()
runtime.getURL()
runtime.id
runtime.onConnect
runtime.onMessage
runtime.sendMessage()
לסקריפטים של תוכן אין גישה ישירה לממשקי API אחרים. אבל הם יכולים לגשת אליהם באופן עקיף על ידי העברת הודעות עם חלקים אחרים של התוסף.
אפשר גם לגשת לקבצים אחרים בתוסף מסקריפט תוכן, באמצעות
ממשקי API כמו fetch()
. לשם כך, עליכם להצהיר עליהן בתור:
משאבים נגישים לאינטרנט. שימו לב שהפעולה הזו גם חושפת את המשאבים
סקריפטים של צד ראשון או של צד שלישי שפועלים באותו אתר.
עבודה בעולמות מבודדים
סקריפטים של תוכן חיים בעולם מבודד, כך שהסקריפט של התוכן יכול לשנות אותו סביבת 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 |
בוליאני | אופציונלי. אם הסקריפט צריך להחדיר אותו למסגרת about:blank
שבהם מסגרת ההורה או מסגרת הפותח תואמת לאחד מהדפוסים המוצהרים
matches . ברירת המחדל היא False. |
match_origin_as_fallback |
בוליאני |
אופציונלי. אם הסקריפט צריך להחדיר אותו במסגרות
נוצר על ידי מקור תואם, אבל כתובת ה-URL או המקור שלו לא בהכרח קיימים
תואמים לדפוס. אלה כוללות מסגרות עם סכמות שונות, כמו
about: , data: , blob: וגם
filesystem: עוד באותו הקשר
החדרה במסגרות קשורות.
|
world |
ExecutionWorld |
אופציונלי. העולם של JavaScript שבו סקריפט יופעל. ברירת המחדל היא ISOLATED . עוד באותו הקשר
עבודה בעולמות מבודדים.
|
החדרה באמצעות הצהרות דינמיות
סקריפטים של תוכן דינמי שימושיים כאשר דפוסי ההתאמה של סקריפטים של תוכן הם או כאשר לא תמיד צריך להחדיר סקריפטים של תוכן למארחים מוכרים.
הושקה ב-Chrome 96, הצהרות דינמיות דומות להצהרות סטטיות
אבל אובייקט סקריפט התוכן רשום ב-Chrome באמצעות
שיטות במרחב השמות chrome.scripting
במקום
manifest.json. גם מפתחי תוספים יכולים להשתמש ב-Scripting API
to:
- רישום סקריפטים של תוכן.
- לקבלת רשימה של סקריפטים של תוכן רשום.
- מעדכנים את רשימת הסקריפטים הרשומים של התוכן.
- הסרה של סקריפטים של תוכן רשום.
בדומה להצהרות סטטיות, הצהרות דינמיות יכולות לכלול קובצי 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"
באופן זמני.
אלה גרסאות שונות של תוסף מבוסס-כרטיסייה פעיל.
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
מילת מפתח של Graeasemonkey. |
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 מקובל
מחרוזות הן כתובות 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 צריכים להיות.
מוכנס לכל המסגרות שתואמות לדרישות כתובת האתר שצוינו או רק למסגרת העליונה
.
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 שלהן לא מתאימים לתבניות ספציפיות של הסקריפט.
מצב כזה קורה כשתוסף רוצה להחדיר במסגרות עם כתובות 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 של המסגרת עצמה. לתשומת ליבכם: המזהה הזה עשוי להיות גם שונה
origin של מסגרת היעד (למשל, ל-data:
כתובות URL יש מקור null).
יצרן הפריים הוא המסגרת שיצרה את המטרה או ניווט בה מסגרת. אומנם בדרך כלל מדובר בהורה או בפתיחה ישירה, אבל לא בהכרח (כמו דוגמה למסגרת שמנווטת ב-iframe בתוך iframe).
מכיוון שאפשר לבצע השוואה בין המקור של מסגרת ההפעלה, מסגרת ההפעלה משווה
יכול להיות בכל נתיב מהמקור הזה. כדי להבהיר את המשמעות הזו, Chrome
מחייב את כל סקריפטים של תוכן שצוינו עם "match_origin_as_fallback"
מוגדר ל-true
כדי לציין גם נתיב של *
.
אם מציינים גם "match_origin_as_fallback"
וגם "match_about_blank"
,
"match_origin_as_fallback"
מקבל עדיפות.
התקשורת עם דף ההטמעה
למרות שסביבות ההפעלה של סקריפטים של תוכן ושל הדפים שמארחים אותם מבודדות הן חולקים גישה ל-DOM של הדף. אם הדף רוצה לתקשר עם או עם התוסף דרך סקריפט התוכן, עליו לעשות זאת דרך ה-DOM המשותף.
אפשר לקבל דוגמה באמצעות הפקודה window.postMessage()
:
content-script.js
var port = chrome.runtime.connect();
window.addEventListener("message", (event) => {
// We only accept messages from ourselves
if (event.source !== window) {
return;
}
if (event.data.type && (event.data.type === "FROM_PAGE")) {
console.log("Content script received: " + event.data.text);
port.postMessage(event.data.text);
}
}, false);
example.js
document.getElementById("theButton").addEventListener("click", () => {
window.postMessage(
{type : "FROM_PAGE", text : "Hello from the webpage!"}, "*");
}, false);
הדף ללא התוספים, example.html, מפרסם הודעות לעצמו. ההודעה הזו נלכדת של הסרטון נבדק על ידי סקריפט התוכן, ולאחר מכן פורסם בתהליך התוסף. באופן הזה, יוצרת קשרים עם תהליך ההארכה. אפשר לעשות את זה דרך פירושים דומים.
גישה לקובצי תוספים
כדי לגשת לקובץ תוסף מסקריפט תוכן, אפשר להפעיל את הפקודה
chrome.runtime.getURL()
כדי לקבל את כתובת ה-URL המוחלטת של נכס התוסף כפי שמוצג בדוגמה הבאה (content.js
):
content-script.js
let image = chrome.runtime.getURL("images/my_image.png")
כדי להשתמש בגופנים או בתמונות בקובץ CSS, אפשר להשתמש ב-@@extension_id
כדי ליצור כתובת URL כפי שמוצג בדוגמה הבאה (content.css
):
content.css
body {
background-image:url('chrome-extension://__MSG_@@extension_id__/background.png');
}
@font-face {
font-family: 'Stint Ultra Expanded';
font-style: normal;
font-weight: 400;
src: url('chrome-extension://__MSG_@@extension_id__/fonts/Stint Ultra Expanded.woff') format('woff');
}
צריך להצהיר על כל הנכסים כמשאבים נגישים לאינטרנט בקובץ manifest.json
:
manifest.json
{
...
"web_accessible_resources": [
{
"resources": [ "images/*.png" ],
"matches": [ "https://example.com/*" ]
},
{
"resources": [ "fonts/*.woff" ],
"matches": [ "https://example.com/*" ]
}
],
...
}
אבטחת החשבון
עולמות מבודדים מספקים שכבת הגנה, אבל שימוש בסקריפטים של תוכן יכול ליצור
נקודות חולשה בתוסף ובדף האינטרנט. אם סקריפט התוכן מקבל תוכן
אתרים נפרדים, למשל על ידי קריאה ל-fetch()
, הקפידו לסנן תוכן לפי
מתקפה של סקריפטים חוצי-אתרים לפני החדרה. תתקשר רק באמצעות HTTPS כדי
להימנע מהתקפות "man-in-the-middle".
הקפידו לסנן לפי דפי אינטרנט זדוניים. לדוגמה, הדפוסים הבאים הם מסוכנים, וגם אסורה במניפסט מגרסה 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);