Bingkai yang Dikontrol

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

Elemen <iframe> biasanya digunakan untuk menyematkan resource eksternal dalam konteks penjelajahan. Iframe menerapkan kebijakan keamanan web dengan mengisolasi konten sematan lintas origin dari halaman host dan sebaliknya. Meskipun pendekatan ini meningkatkan keamanan dengan memastikan batas yang aman antara origin, pendekatan ini membatasi beberapa kasus penggunaan. Misalnya, pengguna mungkin perlu memuat dan mengelola konten secara dinamis dari berbagai sumber, seperti guru yang memicu peristiwa navigasi untuk menampilkan halaman web di layar kelas. Namun, banyak situs secara eksplisit memblokir penyematan di iframe menggunakan header keamanan seperti X-Frame-Options dan Content Security Policy (CSP). Selain itu, batasan iframe mencegah penyematan halaman dari pengelolaan langsung navigasi atau perilaku konten yang disematkan.

Controlled Frame API mengatasi batasan ini dengan mengizinkan pemuatan konten web apa pun, meskipun konten tersebut menerapkan kebijakan penyematan yang ketat. API ini hanya tersedia dalam Aplikasi Web Terisolasi (IWA), yang menggabungkan langkah-langkah keamanan tambahan untuk melindungi pengguna dan developer dari potensi risiko.

Menerapkan Controlled Frame

Sebelum menggunakan Frame Terkontrol, Anda harus menyiapkan IWA fungsional. Kemudian, Anda dapat mengintegrasikan Frame Terkontrol ke dalam halaman Anda.

Menambahkan kebijakan izin

Untuk menggunakan Frame yang Dikontrol, aktifkan izin yang sesuai dengan menambahkan kolom permissions_policy dengan nilai "controlled-frame" ke manifes IWA Anda. Selain itu, sertakan kunci cross-origin-isolated. Kunci ini tidak khusus untuk Bingkai yang Dikontrol, tetapi diperlukan untuk semua IWA dan menentukan apakah dokumen dapat mengakses API yang memerlukan isolasi lintas origin.

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

Kunci controlled-frame dalam manifes Aplikasi Web Terisolasi (IWA) menentukan daftar izin kebijakan izin, yang menentukan origin mana yang dapat menggunakan Bingkai yang Dikontrol. Meskipun mendukung sintaksis Permissions Policy lengkap—yang memungkinkan nilai seperti *, asal tertentu, atau kata kunci seperti self dan src—penting untuk diperhatikan bahwa API khusus IWA tidak dapat didelegasikan ke asal lain. Meskipun daftar yang diizinkan mencakup karakter pengganti atau asal eksternal, izin ini tidak akan berlaku untuk fitur IWA seperti controlled-frame. Tidak seperti aplikasi web standar, IWA secara default menyetel semua fitur yang dikontrol kebijakan ke tidak ada, sehingga memerlukan deklarasi eksplisit. Untuk fitur khusus IWA, ini berarti hanya nilai seperti self (origin IWA itu sendiri) atau src (origin frame yang disematkan) yang berfungsi secara efektif.

Menambahkan elemen Bingkai yang Dikontrol

Sisipkan elemen <controlledframe> ke dalam HTML Anda untuk menyematkan konten pihak ketiga dalam IWA Anda.

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

Atribut partition opsional mengonfigurasi partisi penyimpanan untuk konten yang disematkan, sehingga Anda dapat mengisolasi data seperti cookie dan penyimpanan lokal untuk mempertahankan data di seluruh sesi.

Contoh: Partisi penyimpanan dalam memori

Buat Frame Terkontrol menggunakan partisi penyimpanan dalam memori bernama "session1". Data yang disimpan di partisi ini (misalnya, cookie, dan localStorage) akan dihapus saat frame dihancurkan atau sesi aplikasi berakhir.

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

Contoh: Partisi penyimpanan persisten

Buat Frame yang Dikontrol menggunakan partisi penyimpanan persisten bernama "user_data". Awalan "persist:" memastikan bahwa data yang disimpan di partisi ini disimpan ke disk dan akan tersedia di seluruh sesi aplikasi.

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

Mendapatkan referensi elemen

Dapatkan referensi ke elemen <controlledframe> sehingga Anda dapat berinteraksi dengannya seperti elemen HTML standar:

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

Skenario dan kasus penggunaan umum

Sebagai aturan umum, pilih teknologi terbaik untuk memenuhi kebutuhan Anda sekaligus menghindari kerumitan yang tidak perlu. Dalam beberapa tahun terakhir, Progressive Web App (PWA) telah menutup kesenjangan dengan aplikasi native, sehingga memungkinkan pengalaman web yang canggih. Jika aplikasi web perlu menyematkan konten pihak ketiga, sebaiknya pelajari pendekatan <iframe> reguler terlebih dahulu. Jika persyaratan melebihi kemampuan iframe, Frame yang Dikontrol di IWA mungkin menjadi alternatif terbaik. Kasus penggunaan umum dijelaskan di bagian berikut.

