מסגרת מבוקרת

Demián Renzulli
Demián Renzulli
Simon Hangl
Simon Hangl

בדרך כלל משתמשים ברכיב <iframe> כדי להטמיע משאבים חיצוניים בהקשר של גלישה. רכיבי iframe אוכפים את מדיניות האבטחה של האינטרנט על ידי בידוד תוכן מוטמע ממקורות שונים מדף המארח, ולהפך. הגישה הזו משפרת את האבטחה כי היא יוצרת גבול מאובטח בין מקורות, אבל היא מגבילה חלק מתרחישי השימוש. לדוגמה, יכול להיות שמשתמשים יצטרכו לטעון ולנהל תוכן באופן דינמי ממקורות שונים – כמו מורה שמפעיל אירוע ניווט כדי להציג דף אינטרנט במסך בכיתה. עם זאת, אתרים רבים חוסמים במפורש הטמעה ב-iframe באמצעות כותרות אבטחה כמו X-Frame-Options ו-Content Security Policy (‏CSP). בנוסף, הגבלות על iframe מונעות הטמעה של דפים מניהול ישיר של הניווט או ההתנהגות של התוכן המוטמע.

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

הטמעה של Controlled Frames

לפני שמשתמשים ב-Controlled Frame, צריך להגדיר IWA תקין. לאחר מכן תוכלו לשלב את Controlled Frames בדפים שלכם.

הוספת מדיניות הרשאות

כדי להשתמש ב-Controlled Frames, צריך להוסיף שדה permissions_policy עם הערך "controlled-frame" למניפסט של אפליקציית האינטרנט המבודדת (IWA) כדי להפעיל את ההרשאה המתאימה. בנוסף, צריך לכלול את המפתח cross-origin-isolated. המפתח הזה לא ספציפי ל-Controlled Frames, אבל הוא נדרש לכל אפליקציות האינטרנט המבודדות (IWA) וקובע אם המסמך יכול לגשת לממשקי API שדורשים בידוד בין מקורות שונים.

{
   ...
  "permissions_policy": {
     ...
     "controlled-frame": ["self"],
     "cross-origin-isolated": ["self"]
     ...
  }
   ...
}

המפתח controlled-frame במניפסט של אפליקציית אינטרנט מבודדת (IWA) מגדיר רשימת היתרים של מדיניות הרשאות, שמציינת אילו מקורות יכולים להשתמש ב-Controlled Frames. למרות שהמניפסט תומך בתחביר המלא של מדיניות ההרשאות – ומאפשר ערכים כמו *, מקורות ספציפיים או מילות מפתח כמו self ו-src – חשוב לציין שלא ניתן להעביר ממשקי API ספציפיים של IWA למקורות אחרים. גם אם רשימת ההיתרים כוללת תו כללי או מקורות חיצוניים, ההרשאות האלה לא יחולו על תכונות של IWA כמו controlled-frame. בניגוד לאפליקציות אינטרנט רגילות, באפליקציות IWA כל התכונות שמבוקרות על ידי מדיניות מוגדרות כברירת מחדל לערך none, ולכן נדרשות הצהרות מפורשות. במקרה של תכונות ספציפיות ל-IWA, המשמעות היא שרק ערכים כמו self (המקור של ה-IWA עצמו) או src (המקור של מסגרת מוטמעת) הם יעילים מבחינה פונקציונלית.

הוספת רכיב של מסגרת מבוקרת

מוסיפים רכיב <controlledframe> ל-HTML כדי להטמיע תוכן של צד שלישי ב-IWA.

<controlledframe id="controlledframe_1" src="https://example.com">
</controlledframe>

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

דוגמה: מחיצת אחסון בזיכרון

יוצרים מסגרת מבוקרת באמצעות מחיצת אחסון בזיכרון בשם "session1". הנתונים שמאוחסנים במחיצה הזו (לדוגמה, קובצי Cookie ו-localStorage) יימחקו כשהפריים ייהרס או כשהסשן של האפליקציה יסתיים.

