העברת ההודעה

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

התקשורת בין תוספים וסקריפטים של התוכן שלהם פועלת באמצעות העברת הודעות. כל צד יוכל להאזין להודעות שנשלחו מהצד השני ולהגיב באותו ערוץ. הודעה יכולה להכיל כל אובייקט JSON חוקי (null, בוליאני, מספר, מחרוזת, מערך או אובייקט). יש API פשוט לבקשות חד-פעמיות ו-API מורכב יותר שמאפשר ליצור חיבורים לטווח ארוך להחלפת מספר הודעות בהקשר משותף. אפשר גם לשלוח הודעה לתוסף אחר אם המזהה שלו ידוע לכם, בקטע הודעות בין תוספים.

בקשות חד-פעמיות פשוטות

אם רוצים לשלוח הודעה אחת רק לחלק אחר בתוסף (ואפשר גם לקבל תשובה בחזרה), אפשר להשתמש בגרסאות הפשוטות runtime.sendMessage או tabs.sendMessage . כך תוכלו לשלוח הודעה חד-פעמית של קובץ JSON עם מספר סידורי של JSON, מסקריפט תוכן אל תוסף, או להיפך, בהתאמה . אפשר להשתמש בפרמטר אופציונלי של קריאה חוזרת כדי לטפל בתגובה מהצד השני, אם יש כזה.

שליחת בקשה מסקריפט תוכן נראית כך:

chrome.runtime.sendMessage({greeting: "hello"}, function(response) {
  console.log(response.farewell);
});

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

chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
  chrome.tabs.sendMessage(tabs[0].id, {greeting: "hello"}, function(response) {
    console.log(response.farewell);
  });
});

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

chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    console.log(sender.tab ?
                "from a content script:" + sender.tab.url :
                "from the extension");
    if (request.greeting == "hello")
      sendResponse({farewell: "goodbye"});
  }
);

בדוגמה שלמעלה, הפונקציה sendResponse נקראה באופן סינכרוני. כדי להשתמש ב-sendResponse באופן אסינכרוני, צריך להוסיף את return true; ל-handler של האירועים של onMessage.

הערה: אם מספר דפים מאזינים לאירועי onMessage, רק הראשונים שמפעילים את sendResponse() לאירוע מסוים יצליחו לשלוח את התגובה. המערכת תתעלם מכל שאר התגובות לאירוע הזה.
הערה: ההתקשרות החוזרת של sendResponse תקפה רק אם נעשה בה שימוש סינכרוני, או אם הגורם המטפל באירועים מחזיר true כדי לציין שהוא יגיב באופן אסינכרוני. הקריאה החוזרת (callback) של הפונקציה sendMessage תופעל באופן אוטומטי אם אף handler לא יחזיר את הערך true או אם הקריאה החוזרת (callback) של sendResponse נאסף באשפה.

חיבורים לטווח ארוך

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

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

כשיוצרים חיבור, כל קצה מקבל אובייקט runtime.Port שמשמש לשליחה ולקבלה של הודעות באמצעות אותו החיבור.

כך תוכלו לפתוח ערוץ מתוך סקריפט תוכן, ולשלוח הודעות ולהאזין להן:

var port = chrome.runtime.connect({name: "knockknock"});
port.postMessage({joke: "Knock knock"});
port.onMessage.addListener(function(msg) {
  if (msg.question == "Who's there?")
    port.postMessage({answer: "Madame"});
  else if (msg.question == "Madame who?")
    port.postMessage({answer: "Madame... Bovary"});
});

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

כדי לטפל בחיבורים נכנסים, צריך להגדיר האזנה לאירועים מסוג runtime.onConnect. המראה הזה זהה בסקריפט של תוכן או בדף של תוסף. כשחלק אחר של התוסף קורא ל-"connect()", האירוע הזה מופעל, יחד עם האובייקט runtime.Port שבו אפשר להשתמש כדי לשלוח ולקבל הודעות דרך החיבור. כך נראה להגיב לחיבורים נכנסים:

chrome.runtime.onConnect.addListener(function(port) {
  console.assert(port.name == "knockknock");
  port.onMessage.addListener(function(msg) {
    if (msg.joke == "Knock knock")
      port.postMessage({question: "Who's there?"});
    else if (msg.answer == "Madame")
      port.postMessage({question: "Madame who?"});
    else if (msg.answer == "Madame... Bovary")
      port.postMessage({question: "I don't get it."});
  });
});

משך החיים של יציאה

היציאות מתוכננות כשיטת תקשורת דו-כיוונית בין חלקים שונים של התוסף, כאשר מסגרת (ברמה העליונה) היא החלק הקטן ביותר. קוראים ל-tabs.connect, ל-runtime.connect או ל-runtime.connectNative, תיווצר יציאה. אפשר להשתמש ביציאה הזו מיד כדי לשלוח הודעות לצד השני באמצעות postMessage.

אם בכרטיסייה יש כמה פריימים, ההפעלה של tabs.connect תוביל למספר הפעלות של האירוע runtime.onConnect (פעם אחת לכל פריים בכרטיסייה). באופן דומה, אם משתמשים ב-runtime.connect, ייתכן שהאירוע onConnect יופעל מספר פעמים (פעם אחת לכל פריים בתהליך התוסף).