Menyematkan konten web pihak ketiga

Banyak aplikasi memerlukan kemampuan untuk memuat dan menampilkan konten pihak ketiga dalam antarmuka penggunanya. Namun, jika ada beberapa pemilik aplikasi web yang terlibat—skenario umum dengan aplikasi tersemat—menjadi sulit untuk menetapkan kebijakan end-to-end yang konsisten. Misalnya, setelan keamanan dapat mencegah <iframe> tradisional menyematkan jenis konten tertentu, meskipun bisnis memiliki kebutuhan yang sah untuk melakukannya. Tidak seperti elemen <iframe>, Frame yang Dikontrol dirancang untuk melewati batasan ini, sehingga aplikasi dapat memuat dan menampilkan konten meskipun secara eksplisit melarang penyematan standar.

Kasus penggunaan

  • Presentasi Classroom: Pengajar menggunakan layar sentuh kelas untuk beralih antar-materi pendidikan yang biasanya memblokir penyematan iframe.
  • Reklame digital di retail atau mal: Kios di pusat perbelanjaan menampilkan situs dari berbagai toko secara bergantian. Frame Terkontrol memastikan halaman ini dimuat dengan benar meskipun halaman tersebut membatasi penyematan.

Contoh kode

Controlled Frame API berikut berguna untuk mengelola konten sematan.

Navigation: Frame yang Dikontrol menyediakan beberapa metode untuk mengelola dan mengontrol navigasi serta histori navigasi konten yang disematkan secara terprogram.

Atribut src mendapatkan atau menetapkan URL konten yang ditampilkan dalam frame, berfungsi dengan cara yang sama seperti atribut HTML.

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

Metode back() akan kembali satu langkah dalam histori frame. Promise yang ditampilkan di-resolve ke boolean yang menunjukkan apakah navigasi berhasil.

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

Metode forward() menavigasi maju satu langkah dalam histori frame. Promise yang ditampilkan di-resolve ke boolean yang menunjukkan apakah navigasi berhasil.

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

Metode reload() memuat ulang halaman saat ini dalam frame.

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

