סקריפטים של תוכן

סקריפטים של תוכן הם קבצים שפועלים בהקשר של דפי אינטרנט. באמצעות Document Object Model (DOM) הסטנדרטי, הם יכולים לקרוא פרטים של דפי האינטרנט שהדפדפן מבקר בהם, לבצע בהם שינויים ולהעביר מידע לתוסף ההורה שלהם.

הסבר על היכולות של סקריפטים של תוכן

סקריפטים של תוכן יכולים לגשת לממשקי ה-API של Chrome שבהם משתמש תוסף ההורה שלהם על ידי החלפת הודעות עם התוסף. הם יכולים גם לגשת לכתובת ה-URL של קובץ של תוסף באמצעות chrome.runtime.getURL() ולהשתמש בתוצאה כמו בכתובות URL אחרות.

// Code for displaying EXTENSION_DIR/images/myimage.png:
var imgURL = chrome.runtime.getURL("images/myimage.png");
document.getElementById("someImage").src = imgURL;

בנוסף, ל-content script יש גישה ישירה לממשקי ה-API הבאים של Chrome:

סקריפטים של תוכן לא יכולים לגשת ישירות לממשקי API אחרים.

עבודה בעולמות מבודדים

סקריפטים של תוכן נמצאים בעולם מבודד, שמאפשר להם לבצע שינויים בסביבת ה-JavaScript שלהם בלי להתנגש בדף או בסקריפטים נוספים של תוכן.

תוסף יכול לפעול בדף אינטרנט עם קוד שדומה לדוגמה הבאה.

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

התוסף הזה יכול להחדיר את סקריפט התוכן הבא.

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

אם הלחצן ילחץ, יופיעו שתי ההתראות.

עולמות מבודדים לא מאפשרים לסקריפטים של תוכן, לתוסף ולדף האינטרנט לגשת למשתנים או לפונקציות שנוצרו על ידי האחרים. כך אפשר גם להפעיל בסקריפטים של תוכן פונקציונליות שלא אמורה להיות נגישה לדף האינטרנט.

הזרקת סקריפטים

אפשר להחדיר סקריפטים של תוכן באופן פרוגרמטי או בדקלרציה.

הוספה באופן פרוגרמטי

כדאי להשתמש בהזרקה פרוגרמטית לסקריפטים של תוכן שצריכים לפעול באירועים ספציפיים.

כדי להחדיר סקריפט תוכן פרוגרמטי, צריך לציין את ההרשאה activeTab במניפסט. כך ניתנת גישה מאובטחת למארח האתר הפעיל וגישה זמנית להרשאה tabs, שמאפשרת לסקריפט התוכן לפעול בכרטיסייה הפעילה הנוכחית בלי לציין הרשאות מקור שונות.

{
  "name": "My extension",
  ...
  "permissions": [
    "activeTab"
  ],
  ...
}

אפשר להחדיר סקריפטים של תוכן כקוד.

chrome.runtime.onMessage.addListener(
  function(message, callback) {
    if (message == "changeColor"){
      chrome.tabs.executeScript({
        code: 'document.body.style.backgroundColor="orange"'
      });
    }
  });

אפשר גם להחדיר קובץ שלם.

chrome.runtime.onMessage.addListener(
  function(message, callback) {
    if (message == "runContentScript"){
      chrome.tabs.executeScript({
        file: 'contentScript.js'
      });
    }
  });

הזרקה באופן דקלרטיבי

משתמשים בהזרקה מוצהרת לסקריפטים של תוכן שצריך להריץ באופן אוטומטי בדפים מסוימים.

סקריפטים שהוזרקו באופן דקלרטיבי רשומים במניפסט בשדה "content_scripts". הם יכולים לכלול קובצי JavaScript, קובצי CSS או גם וגם. כל סקריפט התוכן שפועל באופן אוטומטי חייב לציין תבניות התאמה.