כדאי לבדוק מתי החיבור נסגר, למשל אם אתם שומרים מצב נפרד לכל יציאה פתוחה. כדי לעשות זאת, אפשר להאזין לאירוע runtime.Port.onDisconnect. האירוע הזה מופעל כשאין יציאות תקינות בצד השני של הערוץ. זה יכול לקרות במצבים הבאים:

  • אין מאזינים ל-runtime.onConnect בצד השני שלך.
  • הכרטיסייה שמכילה את היציאה בוטלה (למשל, אם מתבצע ניווט בכרטיסייה).
  • הוסרה טעינת המסגרת שממנה הופעל connect.
  • הוסרו כל הפריימים שקיבלו את היציאה (דרך runtime.onConnect).
  • מתבצעת קריאה ל-runtime.Port.disconnect באמצעות הקצה השני. הערה: אם קריאה ל-connect מסתיימת בכמה יציאות בצד של הנמען ומתבצעת קריאה ל-disconnect() בכל אחת מהיציאות האלה, האירוע onDisconnect מופעל רק ביציאה של השולח, ולא ביציאות האחרות.

העברת הודעות בין תוספים שונים

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

ההאזנה לבקשות ולחיבורים נכנסים דומה למקרה הפנימי, אלא שמשתמשים בשיטות runtime.onMessageExternal או runtime.onConnectExternal. הנה דוגמה לכל אחד מהם:

// For simple requests:
chrome.runtime.onMessageExternal.addListener(
  function(request, sender, sendResponse) {
    if (sender.id == blocklistedExtension)
      return;  // don't allow this extension access
    else if (request.getTargetData)
      sendResponse({targetData: targetData});
    else if (request.activateLasers) {
      var success = activateLasers();
      sendResponse({activateLasers: success});
    }
  });

// For long-lived connections:
chrome.runtime.onConnectExternal.addListener(function(port) {
  port.onMessage.addListener(function(msg) {
    // See other examples for sample onMessage handlers.
  });
});

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

// The ID of the extension we want to talk to.
var laserExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";

// Make a simple request:
chrome.runtime.sendMessage(laserExtensionId, {getTargetData: true},
  function(response) {
    if (targetInRange(response.targetData))
      chrome.runtime.sendMessage(laserExtensionId, {activateLasers: true});
  }
);

// Start a long-running conversation:
var port = chrome.runtime.connect(laserExtensionId);
port.postMessage(...);

שליחת הודעות מדפי אינטרנט

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

"externally_connectable": {
  "matches": ["*://*.example.com/*"]
}

פעולה זו תחשוף את ה-API להעברת הודעות לכל דף שתואם לתבניות ה-URL שציינתם. דפוס כתובת ה-URL חייב להכיל לפחות דומיין ברמה השנייה. כלומר, אסור להשתמש בתבניות של שם מארח כמו "*", "*.com" , "*.co.uk" ו-"*.appspot.com". בדף האינטרנט, משתמשים בממשקי ה-API של runtime.sendMessage או runtime.connect כדי לשלוח הודעה לאפליקציה או לתוסף ספציפיים. לדוגמה:

// The ID of the extension we want to talk to.
var editorExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";

// Make a simple request:
chrome.runtime.sendMessage(editorExtensionId, {openUrlInEditor: url},
  function(response) {
    if (!response.success)
      handleError(url);
  });

אפשר להאזין להודעות מדפי אינטרנט מהאפליקציה או לתוסף באמצעות ממשקי ה-API runtime.onMessageExternal או runtime.onConnectExternal, בדומה להודעות בין תוספים שונים. רק דף האינטרנט יכול ליזום חיבור. לדוגמה:

chrome.runtime.onMessageExternal.addListener(
  function(request, sender, sendResponse) {
    if (sender.url == blocklistedWebsite)
      return;  // don't allow this web page access
    if (request.openUrlInEditor)
      openUrl(request.openUrlInEditor);
  });

העברת הודעות באפליקציות מקוריות

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

שיקולי אבטחה

סקריפטים של תוכן פחות מהימנים

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

סקריפטים חוצי-אתרים

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

chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
  // WARNING! Might be evaluating an evil script!
  var resp = eval("(" + response.farewell + ")");
});
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
  // WARNING! Might be injecting a malicious script!
  document.getElementById("resp").innerHTML = response.farewell;
});

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

chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
  // JSON.parse does not evaluate the attacker's scripts.
  var resp = JSON.parse(response.farewell);
});
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
  // innerText does not let the attacker inject HTML elements.
  document.getElementById("resp").innerText = response.farewell;
});

דוגמאות

תוכלו למצוא דוגמאות פשוטות לתקשורת באמצעות הודעות בספרייה examples/api/messaging. דוגמה להעברת הודעות מותאמות היא דוגמה שממחישה איך אפליקציה של Chrome יכולה לתקשר עם אפליקציות מותאמות. בקטע דוגמאות תוכלו למצוא דוגמאות נוספות ועזרה בהצגת קוד המקור.