Controlled Frame

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

يُستخدَم العنصر <iframe> عادةً لتضمين موارد خارجية ضمن سياق التصفّح. تفرض إطارات iframe سياسات أمان الويب من خلال عزل المحتوى المضمّن من مصادر متعددة عن صفحة المضيف والعكس. على الرغم من أنّ هذا النهج يعزّز الأمان من خلال ضمان حدود آمنة بين المصادر، إلا أنّه يحدّ من بعض حالات الاستخدام. على سبيل المثال، قد يحتاج المستخدمون إلى تحميل المحتوى وإدارته بشكل ديناميكي من مصادر مختلفة، مثل أن يفعّل المعلّم حدث تنقّل لعرض صفحة ويب على شاشة في الصف. مع ذلك، تحظر العديد من المواقع الإلكترونية تضمين المحتوى في إطارات iframe بشكل صريح باستخدام عناوين الأمان، مثل X-Frame-Options وContent Security Policy (CSP). بالإضافة إلى ذلك، تمنع قيود iframe تضمين الصفحات من إدارة التنقّل أو سلوك المحتوى المضمّن بشكل مباشر.

تعالج واجهة برمجة التطبيقات Controlled Frame API هذا القيد من خلال السماح بتحميل أي محتوى ويب، حتى إذا كان يفرض سياسات تضمين مقيّدة. تتوفّر واجهة برمجة التطبيقات هذه حصريًا ضمن تطبيقات الويب المعزولة (IWA)، التي تتضمّن إجراءات أمان إضافية لحماية المستخدمين والمطوّرين من المخاطر المحتملة.

تنفيذ واجهة برمجة التطبيقات Controlled Frames API

قبل استخدام إطار تحكُّم، عليك إعداد IWA فعّال. يمكنك بعد ذلك دمج إطارات Controlled Frames في صفحاتك.

إضافة سياسة الأذونات

لاستخدام Controlled Frames، فعِّل الإذن المقابل من خلال إضافة حقل permissions_policy بالقيمة "controlled-frame" إلى بيان تطبيق الويب المعزول. بالإضافة إلى ذلك، يجب تضمين المفتاح cross-origin-isolated. لا يرتبط هذا المفتاح بواجهة برمجة التطبيقات Controlled Frames API تحديدًا، ولكنّه مطلوب لجميع تطبيقات الويب المعزولة (IWA) ويحدّد ما إذا كان بإمكان المستند الوصول إلى واجهات برمجة التطبيقات التي تتطلّب عزل المصادر المتعددة.

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

يحدّد المفتاح controlled-frame في بيان تطبيق الويب المعزول (IWA) قائمة السماح الخاصة بسياسة الأذونات، ويحدّد المصادر التي يمكنها استخدام إطارات Controlled Frames. على الرغم من أنّ ملف البيان يتوافق مع بنية "سياسة الأذونات" الكاملة، ما يتيح استخدام قيم مثل * أو مصادر محدّدة أو كلمات رئيسية مثل self وsrc، من المهم ملاحظة أنّه لا يمكن تفويض واجهات برمجة التطبيقات الخاصة بالتطبيقات المثبَّتة على الويب إلى مصادر أخرى. حتى إذا كانت القائمة المسموح بها تتضمّن حرف بدل أو مصادر خارجية، لن تسري هذه الأذونات على ميزات "التطبيقات المثبَّتة على الويب"، مثل controlled-frame. على عكس تطبيقات الويب العادية، لا تتضمّن تطبيقات الويب المعزولة أي ميزات تلقائية يمكن التحكّم فيها من خلال السياسات، بل تتطلّب إدخال بيانات بشكل واضح. بالنسبة إلى الميزات الخاصة بتطبيقات الويب المعزولة، يعني ذلك أنّ القيم مثل self (مصدر تطبيق الويب المعزول نفسه) أو src (مصدر إطار مضمّن) هي فقط التي تكون فعّالة من الناحية الوظيفية.

إضافة عنصر "إطار خاضع للتحكّم"

أدرِج عنصر <controlledframe> في HTML لتضمين محتوى تابع لجهة خارجية في تطبيق الويب المثبَّت.

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

تضبط السمة الاختيارية partition تقسيم مساحة التخزين للمحتوى المضمّن، ما يتيح لك عزل البيانات، مثل ملفات تعريف الارتباط ومساحة التخزين المحلية، من أجل الاحتفاظ بالبيانات على مستوى الجلسات.

مثال: تقسيم مساحة التخزين داخل الذاكرة