Selain itu, Frame Terkontrol menyediakan peristiwa yang memungkinkan Anda melacak siklus proses lengkap permintaan navigasi—dari inisiasi dan pengalihan hingga pemuatan, penyelesaian, atau pembatalan konten.

  • loadstart: Diaktifkan saat navigasi dimulai dalam frame.
  • loadcommit: Diaktifkan saat permintaan navigasi telah diproses, dan konten dokumen utama mulai dimuat.
  • contentload: Diaktifkan saat dokumen utama dan resource pentingnya telah selesai dimuat (mirip dengan DOMContentLoaded).
  • loadstop: Diaktifkan saat semua resource untuk halaman (termasuk subframe, gambar) telah selesai dimuat.
  • loadabort: Diaktifkan jika navigasi dibatalkan (misalnya, oleh tindakan pengguna atau navigasi lain yang dimulai).
  • loadredirect: Diaktifkan saat pengalihan sisi server terjadi selama navigasi.
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}`);
});

Anda juga dapat memantau dan berpotensi mencegat interaksi atau permintaan tertentu yang dimulai oleh konten yang dimuat dalam frame yang dikontrol, seperti upaya untuk membuka dialog, meminta izin, atau membuka jendela baru.

  • dialog: Diaktifkan saat konten sematan mencoba membuka dialog (alert, confirm, prompt). Anda akan menerima detail dan dapat merespons.
  • consolemessage: Dipicu saat pesan dicatat ke konsol dalam frame.
  • permissionrequest: Diaktifkan saat konten yang disematkan meminta izin (misalnya, geolokasi dan notifikasi). Anda akan menerima detail dan dapat mengizinkan atau menolak permintaan tersebut.
  • newwindow: Diaktifkan saat konten yang disematkan mencoba membuka jendela atau tab baru (misalnya, dengan window.open atau link dengan target="_blank"). Anda akan menerima detail dan dapat menangani atau memblokir tindakan tersebut.
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');
 });

Ada juga peristiwa perubahan status yang memberi tahu Anda tentang perubahan terkait status rendering frame yang dikontrol, seperti modifikasi pada dimensi atau tingkat zoom-nya.

  • sizechanged: Diaktifkan saat dimensi konten frame berubah.
  • zoomchange: Diaktifkan saat level zoom konten frame berubah.
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});
});

Metode penyimpanan: Frame yang Dikontrol menawarkan API untuk mengelola data yang disimpan dalam partisi frame.

Gunakan clearData() untuk menghapus semua data tersimpan, yang sangat berguna untuk mereset frame setelah sesi pengguna atau memastikan status bersih. Metode ini menampilkan Promise yang di-resolve saat operasi selesai. Opsi konfigurasi opsional juga dapat diberikan:

  • types: Array string yang menentukan jenis data yang akan dihapus (misalnya, ['cookies', 'localStorage', 'indexedDB']). Jika tidak ditentukan, semua jenis data yang berlaku biasanya dihapus.
  • options: Mengontrol proses penghapusan, seperti menentukan rentang waktu menggunakan properti sejak (stempel waktu dalam milidetik sejak epoch) untuk menghapus data yang dibuat setelah waktu tersebut saja.

Contoh: Menghapus semua penyimpanan yang terkait dengan Frame yang Dikontrol

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

Contoh: Hapus hanya cookie dan localStorage yang dibuat dalam satu jam terakhir

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

Memperluas atau mengubah aplikasi pihak ketiga

Selain penyematan sederhana, Controlled Frames menawarkan mekanisme bagi IWA penyematan untuk mengontrol konten web pihak ketiga yang disematkan. Anda dapat menjalankan skrip dalam konten sematan, mencegat permintaan jaringan, dan mengganti menu konteks default—semuanya dalam lingkungan yang aman dan terisolasi.

Kasus penggunaan

  • Menerapkan branding di seluruh situs pihak ketiga: Menyisipkan CSS dan JavaScript kustom ke situs yang disematkan untuk menerapkan tema visual yang terpadu.
  • Membatasi Perilaku Link dan Navigasi: Mencegat atau menonaktifkan perilaku tag tertentu dengan injeksi skrip.<a>
  • Mengotomatiskan Pemulihan Setelah Error atau Tidak Ada Aktivitas: Memantau konten sematan untuk status kegagalan (misalnya, layar kosong, error skrip) dan memuat ulang atau mereset sesi secara terprogram setelah waktu tunggu.

Contoh kode

Penyisipan Skrip: Gunakan executeScript() untuk menyisipkan JavaScript ke dalam frame yang dikontrol, sehingga Anda dapat menyesuaikan perilaku, menambahkan overlay, atau mengekstrak data dari halaman pihak ketiga yang disematkan. Anda dapat memberikan kode inline sebagai string atau mereferensikan satu atau beberapa file skrip (menggunakan jalur relatif dalam paket IWA). Metode ini menampilkan promise yang di-resolve ke hasil eksekusi skrip—biasanya nilai pernyataan terakhir.

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

Penyisipan gaya: Gunakan insertCSS() untuk menerapkan gaya kustom ke halaman yang dimuat dalam Frame yang Dikontrol.

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

Penyadapan Permintaan Jaringan: Gunakan WebRequest API untuk mengamati dan berpotensi mengubah permintaan jaringan dari halaman yang disematkan, seperti memblokir permintaan, mengubah header, atau mencatat penggunaan.

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

Menambahkan Menu Konteks Kustom: Gunakan contextMenus API untuk menambahkan, menghapus, dan menangani menu klik kanan kustom dalam frame sematan. Contoh ini menunjukkan cara menambahkan menu "Salin pilihan" kustom di dalam Frame Terkontrol. Saat teks dipilih dan pengguna mengklik kanan, menu akan muncul. Mengklik tombol ini akan menyalin teks yang dipilih ke papan klip, sehingga memungkinkan interaksi yang sederhana dan mudah digunakan dalam konten yang disematkan.

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

Demo

Lihat demo Controlled Frame untuk mengetahui ringkasan metode Controlled Frame.

Demo Controlled Frame

Atau, IWA Kitchen Sink menampilkan aplikasi dengan beberapa tab, yang masing-masing mendemonstrasikan IWA API yang berbeda seperti Controlled Frames, Direct Sockets, dan lainnya.

IWA Kitchen Sink

Kesimpulan

Bingkai yang Dikontrol menawarkan cara yang efektif dan aman untuk menyematkan, memperluas, dan berinteraksi dengan konten web pihak ketiga di Aplikasi Web Terisolasi (IWA). Dengan mengatasi batasan iframe, mereka memungkinkan kemampuan baru seperti menjalankan skrip di dalam konten sematan, mencegat permintaan jaringan, dan menerapkan menu konteks kustom—sekaligus mempertahankan batas isolasi yang ketat. Namun, karena API ini menawarkan kontrol mendalam atas konten sematan, API ini juga dilengkapi dengan batasan keamanan tambahan dan hanya tersedia dalam IWA, yang dirancang untuk menerapkan jaminan yang lebih kuat bagi pengguna dan developer. Untuk sebagian besar kasus penggunaan, developer harus mempertimbangkan untuk menggunakan elemen <iframe> standar terlebih dahulu, yang lebih sederhana dan memadai dalam banyak skenario. Frame Terkontrol harus dievaluasi saat solusi berbasis iframe diblokir oleh batasan penyematan atau tidak memiliki kemampuan kontrol dan interaksi yang diperlukan. Baik Anda sedang membangun pengalaman kios, mengintegrasikan alat pihak ketiga, atau mendesain sistem plugin modular, Frame Terkontrol memungkinkan kontrol terperinci di lingkungan yang terstruktur, diberi izin, dan aman—sehingga menjadikannya alat penting dalam aplikasi web canggih generasi berikutnya.

Referensi Lainnya