<controlledframe id="controlledframe_1" src="https://example.com">
</controlledframe>

דוגמה: מחיצה של אחסון מתמיד

יוצרים מסגרת מבוקרת באמצעות מחיצת אחסון קבוע בשם "user_data". הקידומת "persist:" מבטיחה שהנתונים שמאוחסנים במחיצה הזו יישמרו בדיסק ויהיו זמינים בסשנים שונים של האפליקציה.

<controlledframe id="frame_2" src="..." partition="persist:user_data">
</controlledframe>

קבלת הפניה לרכיב

מקבלים הפניה לרכיב <controlledframe> כדי שתוכלו ליצור איתו אינטראקציה כמו עם כל רכיב HTML רגיל:

const controlledframe = document.getElementById('controlledframe_1');

תרחישים נפוצים לדוגמה

ככלל, כדאי לבחור את הטכנולוגיה הטובה ביותר שתענה על הצרכים שלכם, בלי להסתבך עם מורכבות מיותרת. בשנים האחרונות, אפליקציות Progressive Web App ‏(PWA) צמצמו את הפער בינן לבין אפליקציות מקוריות, וכך אפשרו חוויית שימוש עוצמתית באינטרנט. אם אפליקציית אינטרנט צריכה להטמיע תוכן של צד שלישי, מומלץ קודם לנסות את הגישה הרגילה של <iframe>. אם הדרישות חורגות מהיכולות של iframe, יכול להיות ש-Controlled Frames ב-IWA יהיה הפתרון הטוב ביותר. בקטעים הבאים מתוארים תרחישים נפוצים לדוגמה.

הטמעה של תוכן אינטרנט מצד שלישי

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

תרחישים לדוגמה

  • מצגות ב-Classroom: מורה משתמש במסך מגע בכיתה כדי לעבור בין משאבים חינוכיים שבדרך כלל חוסמים הטמעה של iframe.
  • שילוט דיגיטלי בחנויות קמעונאיות או בקניונים: קיוסק בקניון מציג לסירוגין אתרים של חנויות שונות. המסגרות המבוקרות מבטיחות שהדפים האלה ייטענו בצורה תקינה גם אם הם מגבילים את ההטמעה.

דוגמאות קוד

ממשקי ה-API הבאים של Controlled Frame יכולים לעזור לכם לנהל תוכן מוטמע.

ניווט: מסגרות מבוקרות מספקות כמה שיטות לניהול ולשליטה בניווט ובהיסטוריית הניווט של התוכן המוטמע באופן פרוגרמטי.

המאפיין src מקבל או מגדיר את כתובת ה-URL של התוכן שמוצג במסגרת, והוא פועל באותו אופן כמו מאפיין ה-HTML.

controlledframe.src = "https://example.com";

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

document.getElementById('backBtn').addEventListener('click', () => {
controlledframe.back().then((success) => {
console.log(`Back navigation ${success ? 'succeeded' : 'failed'}`); }).catch((error) => {
   console.error('Error during back navigation:', error);
   });
});

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

document.getElementById('forwardBtn').addEventListener('click', () => {
controlledframe.forward().then((success) => {
   console.log(`Forward navigation ${success ? 'succeeded' : 'failed'}`);
}).catch((error) => {
    console.error('Error during forward navigation:', error);
  });
});

השיטה reload() טוענת מחדש את הדף הנוכחי במסגרת.

document.getElementById('reloadBtn').addEventListener('click', () => {
   controlledframe.reload();
});

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

  • loadstart: מופעל כשמתחיל ניווט בפריים.
  • loadcommit: מופעל כשבקשת הניווט עברה עיבוד ותוכן המסמך הראשי מתחיל להיטען.
  • contentload: מופעל כשהטעינה של המסמך הראשי והמשאבים החיוניים שלו מסתיימת (בדומה ל-DOMContentLoaded).
  • loadstop: מופעל כשכל המשאבים של הדף (כולל מסגרות משנה, תמונות) סיימו להיטען.
  • loadabort: מופעל אם הניווט מבוטל (לדוגמה, על ידי פעולת משתמש או התחלה של ניווט אחר).
  • loadredirect: מופעלת כשמתרחשת הפניה לכתובת אחרת בצד השרת במהלך הניווט.