أنشئ إطارًا خاضعًا للتحكّم باستخدام قسم تخزين في الذاكرة باسم "session1". سيتم محو البيانات المخزّنة في هذا القسم (مثل ملفات تعريف الارتباط و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');

السيناريوهات وحالات الاستخدام المتكرّرة

كقاعدة عامة، اختَر أفضل تكنولوجيا لتلبية احتياجاتك مع تجنُّب التعقيد غير الضروري. في السنوات الأخيرة، سدّت تطبيقات الويب التقدّمية (PWA) الفجوة مع التطبيقات الأصلية، ما أتاح تجارب ويب فعّالة. إذا كان تطبيق الويب بحاجة إلى تضمين محتوى تابع لجهة خارجية، ننصحك أولاً باستكشاف طريقة <iframe> العادية. إذا كانت المتطلبات تتجاوز إمكانات إطارات iframe، قد تكون واجهة برمجة التطبيقات Controlled Frames على تطبيقات الويب المثبَّتة هي البديل الأفضل. يتم وصف حالات الاستخدام الشائعة في الأقسام التالية.

تضمين محتوى ويب تابع لجهة خارجية

تحتاج العديد من التطبيقات إلى إمكانية تحميل وعرض محتوى تابع لجهات خارجية ضمن واجهة المستخدم. ومع ذلك، عندما يتعدّد مالكو تطبيقات الويب، وهو سيناريو شائع مع التطبيقات المضمّنة، يصبح من الصعب وضع سياسات متّسقة من البداية إلى النهاية. على سبيل المثال، يمكن أن تمنع إعدادات الأمان <iframe> التقليدية من تضمين أنواع معيّنة من المحتوى، حتى إذا كانت الأنشطة التجارية بحاجة إلى ذلك. وعلى عكس عناصر <iframe>، تم تصميم Controlled Frames لتجاوز هذه القيود، ما يسمح للتطبيقات بتحميل المحتوى وعرضه حتى إذا كان يحظر التضمين العادي بشكل صريح.

حالات الاستخدام

  • العروض التقديمية في Classroom: يستخدم المعلّم شاشة لمس في الصف للتنقل بين المراجع التعليمية التي تحظر عادةً تضمين إطارات iframe.
  • اللافتات الرقمية في متاجر البيع بالتجزئة أو مراكز التسوق: يعرض كشك في مركز تسوق بالتناوب مواقع إلكترونية من متاجر مختلفة. تضمن إطارات Controlled Frames تحميل هذه الصفحات بشكل صحيح حتى إذا كانت تحظر التضمين.

عيّنات تعليمات برمجية

تساعد واجهات برمجة التطبيقات التالية من Controlled Frame API في إدارة المحتوى المضمّن.

التنقّل: توفّر إطارات Controlled Frames طرقًا متعدّدة لإدارة سجلّ التنقّل والمحتوى المضمّن والتحكّم فيهما آليًا.

تحصل السمة 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 واجهات برمجة تطبيقات لإدارة البيانات المخزّنة ضمن قسم الإطار.

استخدِم clearData() لإزالة جميع البيانات المخزَّنة، وهو أمر مفيد بشكل خاص لإعادة ضبط الإطار بعد جلسة مستخدم أو لضمان حالة نظيفة. تعرض الدالة البرمجية وعدًا يتم تنفيذه عند اكتمال العملية. يمكن أيضًا تقديم خيارات الضبط الاختيارية التالية:

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

مثال: محو ملفات تعريف الارتباط و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 آليات تتيح لتطبيق الويب المعزول الذي يتم التضمين فيه التحكّم في محتوى الويب التابع لجهة خارجية والمضمَّن. يمكنك تنفيذ النصوص البرمجية ضمن المحتوى المضمّن، واعتراض طلبات الشبكة، وتعديل قوائم السياق التلقائية، وكل ذلك في بيئة آمنة ومعزولة.

حالات الاستخدام

  • فرض استخدام العلامة التجارية على المواقع الإلكترونية التابعة لجهات خارجية: يمكنك إدخال 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() لتطبيق أنماط مخصّصة على الصفحات التي يتم تحميلها ضمن إطار خاضع للتحكّم.

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 لإضافة قوائم نقر بزر الماوس الأيمن مخصّصة وإزالتها والتعامل معها ضمن الإطار المضمّن. يوضّح هذا المثال كيفية إضافة قائمة مخصّصة باسم "نسخ النص المحدّد" داخل إطار تحكُّم. عندما يتم تحديد نص وينقر المستخدم بزر الماوس الأيمن، تظهر القائمة. يؤدي النقر عليه إلى نسخ النص المحدّد إلى الحافظة، ما يتيح تفاعلات بسيطة وسهلة الاستخدام ضمن المحتوى المضمّن.

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 API

بدلاً من ذلك، يتضمّن IWA Kitchen Sink تطبيقًا يحتوي على علامات تبويب متعددة، تعرض كل منها واجهة برمجة تطبيقات مختلفة لتطبيقات الويب المعزولة، مثل Controlled Frames وDirect Sockets وغيرها.

IWA Kitchen Sink

الخاتمة

توفّر إطارات Controlled Frames طريقة فعّالة وآمنة لتضمين محتوى ويب تابع لجهات خارجية وتوسيعه والتفاعل معه في تطبيقات الويب المعزولة (IWA). ومن خلال التغلّب على قيود أُطر iframe، تتيح هذه الأُطر إمكانات جديدة، مثل تنفيذ النصوص البرمجية داخل المحتوى المضمّن، واعتراض طلبات الشبكة، وتنفيذ قوائم سياقية مخصّصة، وكل ذلك مع الحفاظ على حدود عزل صارمة. ومع ذلك، بما أنّ واجهات برمجة التطبيقات هذه توفّر تحكّمًا دقيقًا في المحتوى المضمّن، فإنّها تتضمّن أيضًا قيودًا إضافية على الأمان ولا تتوفّر إلا في التطبيقات المثبَّتة على الويب، والتي تم تصميمها لفرض ضمانات أقوى لكل من المستخدمين والمطوّرين. في معظم حالات الاستخدام، على المطوّرين أولاً التفكير في استخدام عناصر <iframe> العادية، وهي أبسط وتكفي في العديد من السيناريوهات. يجب تقييم Controlled Frames عندما يتم حظر الحلول المستندة إلى iframe بسبب قيود التضمين أو عندما لا تتوفّر إمكانات التحكّم والتفاعل اللازمة. سواء كنت بصدد إنشاء تجارب أكشاك أو دمج أدوات تابعة لجهات خارجية أو تصميم أنظمة إضافات معيارية، تتيح لك إطارات Controlled Frames التحكّم الدقيق في بيئة منظَّمة وآمنة تتطلّب الحصول على إذن، ما يجعلها أداة مهمة في الجيل التالي من تطبيقات الويب المتقدّمة.

مزيد من المراجع