JavaScript サンドボックスの探索

JavaScript サンドボックスの探索

1. シナリオ

最近、Web ベースのプラグイン システムに似たものに取り組んでおり、サードパーティ アプリケーションのコードを実行するために js サンドボックスを操作しています。

2. サンドボックスの基本機能

実装前に(正確にはいくつかの解決策を研究した後)、サンドボックスはevent bus通信に基づいて上位層機能を実装することが決定されました。基本的なインターフェースは次のとおりです。

エクスポートインターフェースIEventEmitter {
  /**
   * リスニングイベント * @param チャネル
   * @param ハンドル
   */
  on(チャネル: 文字列、ハンドル: (データ: any) => void): void;

  /**
   * リスニングをキャンセル * @param チャネル
   */
  offByChannel(チャンネル: 文字列): void;

  /**
   * トリガーイベント * @param チャネル
   * @param データ
   */
  出力(チャネル: 文字列、データ: 任意): void;
}

/**
 * 基本的な js vm 機能 */
エクスポートインターフェースIJavaScriptShadowboxはIEventEmitterを拡張します{
  /**
   * 任意のコードを実行する * @param code
   */
  eval(コード: 文字列): void;

  /**
   * インスタンスを破棄する */
  破棄(): void;
}

通信機能に加えて、次の 2 つの追加方法が必要です。

  • eval : jsコードを実行する
  • destroy : サンドボックスを破壊し、内部実装でいくつかのクリーンアップタスクを処理する

JavaScript サンドボックス図:

以下ではiframe/web worker/quickjsを使用して任意のjsを実行する方法を説明します。

3. iframeの実装

正直に言うと、Web のサンドボックスについて話すとき、最初に思い浮かぶのはおそらくiframeですが、これはエントリ ファイルとして js ではなくhtmlを使用するため、エントリとして js を使用したいが、必ずしもiframeを表示する必要がないシナリオにはあまり適していません。

もちろん、jsコードをhtmlでラップして実行することもできます。

関数 evalByIframe(コード: 文字列) {
  const html = `<!DOCTYPE html><body><script>$[code]</script></body></html>`;
  定数 iframe = document.createElement("iframe");
  iframeの幅 = "0";
  iframe.height = "0";
  iframe.style.display = "なし";
  document.body.appendChild(iframe);
  const blob = new Blob([html], { type: "text/html" });
  iframe.src = URL.createObjectURL(blob);
  iframe を返します。
}

evalByIframe(`
document.body.innerHTML = 'こんにちは世界'
console.log('location.href: ', location.href)
console.log('localStorage: ',localStorage)
`);

しかし、 iframeは次のような問題があります。

  • evalとほぼ同じです (主にObject.createObjectURLを使用し、相同性をもたらします) – 致命的
  • すべてのブラウザapi –すべてのdom apiではなく、挿入されたapiにのみアクセスできることを推奨します。

4. Webワーカーの実装

基本的に、 web worker 、js をエントリ ポイントとして使用し、 iframe

関数 evalByWebWorker(コード: 文字列) {
  const blob = new Blob([code], { type: "application/javascript" });
  url を URL.createObjectURL(blob) に変換します。
  新しい Worker(url) を返します。
}

WebWorkerによる評価(`
console.log('location.href: ', location.href)
// console.log('localStorage: ', localStorage)
`);

しかし同時に、 iframeよりも優れているのは確かだ

  • アクセスできないlocalStorage/document APIなど、サポートされるブラウザAPIは限られています。詳細については、[MDN] Web Workersで使用できる関数とクラスを参照してください。
  • 結局のところ、挿入されたAPIはすべて、 postMessage/onmessageに基づく非同期操作です。

5. Quickjsの実装

quickjsを使用する主なインスピレーションは、figmaのプラグインシステムに関するブログ投稿、quickjsの中国語ドキュメントから来ています。

quickjs とは何ですか?これは JavaScript ランタイムです。最も一般的に使用されるランタイムはブラウザとnodejsですが、他にも多くのランタイムがあり、GoogleChromeLabs/jsvu でさらに詳しく見つけることができます。 quickjswasmへのコンパイルをサポートし、ブラウザ上で実行される軽量の組み込みランタイムです。また、 es2020までの js 機能 (人気のPromiseasync/awaitを含む) もサポートしています。

非同期関数evalByQuickJS(コード:文字列) {
  const quickJS = getQuickJS() を待機します。
  定数 vm = quickJS.createVm();
  定数 res = vm.dump(vm.unwrapResult(vm.evalCode(code)));
  vm.dispose();
  res を返します。
}

 
console.log(evalByQuickJS(`1+1`) を待機します);