controlledframe.addEventListener('loadstart', (event) => {
   console.log('Navigation started:', event.url);
   // Example: Show loading indicator
 });
controlledframe.addEventListener('loadcommit', (event) => {
   console.log('Navigation committed:', event.url);
 });
controlledframe.addEventListener('contentload', (event) => {
   console.log('Content loaded for:', controlledframe.src);
   // Example: Hide loading indicator, maybe run initial script
 });
controlledframe.addEventListener('loadstop', (event) => {
   console.log('All resources loaded for:', controlledframe.src);
 });
controlledframe.addEventListener('loadabort', (event) => {
   console.warn(`Navigation aborted: ${event.url}, Reason: ${event.detail.reason}`);
 });
controlledframe.addEventListener('loadredirect', (event) => {
   console.log(`Redirect detected: ${event.oldUrl} -> ${event.newUrl}`);
});

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

  • dialog: מופעל כשתוכן מוטמע מנסה לפתוח תיבת דו-שיח (התראה, אישור, הנחיה). אתם מקבלים פרטים ויכולים להשיב.
  • consolemessage: מופעל כשמתבצעת רישום של הודעה במסוף בתוך המסגרת.
  • permissionrequest: מופעל כשתוכן מוטמע מבקש הרשאה (לדוגמה, מיקום גיאוגרפי והתראות). אתם מקבלים פרטים ויכולים לאשר או לדחות את הבקשה.
  • newwindow: מופעל כשתוכן מוטמע מנסה לפתוח חלון או כרטיסייה חדשים (לדוגמה, באמצעות window.open או קישור עם target="_blank"). אתם מקבלים פרטים ויכולים לטפל בפעולה או לחסום אותה.
controlledframe.addEventListener('dialog', (event) => {
   console.log(Dialog opened: Type=${event.messageType}, Message=${event.messageText});
   // You will need to respond, e.g., event.dialog.ok() or .cancel()
 });

controlledframe.addEventListener('consolemessage', (event) => {
   console.log(Frame Console [${event.level}]: ${event.message});
 });

controlledframe.addEventListener('permissionrequest', (event) => {
   console.log(Permission requested: Type=${event.permission});
   // You must respond, e.g., event.request.allow() or .deny()
   console.warn('Permission request needs handling - Denying by default');
   if (event.request && event.request.deny) {
     event.request.deny();
   }
});

controlledframe.addEventListener('newwindow', (event) => {
   console.log(New window requested: URL=${event.targetUrl}, Name=${event.name});
   // Decide how to handle this, e.g., open in a new controlled frame and call event.window.attach(), ignore, or block
   console.warn('New window request needs handling - Blocking by default');
 });

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

  • sizechanged: מופעל כשמידות התוכן של המסגרת משתנות.
  • zoomchange: מופעל כשמשנים את רמת הזום של תוכן המסגרת.
controlledframe.addEventListener('sizechanged', (event) => {
  console.log(Frame size changed: Width=${event.width}, Height=${event.height});
});

controlledframe.addEventListener('zoomchange', (event) => {
  console.log(Frame zoom changed: Factor=${event.newZoomFactor});
});

שיטות אחסון: Controlled Frames מציע ממשקי API לניהול נתונים שמאוחסנים במחיצה של פריים.

