قاب کنترل شده

دمیان رنزولی
Demián Renzulli
سایمون هانگل
Simon Hangl

عنصر <iframe> معمولاً برای جاسازی منابع خارجی در یک زمینه مرور استفاده می شود. Iframes با جداسازی محتوای جاسازی شده با منبع متقابل از صفحه میزبان و بالعکس، خط‌مشی‌های امنیتی وب را اجرا می‌کند. در حالی که این رویکرد با تضمین یک مرز امن بین مبدا، امنیت را افزایش می دهد، برخی موارد استفاده را محدود می کند. برای مثال، کاربران ممکن است نیاز به بارگیری و مدیریت پویا محتوا از منابع مختلف داشته باشند - مانند معلمی که یک رویداد ناوبری را برای نمایش یک صفحه وب در صفحه کلاس درس راه اندازی می کند. با این حال، بسیاری از وب‌سایت‌ها صراحتاً با استفاده از سرصفحه‌های امنیتی مانند X-Frame-Options و Content Security Policy (CSP) تعبیه‌شده در iframe را مسدود می‌کنند. علاوه بر این، محدودیت‌های iframe از مدیریت مستقیم پیمایش یا رفتار محتوای تعبیه‌شده توسط صفحات جاسازی شده جلوگیری می‌کند.

Controlled Frame API این محدودیت را با اجازه بارگیری هر محتوای وب برطرف می‌کند، حتی اگر سیاست‌های جاسازی محدودی را اعمال کند. این API به طور انحصاری در برنامه های کاربردی وب ایزوله (IWA) موجود است که اقدامات امنیتی بیشتری را برای محافظت از کاربران و توسعه دهندگان در برابر خطرات احتمالی در بر می گیرد.

فریم های کنترل شده را پیاده سازی کنید

قبل از استفاده از یک قاب کنترل شده، باید یک IWA کاربردی راه اندازی کنید . سپس می توانید فریم های کنترل شده را در صفحات خود ادغام کنید.

سیاست مجوز را اضافه کنید

برای استفاده از Frames کنترل شده، مجوز مربوطه را با افزودن یک فیلد permissions_policy با مقدار "controlled-frame" به مانیفست IWA خود فعال کنید. علاوه بر این، از جمله کلید متقاطع ایزوله . این کلید مخصوص فریم‌های کنترل‌شده نیست، اما برای همه IWAها لازم است و تعیین می‌کند که آیا سند می‌تواند به APIهایی که نیاز به جداسازی متقاطع دارند دسترسی داشته باشد یا خیر.

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

کلید controlled-frame در مانیفست برنامه وب ایزوله (IWA) یک لیست مجاز خط مشی مجوزها را تعریف می‌کند و مشخص می‌کند کدام مبدا می‌تواند از فریم‌های کنترل‌شده استفاده کند. در حالی که مانیفست از دستور کامل Permissions Policy پشتیبانی می‌کند - اجازه می‌دهد مقادیری مانند * ، مبداهای خاص، یا کلمات کلیدی مانند self و src را بدهد، مهم است که توجه داشته باشید که APIهای خاص IWA را نمی‌توان به مبداهای دیگر واگذار کرد. حتی اگر فهرست مجاز شامل یک علامت عام یا مبدا خارجی باشد، این مجوزها برای ویژگی‌های IWA مانند controlled-frame اعمال نمی‌شوند. برخلاف برنامه‌های وب استاندارد، IWAها به‌طور پیش‌فرض تمام ویژگی‌های کنترل‌شده توسط سیاست‌ها را به هیچ‌کدام نمی‌دهند و نیاز به اعلامیه‌های صریح دارند. برای ویژگی‌های خاص IWA، این بدان معناست که فقط مقادیری مانند self (منشا خود IWA) یا src (منشا یک قاب تعبیه‌شده) از نظر عملکردی مؤثر هستند.

یک عنصر Frame Controlled اضافه کنید

یک عنصر <controlledframe> را در HTML خود وارد کنید تا محتوای شخص ثالث را در IWA خود جاسازی کنید.

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

ویژگی partition اختیاری پارتیشن بندی ذخیره سازی را برای محتوای جاسازی شده پیکربندی می کند و به شما امکان می دهد داده هایی مانند کوکی ها و ذخیره سازی محلی را برای ماندگاری داده ها در هر جلسه ایزوله کنید.

مثال: پارتیشن ذخیره سازی در حافظه

یک قاب کنترل شده با استفاده از یک پارتیشن ذخیره سازی در حافظه به نام "session1" ایجاد کنید. داده‌های ذخیره شده در این پارتیشن (به عنوان مثال، کوکی‌ها و محل ذخیره‌سازی محلی) هنگامی که فریم از بین می‌رود یا جلسه برنامه به پایان می‌رسد، پاک می‌شوند.

