1. サンドボックスとは何か、なぜウィジェットは檻の中なのか
ChatGPT があなたのウィジェットを表示するとき、決して通常の <iframe src="https://your-site"> のようにはレンダーしません。 ウィジェットは管理された「サンドボックス」— 別オリジンかつ強固なセキュリティ設定の iframe — 内で起動します。
技術的には、概ね次のような構成です。
flowchart TD
User["ChatGPT のユーザー"]
Chat["ChatGPT UI + モデル"]
Iframe["あなたのウィジェット
sandboxed iframe"]
MCP["あなたの MCP / backend"]
User --> Chat
Chat -->|tool 呼び出し| MCP
MCP -->|structuredContent + _meta| Chat
Chat -->|window.openai.*| Iframe
Iframe -->|callTool / follow-up| Chat
Chat --> MCP
あなたのコードはこの iframe の中でのみ実行され、外界へのアクセスはホスト(ChatGPT)が提供する厳密に制御された API経由になります。ウィジェットがやってはならないこと:
- ChatGPT 自体(DOM、スタイル、パフォーマンス)を壊す;
- ユーザーのプライバシーを侵害する;
- 無制限にネットワークへアクセスする。
ここからサンドボックスの主要な制限が導かれます。
DOM と origin の分離
ウィジェットは(例: https://sandbox-apps.oaiusercontent.com)のようなサンドボックス用ドメイン上にあり、iframe には sandbox 属性が付きます。これはつまり:
- window.parent や ChatGPT の document に触れることはできず、試せば SecurityError になる;
- postMessage のようなクロスドメイン機能はホストにより制御される;
- 「CSS で ChatGPT の UI を『修正』する」類いの試みは失敗に終わる。
ネットワークと CSP 制限
ブラウザーとホストの CSP ポリシーにより、ウィジェットからのネットワークアクセスは制限されます:
- fetch はレビューを通過したホワイトリストのドメインにのみアクセス可;
- ウィジェットから触れてよいドメインは MCP のレスポンスで openai/widgetCSP として明示宣言する必要があり、そうでない要求は通らない;
- 推奨は、本格的な処理はウィジェットから直接ネットワークに出ず、MCP ツールと callTool を通じて backend に行うこと(詳細はモジュール 4)。
実務的には、ウィジェットは薄い UI レイヤーだと考えてください。ChatGPT とあなたのサーバーとを、規定されたチャネルで会話するのであって、インターネット上で自由に動き回る普通の SPA ではありません。
ストレージとリソース
ローカルストレージ(localStorage、sessionStorage)は利用できますが、cookie は利用できません。これはアプリ実装時に考慮してください。 メモリと CPU も制限されます。例えばウィジェット内で 10 億までの素数を全部計算しようとすれば、ホストはその iframe を終了させる権利があります。
重要な結論は次のとおりです。ウィジェット内で重い計算や長寿命の「キャッシュ」を持たないこと。 複雑なロジックはサーバー側で行い、React コンポーネント側では行わないでください。
2. window.openai: ウィジェットと ChatGPT の橋
ウィジェットが(ツールの結果、表示モード、ロケール、状態など)を知るために、ChatGPT は初期化時に iframe ウィンドウに 1 つのグローバルオブジェクト — window.openai — を組み込みます。
これはあなたのコードでも npm パッケージでもなく、ホストオブジェクトで、AI プラットフォーム自身が提供します。 内部ではホストと iframe の間のイベントやメッセージに紐づいていますが、普段それを意識する必要はほとんどありません。 覚えておくべき点がいくつかあります。
window.openai は誰が・いつ作るのか
window.openai が現れるのは次のケースのみです。
- ChatGPT があなたのウィジェット用に作成したその iframe の中;
- HTML テンプレートが正しい mimeType(text/html+skybridge)で提供され、すべての検証を通過したとき。
このタイプは HelloWorld App のモジュールで既に見ました。通常の text/html ではなく、ウィジェットのページが返すタイプです。
もしウィジェットのページをブラウザーで直接開いただけなら:
console.log(window.openai); // undefined
となって当然です。したがって、ローカル開発や Storybook のような「standalone」モードを想定するなら、ウィジェットのコードでは常にこのオブジェクトの存在をチェックしてください。
単純な例(最終形ではなくイメージ):
if (typeof window !== "undefined" && (window as any).openai) {
console.log("We are inside ChatGPT sandbox!");
}
初期化の非同期性
内部的に ChatGPT は、新しいデータの到着(新しい toolOutput、displayMode の変更など)に応じて、内部イベント openai:set_globals を使いながら window.openai を更新します。
つまり中の「値」は静的ではありません。AI モデルが MCP ツールを呼び出し、backend が新しい structuredContent を返せば、 window.openai.toolOutput は React コンポーネントの背後でその場で変わります。
ここから 2 つの推奨が導かれます。
- 初期化時に 1 回だけ const toolOutput = window.openai.toolOutput のように「スナップショット」を取って、それが永遠に不変だと思い込まないこと。同じウィジェットを ChatGPT が再利用することもあります。
- 変更に購読できるフック層(後述)を使うこと。
3. window.openai の構造: データ、API、コンテキスト
公式ドキュメントは window.openai のフィールドとメソッドをコンパクトにまとめています。 ここではもう少し「人間的な」形に整理します。
主なフィールドとメソッド
window.openai = {
// State & data
toolInput, // JSON: モデルがあなたの MCP ツールに渡したパラメータ
toolOutput, // JSON: あなたの MCP ツールがモデルに返したパラメータ
toolResponseMetadata, // MCP ツールの応答: _meta: {...} の一部
widgetState, // ウィジェットの保存済み状態を読み取れる
setWidgetState, // ウィジェットの状態をここに保存できる
// Runtime APIs
callTool, // MCP ツールを呼び出す
sendFollowUpMessage, // チャット内でモデルにメッセージを密かに送る: 応答が始まる
requestDisplayMode, // ウィジェットの表示モードを切り替える: fullscreen, pip, inline
requestModal, // ウィジェットをモーダルにする
requestClose, // ウィジェットを閉じる。モーダルなら閉じてウィジェットに戻る
requestCheckout, // 決済用モーダルを開く。サーバーは ACP を実装している必要がある
notifyIntrinsicHeight, // ウィジェットの高さ変更を通知する
openExternal, // 新しいウィンドウでリンクを開く
// Context
theme, // ダーク or ライトテーマ
displayMode, // 現在の表示モード。requestDisplayMode と異なることがある
maxHeight, // ウィジェットに許可された最大高さ
safeArea, // 「安全な描画領域」— ノッチ付き端末などで有効
view,
userAgent, // ブラウザーの userAgent
locale // ブラウザーの locale
}
同じ内容を表にまとめると:
| カテゴリー | プロパティ / メソッド | 用途 |
|---|---|---|
| State & data | |
ツールが呼び出されたときの引数。Read-only。 |
| State & data | |
MCP 応答の structuredContent。ウィジェットとモデルが読むデータ。 |
| State & data | |
応答の _meta。ウィジェットからのみ見える。モデルは読まない。 |
| State & data | |
ChatGPT がウィジェットの再レンダー間で保持する UI 状態のスナップショット。 |
| State & data | |
widgetState の新しいスナップショットを同期的に保存。 |
| Function | |
ウィジェットから MCP ツールを呼び出す。 |
| Function | |
ウィジェットからのメッセージを ChatGPT に送って応答を開始させる。 |
| Function | |
ホストに inline / fullscreen / pip をリクエスト。 |
| Function | |
モーダルの表示をリクエスト。 |
| Function | |
コンテンツの高さが変わったことを通知。 |
| Function | |
ACP プロトコルによる決済ダイアログを開く。 |
| Function | |
外部リンクをユーザーのブラウザーで開く。 |
| Context | |
環境シグナル(テーマ、モード、利用可能な高さ、ロケールなど)。 |
この表のすべてを一度に暗記する必要はありません。まずは「地図」として眺めてください。 次は「リファレンス」ではなく、実際の使い方として見ていきます。
toolInput と toolOutput: データはどこから来るか
モデルがあなたのツールを呼ぶとき、JSON 引数を組み立てます。これらの引数は:
- MCP サーバーのハンドラーに input として届き、
- 同時にウィジェット内の window.openai.toolInput にも入ります。
ツール実行後、サーバーは次を返します:
- structuredContent — UI 向けの構造化データ;
- _meta — ウィジェットだけが読むプライベートデータ;
- content — 「何が起きたか」をユーザーに説明するためのモデル向けテキスト。
structuredContent は window.openai.toolOutput に、 _meta は window.openai.toolResponseMetadata になります。
ミニ例(バニラ JS、React なし):
const root = document.getElementById("root");
// null 合体演算子は安全に使える
const gifts = window.openai.toolOutput?.gifts ?? [];
root.textContent = `見つかったギフト: ${gifts.length}`;
widgetState と setWidgetState: ウィジェットのメモリ
widgetState は、プラットフォームがレンダー間、さらには会話のターンをまたいであなたの UI について覚えておくことを許してくれる領域です。
widgetState に向いている例:
- 選択中のギフト;
- 現在のソート(価格順 / 人気順);
- 一覧のページ番号。
向いていない例:
- 外部 API の生データ;
- base64 の画像;
- 秘密のトークン。
覚えておくべきことは 2 つ:
- widgetState はコンテキストと一緒にモデルにも渡されるため、機密情報は入れない。
- 容量は制限(おおよそ 4 千トークン)されているので、ミニ DB のように使わない。
最小の使用例(フックなし、バニラ JS):
const current = window.openai.widgetState ?? { selectedGiftId: null };
function selectGift(id) {
window.openai.setWidgetState({ ...current, selectedGiftId: id });
}
実コードではこれを React フックでラップします。
ランタイム API: callTool、sendFollowUpMessage など
これらのメソッドにより、ウィジェットは「描画」だけでなく、対話やサーバーとのやり取りも行えます。
典型的なシナリオ:
- callTool("search_gifts", { budget: 50 }) — ユーザーが「予算を変更」ボタンを押し、サーバーを叩いて UI を更新。
- sendFollowUpMessage({ prompt: "今より高価なアイデアを見せて" }) — ユーザーにテキスト入力を求める代わりに、follow-up ボタンでチャットに新規メッセージを作る。
- requestDisplayMode({ mode: "fullscreen" }) — inline モードが手狭なら、フルスクリーン表示を丁寧に依頼。
- openExternal({ href: "https://myshop.com/checkout?giftId=123" }) — 検証済みチャネルで外部サイト(チェックアウトやプロフィールなど)へ遷移。
いずれも、インターネットへ直接ではなく ChatGPT を介してやり取りされます。
環境コンテキスト: テーマ、モード、高さ、ロケール
theme、displayMode、maxHeight、locale のようなフィールドは、ウィジェットがどのような環境にいるかを示します。
例:
const theme = window.openai.theme; // "light" または "dark"
const mode = window.openai.displayMode; // "inline" | "fullscreen" | "pip"
const maxH = window.openai.maxHeight; // 利用可能な高さ
const locale = window.openai.locale; // "en-US", "de-DE", ...
これらのシグナルを使って次のことが可能です。
- テーマに合わせて色や余白を調整する;
- モード(inline vs fullscreen)に応じてレイアウトを切り替える;
- ユーザーの言語に合わせて UI ラベルをローカライズする(この話は別モジュールで詳述)。
プラットフォームは「スペースの量」「テーマ」「ロケール」を教えてくれます。これらは useOpenAIGlobal、useDisplayMode、useMaxHeight などのフックを通して活用し、ChatGPT に溶け込む見た目にするとよいでしょう。
4. window.openai のラッパーとしてのフック: グローバルを直接触らない
素の window.openai へのアクセスは試作には便利ですが、すぐにコードが混乱します。 イベント購読、undefined チェック、同じラッパーの重複…。 そのため Apps SDK の Next.js テンプレートには、詳細を隠し、リアクティブにしてくれる React フック群が用意されています。
フックのインデックスは典型的に次のようになります。
// app/hooks/openai/index.ts
export { useCallTool } from "./use-call-tool";
export { useSendMessage } from "./use-send-message";
export { useOpenExternal } from "./use-open-external";
export { useRequestDisplayMode, useRequestModal, useRequestClose } from "./use-request-display-mode";
export { useRequestCheckout } from "./use-request-checkout";
// State hooks
export { useDisplayMode } from "./use-display-mode";
export { useWidgetProps } from "./use-widget-props";
export { useWidgetState } from "./use-widget-state";
export { useOpenAIGlobal } from "./use-openai-global";
export { useMaxHeight } from "./use-max-height";
export { useIsChatGptApp } from "./use-is-chatgpt-app";
名称やパスはテンプレートにより多少異なりますが、考え方は同じです。 window.openai.* の代わりにフックを使います。主要なものを見ていきます。
useWidgetProps: ツールの入力と出力
useWidgetProps は通常、ウィジェットに必要なデータ( toolInput、toolOutput、toolResponseMetadata)や、 場合によっては isLoading のようなフラグを返します。
例:
import { useWidgetProps } from "../hooks/openai";
type Gift = { id: string; title: string; price: number };
export function GiftList() {
const { toolOutput } = useWidgetProps<{ gifts: Gift[] }>();
const gifts = toolOutput?.gifts ?? [];
if (!gifts.length) {
return <div>まだ候補のギフトはありません。</div>;
}
return (
<ul>
{gifts.map((g) => (
<li key={g.id}>{g.title} — ${g.price}</li>
))}
</ul>
);
}
コンポーネント内に window.openai は出てきません — それが望ましい形です。
useWidgetState: widgetState の「リアクティブラッパー」
useWidgetState を使うと widgetState を通常の React state のように扱えます。 [state、setState] が得られ、フックが内部で window.openai.widgetState および setWidgetState と同期します。
例:
import { useWidgetState } from "../hooks/openai";
type UiState = { selectedGiftId: string | null };
export function SelectedGiftIndicator() {
const [uiState, setUiState] = useWidgetState<UiState>(() => ({
selectedGiftId: null,
}));
if (!uiState?.selectedGiftId) {
return <div>ギフトはまだ選択されていません。</div>;
}
return (
<div>
選択中のギフト id={uiState.selectedGiftId}
<button onClick={() => setUiState({ selectedGiftId: null })}>
クリア
</button>
</div>
);
}
クリック後、setUiState は React の state を更新するだけでなく、新しい状態を ChatGPT 側にも保存します。
useOpenAIGlobal: window.openai の任意フィールドにアクセス
特定のグローバルフィールド(テーマやモードなど)にアクセスしたい場合は、汎用フック useOpenAIGlobal(key) を使います。 これはイベント openai:set_globals に購読し、常に最新の値を返します。
例:
import { useOpenAIGlobal } from "../hooks/openai";
export function ThemeAwareBlock() {
const theme = useOpenAIGlobal<"light" | "dark">("theme");
const background = theme === "dark" ? "#222" : "#fff";
const color = theme === "dark" ? "#fff" : "#000";
return <div style={{ background, color }}>ChatGPT のテーマを尊重します</div>;
}
useCallTool、useSendMessage、useOpenExternal ほか
- useCallTool(name) — 指定名の MCP ツールを呼ぶ関数を返す。callTool のラッパー。
- useSendMessage() — sendFollowUpMessage のラッパー。ウィジェットからメッセージを開始できる。
- useOpenExternal() — openExternal({ href }) の便利ヘルパー。
- useRequestDisplayMode() と useRequestModal() — モード変更やモーダル表示のためのラッパー。
ほぼすべてを使うミニウィジェット GiftGenius の例:
import {
useWidgetProps,
useWidgetState,
useCallTool,
useSendMessage,
useOpenExternal,
} from "../hooks/openai";
type Gift = { id: string; title: string; url: string; price: number };
export function GiftWidget() {
const { toolOutput } = useWidgetProps<{ gifts: Gift[] }>();
const gifts = toolOutput?.gifts ?? [];
const [ui, setUi] = useWidgetState<{ selectedId: string | null }>(() => ({
selectedId: null,
}));
const callSearch = useCallTool("search_gifts");
const sendMessage = useSendMessage();
const openExternal = useOpenExternal();
if (!gifts.length) {
return <div>今はアイデアがありません。GPT に結果の更新を依頼してみてください。</div>;
}
return (
<div>
{gifts.map((g) => (
<button
key={g.id}
style={{
display: "block",
fontWeight: ui?.selectedId === g.id ? "bold" : "normal",
}}
onClick={() => setUi({ selectedId: g.id })}
>
{g.title} — ${g.price}
</button>
))}
<div style={{ marginTop: 12 }}>
<button
onClick={() =>
sendMessage({ prompt: "今より高価なギフトを見せて。" })
}
>
もっとアイデアを求める
</button>
<button
onClick={async () => {
await callSearch({ budget: 200 });
}}
>
予算 $200 で更新
</button>
{ui?.selectedId && (
<button
onClick={() =>
openExternal({
href: `https://giftgenius.example.com/checkout?id=${ui.selectedId}`,
})
}
>
購入に進む
</button>
)}
</div>
</div>
);
}
このページはまだ粗い状態です(今後のモジュールで UX やエラー処理などを整えます)が、方針は示せています。 つまり、window.openai を直接叩かず、フックだけを使うということです。
5. 実践: サンドボックスと window.openai を体感する
「普通のサイトとは違うウィジェット」を実感するには、いくつかの練習が役立ちます。
練習: 「環境を触ってみる」
ウィジェットの現在の app/page.tsx に、初回レンダー時の簡単な副作用を追加します。
import { useEffect } from "react";
import { useIsChatGptApp } from "../hooks/openai";
export default function Root() {
const isChatGpt = useIsChatGptApp();
useEffect(() => {
if (typeof window !== "undefined") {
console.log("window.origin =", window.origin);
console.log("window.openai =", (window as any).openai);
}
}, []);
return (
<main>
<h1>GiftGenius widget</h1>
<p>ChatGPT 内で起動: {String(isChatGpt)}</p>
</main>
);
}
DevTools を開きます。ChatGPT のウィンドウ内(トンネルのビューワーが許せば)でも、ページを直接開いたローカルブラウザーでも構いません。両方で次を比較します。
- 通常のブラウザーで起動した場合、isChatGptApp は false、かつ window.openai はおそらく undefined;
- ChatGPT 経由で起動した場合、toolInput、toolOutput、theme などのフィールドを持つオブジェクトが見える。
これは良い直感的理解につながります。同じ React コードでも、環境によって振る舞いが変わるのです。 そのためにこそフックが用意されています。
練習: 「プラットフォームが与えるすべてを出力する」
デバッグ用の一時コンポーネントを追加します。
import { useWidgetProps, useOpenAIGlobal } from "../hooks/openai";
export function DebugPanel() {
const { toolInput, toolOutput, toolResponseMetadata } = useWidgetProps();
const theme = useOpenAIGlobal("theme");
const displayMode = useOpenAIGlobal("displayMode");
return (
<pre style={{ fontSize: 10, maxHeight: 200, overflow: "auto" }}>
{JSON.stringify(
{ toolInput, toolOutput, toolResponseMetadata, theme, displayMode },
null,
2
)}
</pre>
);
}
そして一時的に、メイン UI の下に <DebugPanel /> を挿入します。すると次のことが可視化されます。
- MCP から toolOutput に届いている具体的なフィールド;
- _meta に何が入っているか(例: locale、userLocation など);
- ウィジェットを展開したときに displayMode がどう変化するか。
その後、このコンポーネントは削除するか、DEBUG_WIDGET のようなフラグでオンオフできるようにしておくとよいでしょう。
6. 関係性: ChatGPT ↔ ウィジェット ↔ MCP/サーバー
ウィジェットを「主役」と誤解しないためにも、役割をもう一度整理しておきます。
- ユーザーがメッセージを送る: 「彼女向けのギフトを選んで。予算 50$」。
- ChatGPT のモデルは MCP ツール search_gifts を、次の引数で呼ぶと判断する: { recipient: "girlfriend", budget: 50 }。
- MCP サーバーはビジネスロジックを実行し、次を返す:
- モデル向けの簡潔な説明 content;
- ギフトの配列などを含む structuredContent;
- 技術的詳細(例: source と通貨)を含む _meta。
- ChatGPT は:
- ユーザーにテキストメッセージ(「いくつかの候補を見つけました…」)を表示;
- ウィジェット用 iframe を作成し、structuredContent と _meta を window.openai.toolOutput と toolResponseMetadata 経由で渡す。
- あなたのウィジェットは:
- toolOutput に基づいて UI をレンダー;
- ユーザー操作に応じて callTool を呼ぶか、follow-up を送る。
- モデルは、それらの結果を踏まえて次のアクションを決める。
ここから重要な教訓が得られます。ウィジェットがプロセスの唯一の主催者になることはないということです。 ウィジェットは、モデルと MCP サーバーから成るエコシステムの中で動く UI レイヤーです。 認可、プライベートデータへのアクセス、重いビジネスロジックといったことはサーバー側に置くべきです。 ウィジェットの責務は、使いやすいインターフェースと丁寧なユーザーとの対話です。
7. サンドボックスでのポリシーとルール
分離された iframe と window.openai という構成は、セキュリティとプライバシー要件に基づくものです。 OpenAI の公式ガイドでは、いくつかの原則が強調されています。
第一に、データ最小化。 ウィジェットを通じて可能な限り多くの PII(個人を特定できる情報)を引き出し、持ち去るべきではありません。 本当に必要なものはツールに明確に記述し、モデルとセキュリティ層はそのような呼び出しを厳しく監視します。
第二に、隠れたトラッキングやフィンガープリンティングの禁止。 ユーザーのデバイスを「覗き見る」仕組みや、ブラウザー指紋の収集、制限回避は認められません。 userAgent、userLocation などのパラメータは UX のヒントであり、認証や識別に使うものではありません。
第三に、structuredContent、_meta、widgetState に入れるものは、ユーザーが目にする、あるいは Store のレビュー担当者が見うる形で扱われます。したがって:
- API キー、トークン、パスワード、管理用シークレットなどを入れてはならない;
- ウィジェットの状態は、ユーザーがログやデバッグで見ても不自然に思わないよう設計する。
第四に、ネットワーク呼び出し。 ウィジェットからの外部 API への直接リクエストは、厳しく限定されたドメインに対して、かつセンシティブでない用途に限り許されます。 お金、アカウント、プライベートデータが絡む場合は、すべて MCP/backend を経由すべきです。
8. サンドボックスと window.openai でのよくあるミス
エラー №1: ウィジェットを「iframe 内の普通のサイト」だと思う。
慣れで window.parent を触ったり、ChatGPT のスタイルを変えようとしたり、localStorage を普段通り使おうとしがちです。 サンドボックスでは origin が別、ストレージは分離、DOM アクセスはブロックされるため、動かないか不安定です。 管理された環境に住んでおり、ホストとは window.openai とフックだけで会話するのだと受け入れましょう。
エラー №2: window.openai をあちこちで直接触る。
10 個のコンポーネントで window.openai.toolOutput を読むコードは、デバッグ困難への道です。 イベント、非同期、undefined チェックを自分で面倒を見ることになります。 代わりに最初から、useWidgetProps、useWidgetState、useOpenAIGlobal など、openai:set_globals を内包し状態を同期してくれるフックを使いましょう。
エラー №3: widgetState に何でも(特に秘密)を入れる。
つい、API の巨大な結果やアクセストークンを「念のため」入れたくなります。 その結果、コンテキストは膨れ、モデルの動作は悪化し、基本的なセキュリティ要件に違反します。 widgetState は小さく、UI シグナルだけを持ち、機密は絶対に入れないでください。
エラー №4: ウィジェットからインターネットに直接出ようとする。
サンドボックスからの fetch("https://api.superbank.com/...") は、ほぼ確実に CORS に引っかかります。 仮にうまく設定できても、安全ではなく制御もしにくい。 アカウント、お金、個人データが絡むすべては MCP ツールとして実装し、callTool やサーバー側を通じて実行してください。
エラー №5: ChatGPT の外で window.openai の安定性を当てにする。
ウィジェットを独立した SPA として起動し、window.openai が undefined になりうることを考慮しないケースがあります。 開発環境では「Cannot read properties of undefined」でクラッシュします。 useIsChatGptApp、typeof window !== "undefined" のチェック、そしてウィジェットが存在しない場合のフォールバック UI を用意しましょう。
エラー №6: 環境コンテキスト(theme, displayMode, maxHeight, locale)を無視する。
常に 2000px の固定高さ、常にダークテーマ、そしてデスクトップ前提のレイアウト…これではユーザー体験は妙なものになります。 プラットフォームは「スペース量」「テーマ」「ロケール」を教えてくれます。 useOpenAIGlobal、useDisplayMode、useMaxHeight などを通して活用し、ChatGPT に「馴染む」ウィジェットにしましょう。
エラー №7: 外部スクリプトでポリシーを「回避」しようとする。
トラッカーや外部 JS バンドルをこっそり読み込む、他ドメインのコードを実行する…といった誘惑が生じることがあります。 サンドボックスと CSP ポリシーはまさにそれを防ぐためにあります。外部スクリプトはブロックされ、回避の試みは Store でアプリが却下される近道です。
GO TO FULL VERSION