アドバンテージ:

  • 実際、セキュリティの面では比類のないもので、異なるvm上で実行されるため、既存のProxyベースのマイクロフロントエンドで発生する可能性のあるセキュリティ上の問題が発生しにくくなります。
  • 実際のテストはありませんが、 figmaのブログ投稿では、ブラウザの構造化クローンでは大きなオブジェクトを扱うときにパフォーマンスの問題が発生するのに対し、 quickjsではこの問題は発生しないと指摘されています。

欠点:

  • 共通のconsole/setTimeout/setIntervalなどのグローバルapi存在しません。これらはjsの機能ではなく、ブラウザとnodejsランタイムによって実装されているため、手動で実装して挿入する必要があり、これは大きな欠点です。
  • ブラウザのDevTooデバッグが使用できない
  • 基礎となる実装は C で行われるため、メモリの解放は手動で管理する必要があります。

6. 結論

最終的に、インターフェースに基づいて Web Worker と QuickJS の EventEmitter を実装し、いつでも切り替えられる機能をサポートすることを選択しました。

JavaScript サンドボックスの探索に関するこの記事はこれで終わりです。JavaScript サンドボックスに関するより関連性の高いコンテンツについては、123WORDPRESS.COM の過去の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • Quickjs は JavaScript サンドボックスの詳細をカプセル化します
  • JavaScript Sandboxについての簡単な説明
  • フロントエンドJSサンドボックスを実装するいくつかの方法についての簡単な説明
  • Node.jsサンドボックス環境についての簡単な説明
  • Node.js アプリケーション用の安全なサンドボックス環境の設定
  • JS実装クロージャにおけるサンドボックスモードの例
  • JS サンドボックス モードの例の分析
  • JavaScript デザインパターン セキュリティ サンドボックス モード
  • WebWorkerはJavaScriptサンドボックスの詳細をカプセル化します

<<:  CSS BEM 記述標準の詳細な説明

>>:  MySQLトリガーの例の詳細な説明

推薦する

Elementのメッセージポップアップウィンドウが繰り返しポップアップする問題の解決

目次1. 使用2. メッセージポップアップウィンドウが繰り返し表示される問題を解決する1. 使用Vu...

nginx リバース プロキシでの proxy_pass の実装

フォーマットはシンプルです: proxy_pass URL; URL には、送信プロトコル (htt...

Dockerデータのバックアップとリカバリプロセスの詳細な説明

データのバックアップ操作は非常に簡単です。次のコマンドを実行します。 docker run --vo...

JavaScript排他的思考の具体的な実装

前回のブログで、Xiao Xiong は関連する要素の操作方法を更新しましたが、同じ要素のグループが...

虫眼鏡コンポーネントのネイティブ js 実装

この記事の例では、参考までに虫眼鏡コンポーネントを開発するためのjsの具体的なコードを共有しています...

html+cssレイアウトの3つの方法(ナチュラルレイアウト/フローレイアウト/ポジショニングレイアウト)

1. 自然なレイアウト<br />レイアウトは変更せずに自動的に左揃えになります。 2....

MySQL で遅いクエリ SQL を見つけて最適化する詳細な例

目次1. 遅いクエリSQLを見つけて最適化する方法a. スローログに基づいてスロークエリSQLを見つ...

Unicode 署名 BOM の詳細な説明

Unicode 署名 BOM - BOM とは何ですか? BOM は Byte Order Mark...

XHTML 入門チュートリアル: テーブルタグの応用

<br />テーブルは XHTML では扱いにくいタグなので、このセクションで理解するだ...

MySQL 更新セットとの違い

目次問題の説明原因分析解決問題の説明最近、奇妙な問い合わせを受けました。更新ステートメントはエラーな...

ReactでCSSスタイルを動的に変更する2つの方法の詳細な説明

最初の方法: デモとしてボタンをクリックしてテキストを表示または非表示にするクラスを動的に追加します...

JavaScript スロットリングとアンチシェイクに関する簡単な説明

目次スロットルと手ぶれ防止コンセプト:違いスロットリングの実装スロットル機能手ぶれ補正の実装手ぶれ防...

Mysql 5.6ではユーザー名とパスワードを変更するメソッドが追加されました

まずMySQLにログインする シェル> mysql --user=root mysqlパスワー...

JavaScriptはブラウザがIEかどうかを判定します

フロントエンド開発者としては、IEの落とし穴は避けて通れません。他のブラウザはいいのにIEは壊れてい...

Vite2とVue3を使用したウェブサイトの国際化を実現するプロセス全体

目次序文vue-i18nをインストールするロケールの設定getLangs.js の実装i18nインス...