<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');

سناریوهای مکرر و موارد استفاده

به عنوان یک قاعده کلی، بهترین فناوری را انتخاب کنید تا نیازهای خود را برآورده کند و در عین حال از پیچیدگی های غیر ضروری اجتناب کنید. در سال‌های اخیر، برنامه‌های وب پیشرو (PWA) شکاف را با برنامه‌های بومی کاهش داده‌اند و تجربه‌های قدرتمند وب را ممکن می‌سازند. اگر یک برنامه وب نیاز به جاسازی محتوای شخص ثالث دارد، توصیه می شود ابتدا رویکرد معمول <iframe> را بررسی کنید. اگر الزامات بیش از توانایی های iframe باشد، فریم های کنترل شده در IWA ممکن است بهترین جایگزین باشد. موارد استفاده متداول در بخش های زیر توضیح داده شده است.

جاسازی محتوای وب شخص ثالث

بسیاری از برنامه ها به توانایی بارگذاری و نمایش محتوای شخص ثالث در رابط کاربری خود نیاز دارند. با این حال، هنگامی که چندین مالک برنامه وب درگیر هستند - یک سناریوی رایج با برنامه‌های تعبیه‌شده - ایجاد خط‌مشی‌های سرتاسر ثابت دشوار می‌شود. به عنوان مثال، تنظیمات امنیتی می‌توانند از تعبیه انواع خاصی از محتوا توسط <iframe> سنتی جلوگیری کنند، حتی زمانی که کسب‌وکارها نیاز قانونی به این کار دارند. برخلاف عناصر <iframe> ، فریم‌های کنترل‌شده برای دور زدن این محدودیت‌ها طراحی شده‌اند و به برنامه‌ها اجازه بارگیری و نمایش محتوا را می‌دهند، حتی اگر صراحتاً تعبیه استاندارد را ممنوع کند.

موارد استفاده کنید

  • ارائه‌های کلاسی : معلم از صفحه لمسی کلاس درس برای جابه‌جایی بین منابع آموزشی استفاده می‌کند که معمولاً جاسازی iframe را مسدود می‌کند.
  • تابلوهای دیجیتال در خرده فروشی یا مراکز خرید : یک کیوسک مرکز خرید از طریق وب سایت های فروشگاه های مختلف می چرخد. فریم های کنترل شده تضمین می کنند که این صفحات به درستی بارگیری می شوند حتی اگر جاسازی را محدود کنند.

نمونه کد

API های Controlled Frame زیر برای مدیریت محتوای جاسازی شده مفید هستند.

ناوبری : فریم های کنترل شده روش های متعددی را برای مدیریت و کنترل برنامه نویسی ناوبری و تاریخچه پیمایش محتوای تعبیه شده ارائه می دهند.

مشخصه src نشانی اینترنتی محتوای نمایش داده شده در قاب را می گیرد یا تنظیم می کند و مانند ویژگی 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();
});

به‌علاوه، «قاب‌های کنترل‌شده» رویدادهایی را ارائه می‌کنند که به شما امکان می‌دهند چرخه عمر کامل درخواست‌های پیمایش را دنبال کنید - از شروع و تغییر مسیر تا بارگیری محتوا، تکمیل یا سقط.

  • 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});
});

روش‌های ذخیره‌سازی : فریم‌های کنترل‌شده APIهایی را برای مدیریت داده‌های ذخیره‌شده در پارتیشن فریم ارائه می‌دهند.

از clearData() برای حذف تمام داده های ذخیره شده استفاده کنید، که به ویژه برای تنظیم مجدد قاب پس از یک جلسه کاربر یا اطمینان از وضعیت تمیز مفید است. این روش یک Promise را برمی‌گرداند که پس از اتمام عملیات برطرف می‌شود. گزینه های پیکربندی اختیاری نیز می تواند ارائه شود:

  • types : آرایه‌ای از رشته‌ها که مشخص می‌کند کدام نوع داده باید پاک شود (برای مثال، ['cookies', 'localStorage', 'indexedDB'] ). اگر حذف شود، همه انواع داده های قابل اجرا معمولاً پاک می شوند.
  • options : فرآیند پاکسازی را کنترل کنید، مانند تعیین محدوده زمانی با استفاده از ویژگی since (مهر زمانی بر حسب میلی‌ثانیه از دوره) تا فقط داده‌های ایجاد شده پس از آن زمان را پاک کنید.

مثال: تمام فضای ذخیره سازی مرتبط با Controlled Frame را پاک کنید

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);
     });
}

مثال: فقط کوکی‌ها و محل ذخیره‌سازی محلی ایجاد شده در ساعت گذشته را پاک کنید

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);
});
}

گسترش یا تغییر برنامه های شخص ثالث