{
 "name": "My extension",
 ...
 "content_scripts": [
   {
     "matches": ["http://*.nytimes.com/*"],
     "css": ["myStyles.css"],
     "js": ["contentScript.js"]
   }
 ],
 ...
}
שם סוג תיאור
matches {: #matches } מערך מחרוזות חובה. קובע אילו דפים יוטמע בהם סקריפט התוכן הזה. מידע נוסף על התחביר של המחרוזות האלה זמין במאמר תבניות התאמה, ומידע על החרגת כתובות URL זמין במאמר תבניות התאמה ו-globs.
css {: #css } מערך מחרוזות אופציונלי. רשימת קובצי ה-CSS שרוצים להחדיר לדפים התואמים. הקוד הזה מוזרק לפי הסדר שבו הוא מופיע במערך הזה, לפני ש-DOM כלשהו נוצר או מוצג בדף.
js {: #js } מערך מחרוזות אופציונלי. רשימת קובצי ה-JavaScript שיוזנו בדפים התואמים. הם מוחדרים לפי הסדר שבו הם מופיעים במערך הזה.
match_about_blank {: #match_about_blank } בוליאני אופציונלי. האם הסקריפט צריך להחדיר לפריים about:blank שבו הפריים של ההורה או הפריים הפותח תואם לאחד מהדפוסים שהוגדרו ב-matches. ברירת המחדל היא false.

החרגת התאמות ו-globs

כדי להתאים אישית את ההתאמה של דפים ספציפיים, צריך לכלול את השדות הבאים ברישום המניפסט.

שם סוג תיאור
exclude_matches {: #exclude_matches } מערך מחרוזות אופציונלי. החרגה של דפים שבהם סקריפט התוכן הזה היה מוחדר אחרת. פרטים נוספים על התחביר של המחרוזות האלה מופיעים בקטע תבניות התאמה.
include_globs {: #include_globs } מערך מחרוזות אופציונלי. הקוד הזה מופעל אחרי matches כדי לכלול רק כתובות URL שתואמות גם לביטוי ה-glob הזה. מיועדת לחקות את מילת המפתח @include ב-Greasemonkey.
exclude_globs {: #exclude_globs } array of string אופציונלי. הקוד הזה מופעל אחרי matches כדי להחריג כתובות URL שתואמות לביטוי ה-glob הזה. מיועדת לחקות את מילת המפתח @exclude ב-Greasemonkey.

סקריפט התוכן יוזרק לדף אם כתובת ה-URL שלו תואמת לדפוס matches ולדפוס include_globs כלשהו, כל עוד כתובת ה-URL לא תואמת גם לדפוס exclude_matches או לדפוס exclude_globs.

מאחר שמאפיין matches נדרש, אפשר להשתמש ב-exclude_matches, ב-include_globs וב-exclude_globs רק כדי להגביל את הדפים שיושפעו.

התוסף הבא יזין את סקריפט התוכן בדף http://www.nytimes.com/ health, אבל לא בדף http://www.nytimes.com/ business .

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

למאפייני Glob יש תחביר שונה וגמיש יותר מזה של דפוסי התאמה. מחרוזות glob מקובלות הן כתובות URL שעשויות להכיל כוכבית ותווים כלליים לחיפוש (wildcard) וסימני שאלה. הכוכבית * תואמת לכל מחרוזת בכל אורך, כולל מחרוזת ריקה, בעוד שסימן השאלה ? תואם לכל תו יחיד.

לדוגמה, ביטוי ה-glob http:// ??? .example.com/foo/ * תואם לכל אחד מהכתובות הבאות:

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

עם זאת, הוא לא תואם לקריטריונים הבאים:

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

התוסף הזה יזין את סקריפט התוכן לכתובות http:/www.nytimes.com/ arts /index.html ו-http://www.nytimes.com/ jobs /index.html, אבל לא לכתובת http://www.nytimes.com/ sports /index.html.

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

התוסף הזה יזין את סקריפט התוכן בכתובות http:// history .nytimes.com ו-http://.nytimes.com/ history, אבל לא בכתובות http:// science .nytimes.com או http://www.nytimes.com/ science .

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

אפשר לכלול אחד מהם, את כולם או חלק מהם כדי להגיע להיקף הנכון.

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

זמן ריצה

השדה run_at קובע מתי קובצי JavaScript מוחדרים לדף האינטרנט. השדה המועדף והשדה שמוגדר כברירת מחדל הוא "document_idle", אבל אפשר לציין גם את הערך "document_start" או "document_end" אם צריך.

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["http://*.nytimes.com/*"],
      "run_at": "document_idle",
      "js": ["contentScript.js"]
    }
  ],
  ...
}
שם סוג תיאור
document_idle {: #document_idle } מחרוזת מועדף. מומלץ להשתמש ב-"document_idle" בכל הזדמנות אפשרית.

הדפדפן בוחר מתי להחדיר סקריפטים בין "document_end" לבין זמן קצר לאחר הפעלת האירוע windowonload. הרגע המדויק של ההזרקה תלוי ברמת המורכבות של המסמך ובמשך הזמן שלוקח לו להיטען, והוא מותאם לצורך אופטימיזציה של מהירות הטעינה של הדף.

סקריפטים של תוכן שפועלים ב-"document_idle" לא צריכים להאזין לאירוע window.onload, הם מובטחים לפעול אחרי שה-DOM יושלם. אם סקריפט צריך לפעול אחרי window.onload, התוסף יכול לבדוק אם האירוע onload כבר הופעל באמצעות הנכס document.readyState.
document_start {: #document_start } מחרוזת הזרקת הסקריפטים מתבצעת אחרי כל הקבצים מ-css, אבל לפני יצירת DOM אחר או הרצת סקריפט אחר.
document_end {: #document_end } מחרוזת הסקריפטים מוחדרים מיד אחרי שה-DOM הושלם, אבל לפני שמשאבים משניים כמו תמונות ופריימים נטענים.

ציון פריימים

השדה "all_frames" מאפשר להגדיר אם קובצי JavaScript ו-CSS צריכים להוזן לכל המסגרות שתואמות לדרישות של כתובת ה-URL שצוינה, או רק למסגרת העליונה בכרטיסייה.

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["http://*.nytimes.com/*"],
      "all_frames": true,
      "js": ["contentScript.js"]
    }
  ],
  ...
}
שם סוג תיאור
all_frames {: #all_frames } בוליאני אופציונלי. ברירת המחדל היא false, כלומר רק הפריים העליון יתאים.

אם יצוין true, הקוד יוזרק לכל הפריימים, גם אם הפריים הוא לא הפריים העליון בכרטיסייה. כל פריים נבדק בנפרד כדי לוודא שהוא עומד בדרישות לגבי כתובות URL. הוא לא יוזרק לפריימים צאצאים אם הוא לא עומד בדרישות לגבי כתובות URL.

תקשורת עם דף ההטמעה

סביבות הביצוע של סקריפטים של תוכן והדפים שמארחים אותם מבודדות זו מזו, אבל יש להן גישה משותפת ל-DOM של הדף. אם הדף רוצה לתקשר עם סקריפט התוכן, או עם התוסף דרך סקריפט התוכן, הוא צריך לעשות זאת דרך ה-DOM המשותף.

אפשר לעשות זאת באמצעות window.postMessage:

var port = chrome.runtime.connect();

window.addEventListener("message", function(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);
document.getElementById("theButton").addEventListener("click",
    function() {
  window.postMessage({ type: "FROM_PAGE", text: "Hello from the webpage!" }, "*");
}, false);

הדף ללא התוסף, example.html, מפרסם הודעות לעצמו. סקריפט התוכן מיירט את ההודעה הזו ובודק אותה, ולאחר מכן מפרסם אותה בתהליך התוסף. כך הדף יוצר קו תקשורת לתהליך ההרחבה. אפשר לבצע את הפעולה ההפוכה בדרכים דומות.

אבטחת החשבון

עולמות מבודדים מספקים שכבת הגנה, אבל שימוש בסקריפטים של תוכן עלול ליצור נקודות חולשה בתוסף ובדף האינטרנט. אם סקריפט התוכן מקבל תוכן מאתר נפרד, למשל באמצעות XMLHttpRequest, חשוב לסנן את התוכן לפני ההזרקה כדי למנוע התקפות של סקריפטים באתרים שונים. צריך לתקשר רק דרך HTTPS כדי להימנע מהתקפות "man-in-the-middle".

חשוב לסנן דפי אינטרנט זדוניים. לדוגמה, הדפוסים הבאים מסוכנים:

var data = document.getElementById("json-data")
// WARNING! Might be evaluating an evil script!
var parsed = eval("(" + data + ")")
var elmt_id = ...
// WARNING! elmt_id might be "); ... evil script ... //"!
window.setTimeout("animate(" + elmt_id + ")", 200);

במקום זאת, כדאי להשתמש בממשקי API בטוחים יותר שלא מריצים סקריפטים:

var data = document.getElementById("json-data")
// JSON.parse does not evaluate the attacker's scripts.
var parsed = JSON.parse(data);
var elmt_id = ...
// The closure form of setTimeout does not evaluate scripts.
window.setTimeout(function() {
  animate(elmt_id);
}, 200);