אפשר להשתמש ב-clearData() כדי להסיר את כל הנתונים המאוחסנים. זה שימושי במיוחד לאיפוס המסגרת אחרי סשן של משתמש או כדי לוודא שהמצב נקי. השיטה מחזירה הבטחה (Promise) שמושלמת כשהפעולה מסתיימת. אפשר גם לספק אפשרויות הגדרה אופציונליות:

  • types: מערך של מחרוזות שמציין אילו סוגי נתונים לנקות (לדוגמה, ['cookies', 'localStorage', 'indexedDB']). אם לא מציינים, בדרך כלל מנקים את כל סוגי הנתונים הרלוונטיים.
  • options: שליטה בתהליך הניקוי, למשל ציון טווח זמן באמצעות המאפיין since (חותמת זמן באלפיות השנייה מאז ראשית זמן יוניקס) כדי לנקות רק נתונים שנוצרו אחרי הזמן הזה.

דוגמה: מחיקת כל האחסון שמשויך למסגרת המבוקרת

function clearAllPartitionData() {
   console.log('Clearing all data for partition:', controlledframe.partition);
   controlledframe.clearData()
     .then(() => {
       console.log('Partition data cleared successfully.');
     })
     .catch((error) => {
       console.error('Error clearing partition data:', error);
     });
}

דוגמה: מחיקה רק של קובצי Cookie ונתונים ב-localStorage שנוצרו בשעה האחרונה

function clearRecentCookiesAndStorage() {
   const oneHourAgo = Date.now() - (60 * 60 * 1000);
   const dataTypesArray = ['cookies', 'localStorage'];
   const dataTypesToClearObject = {};
   for (const type of dataTypesArray) {
      dataTypesToClearObject[type] = true;
   }
   const clearOptions = { since: oneHourAgo };
   console.log(`Clearing ${dataTypesArray.join(', ')} since ${new    Date(oneHourAgo).toISOString()}`); controlledframe.clearData(clearOptions, dataTypesToClearObject) .then(() => {
   console.log('Specified partition data cleared successfully.');
}).catch((error) => {
   console.error('Error clearing specified partition data:', error);
});
}

הרחבה או שינוי של אפליקציות צד שלישי

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

תרחישים לדוגמה

  • החלת מיתוג באתרים של צד שלישי: הוספת CSS ו-JavaScript מותאמים אישית לאתרים מוטמעים כדי להחיל ערכת נושא ויזואלית אחידה.
  • הגבלת הניווט והתנהגות הקישורים: חסימה או השבתה של התנהגויות מסוימות של תגי <a> באמצעות הזרקת סקריפט.
  • אוטומציה של שחזור אחרי קריסות או חוסר פעילות: מעקב אחרי תוכן מוטמע כדי לזהות מצבי כשל (לדוגמה, מסך ריק, שגיאות בסקריפט) וטעינה מחדש או איפוס של הסשן באופן אוטומטי אחרי פסק זמן.

דוגמאות קוד

הזרקת סקריפט: אפשר להשתמש ב-executeScript() כדי להזריק JavaScript למסגרת המבוקרת, וכך להתאים אישית את ההתנהגות, להוסיף שכבות-על או לחלץ נתונים מדפי צד שלישי מוטמעים. אפשר לספק קוד מוטבע כמחרוזת או להפנות לקובץ סקריפט אחד או יותר (באמצעות נתיבים יחסיים בחבילת ה-IWA). השיטה מחזירה הבטחה שמובילה לתוצאה של ביצוע הסקריפט – בדרך כלל הערך של ההצהרה האחרונה.

document.getElementById('scriptBtn').addEventListener('click', () => {
   controlledframe.executeScript({
      code: `document.body.style.backgroundColor = 'lightblue';
             document.querySelectorAll('a').forEach(link =>    link.style.pointerEvents = 'none');
             document.title; // Return a value
            `,
      // You can also inject files:
      // files: ['./injected_script.js'],
}) .then((result) => {
   // The result of the last statement in the script is usually returned.
   console.log('Script execution successful. Result (e.g., page title):', result); }).catch((error) => {
   console.error('Script execution failed:', error);
   });
});

החדרת סגנון: אפשר להשתמש ב-insertCSS() כדי להחיל סגנונות מותאמים אישית על דפים שנטענו בתוך Controlled Frame.