فراتر از جاسازی ساده، فریم های کنترل شده مکانیسم هایی را برای IWA تعبیه شده برای اعمال کنترل بر محتوای وب شخص ثالث تعبیه شده ارائه می دهد. می‌توانید اسکریپت‌ها را در محتوای تعبیه‌شده اجرا کنید، درخواست‌های شبکه را رهگیری کنید، و منوهای زمینه پیش‌فرض را لغو کنید—همه در یک محیط ایمن و ایزوله.

موارد استفاده کنید

  • اعمال نام تجاری در سایت های شخص ثالث : CSS و جاوا اسکریپت سفارشی را به وب سایت های تعبیه شده تزریق کنید تا یک تم بصری یکپارچه را اعمال کنید.
  • محدود کردن پیمایش و رفتار پیوند : برخی از رفتارهای برچسب <a> را با تزریق اسکریپت قطع یا غیرفعال کنید.
  • بازیابی خودکار پس از خرابی یا عدم فعالیت : محتوای تعبیه‌شده را از نظر وضعیت‌های خرابی (مثلاً صفحه خالی، خطاهای اسکریپت) نظارت کنید و بعد از مهلت زمانی، جلسه را مجدداً بارگیری یا بازنشانی کنید.

نمونه کد

تزریق اسکریپت : از executeScript() برای تزریق جاوا اسکریپت به فریم کنترل شده استفاده کنید، به شما امکان می دهد رفتار را سفارشی کنید، همپوشانی اضافه کنید یا داده ها را از صفحات شخص ثالث تعبیه شده استخراج کنید. شما می توانید کد درون خطی را به عنوان یک رشته یا به یک یا چند فایل اسکریپت ارجاع دهید (با استفاده از مسیرهای نسبی در بسته 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() برای اعمال سبک های سفارشی در صفحات بارگذاری شده در یک قاب کنترل شده استفاده کنید.

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 برای افزودن، حذف و مدیریت منوهای راست کلیک سفارشی در کادر تعبیه شده استفاده کنید. این مثال نشان می‌دهد که چگونه می‌توان یک منوی سفارشی «Copy selection» را در داخل یک قاب کنترل‌شده اضافه کرد. وقتی متن انتخاب شد و کاربر راست کلیک کرد، منو ظاهر می شود. با کلیک بر روی آن، متن انتخاب شده در کلیپ بورد کپی می شود و تعاملات ساده و کاربرپسند در محتوای جاسازی شده را امکان پذیر می کند.

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));
    }
});

نسخه ی نمایشی

برای مروری بر روش های فریم های کنترل شده، نسخه ی نمایشی قاب کنترل شده را بررسی کنید.

نسخه ی نمایشی قاب کنترل شده

از طرف دیگر، سینک آشپزخانه IWA دارای یک برنامه با چندین زبانه است که هر کدام یک IWA API متفاوت مانند قاب های کنترل شده، سوکت های مستقیم و موارد دیگر را نشان می دهد.

سینک آشپزخانه IWA

نتیجه گیری

فریم های کنترل شده راهی قدرتمند و امن برای جاسازی، گسترش و تعامل با محتوای وب شخص ثالث در برنامه های وب ایزوله (IWA) ارائه می دهد. با غلبه بر محدودیت‌های iframe، آنها قابلیت‌های جدیدی مانند اجرای اسکریپت‌ها در محتوای تعبیه‌شده، رهگیری درخواست‌های شبکه و پیاده‌سازی منوهای زمینه سفارشی را فعال می‌کنند - همه اینها در عین حفظ مرزهای جداسازی دقیق. با این حال، از آنجایی که این APIها کنترل عمیقی بر محتوای تعبیه شده ارائه می دهند، محدودیت های امنیتی بیشتری نیز دارند و فقط در IWA ها در دسترس هستند، که برای اعمال ضمانت های قوی تری هم برای کاربران و هم برای توسعه دهندگان طراحی شده اند. برای بیشتر موارد استفاده، توسعه دهندگان ابتدا باید از عناصر استاندارد <iframe> استفاده کنند که در بسیاری از سناریوها ساده تر و کافی هستند. فریم‌های کنترل‌شده باید زمانی ارزیابی شوند که راه‌حل‌های مبتنی بر iframe با محدودیت‌های تعبیه‌شده مسدود شده یا فاقد قابلیت‌های کنترل و تعامل لازم هستند. چه در حال ساخت تجربه‌های کیوسک، یکپارچه‌سازی ابزارهای شخص ثالث یا طراحی سیستم‌های پلاگین مدولار باشید، فریم‌های کنترل‌شده کنترل دقیق را در یک محیط ساخت‌یافته، مجاز و امن امکان‌پذیر می‌سازد و آنها را به ابزاری حیاتی در نسل بعدی برنامه‌های وب پیشرفته تبدیل می‌کند.

منابع بیشتر