document.getElementById('cssBtn').addEventListener('click', () => {
  controlledframe.insertCSS({
    code: `body { font-family: monospace; }`
    // You can also inject files:
    // files: ['./injected_styles.css']
  })
  .then(() => {
    console.log('CSS injection successful.');
  })
  .catch((error) => {
    console.error('CSS injection failed:', error);
  });
});

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

// Get the request object
const webRequest = controlledframe.request;

// Create an interceptor for a specific URL pattern
const interceptor = webRequest.createWebRequestInterceptor({
  urlPatterns: ["*://evil.com/*"],
  blocking: true,
  includeHeaders: "all"
});

// Add a listener to block the request
interceptor.addEventListener("beforerequest", (event) => {
  console.log('Blocking request to:', event.url);
  event.preventDefault();
});

// Add a listener to modify request headers
interceptor.addEventListener("beforesendheaders", (event) => {
  console.log('Modifying headers for:', event.url);
  const newHeaders = new Headers(event.headers);
  newHeaders.append('X-Custom-Header', 'MyValue');
  event.setRequestHeaders(newHeaders);
});

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

const menuItemProperties = {
  id: "copy-selection",
  title: "Copy selection",
  contexts: ["selection"],
  documentURLPatterns: [new URLPattern({ hostname: '*.example.com'})]
};

// Create the context menu item using a promise
try {
  await controlledframe.contextMenus.create(menuItemProperties);
  console.log(`Context menu item "${menuItemProperties.id}" created successfully.`);
} catch (error) {
  console.error(`Failed to create context menu item:`, error);
}

// Add a standard event listener for the 'click' event
controlledframe.contextMenus.addEventListener('click', (event) => {
    if (event.menuItemId === "copy-selection" && event.selectionText) {
        navigator.clipboard.writeText(event.selectionText)
          .then(() => console.log("Text copied to clipboard."))
          .catch(err => console.error("Failed to copy text:", err));
    }
});

הדגמה (דמו)

בסרטון ההדגמה של Controlled Frame אפשר לראות סקירה כללית של השיטות של Controlled Frames.

הדגמה (דמו) של Controlled Frame

לחלופין, באפליקציה IWA Kitchen Sink יש כמה כרטיסיות, וכל אחת מהן מדגימה IWA API אחר, כמו Controlled Frames,‏ Direct Sockets ועוד.

IWA Kitchen Sink

סיכום

‫Controlled Frames מספקות דרך יעילה ומאובטחת להטמיע תוכן אינטרנט של צד שלישי באפליקציות אינטרנט מבודדות (IWA), להרחיב אותו ולבצע בו פעולות. הם מאפשרים יכולות חדשות כמו הפעלת סקריפטים בתוך תוכן מוטמע, יירוט בקשות רשת והטמעה של תפריטי הקשר בהתאמה אישית – והכול תוך שמירה על גבולות בידוד מחמירים. עם זאת, מכיוון שממשקי ה-API האלה מאפשרים שליטה מעמיקה בתוכן מוטמע, הם מגיעים גם עם מגבלות אבטחה נוספות וזמינים רק ב-IWA, שנועדו לאכוף ערבויות חזקות יותר למשתמשים ולמפתחים. ברוב תרחישי השימוש, מומלץ למפתחים להשתמש קודם ברכיבי <iframe> רגילים, שהם פשוטים יותר ומספיקים בהרבה מקרים. צריך להעריך את השימוש ב-Controlled Frames כשפתרונות מבוססי iframe נחסמים בגלל הגבלות הטמעה או כשחסרות להם יכולות השליטה והאינטראקציה הנדרשות. בין אם אתם יוצרים חוויית שימוש בקיוסקים, משלבים כלים של צד שלישי או מתכננים מערכות מודולריות של תוספים, Controlled Frames מאפשרים לכם שליטה מדויקת בסביבה מובנית, מאובטחת ועם הרשאות – מה שהופך אותם לכלי חשוב בדור הבא של אפליקציות אינטרנט מתקדמות.

מקורות מידע נוספים