window.openのメモ


## はじめに
クリックしてダイアログを表示して処理する場合で、window.openを使用して対応する場合のメモ。
現状の画面(親ウィンドウ)から、新たにウィンドウを開いてその値を取得する手順です。

## window.openメソッド
- **親ウィンドウ** は、`window.open` を使って子ウィンドウを開き、`postMessage` を使って子ウィンドウから送られてきたデータを受け取ります。
- **子ウィンドウ** は、`window.opener.postMessage` を使って親ウィンドウに選択結果を送信し、送信後に自分自身を閉じます。



#### 親ウィンドウ (`index.html` 側のスクリプト)

```html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Window Open Example</title>
</head>
<body>

<h1>親ウィンドウ</h1>
<button id="openDialogBtn">モーダルを開く</button>
<p id="result">選択された結果: なし</p>

<script>
function openModalDialog(url) {
    const width = 500;
    const height = 400;
    const left = (window.screen.width - width) / 2;
    const top = (window.screen.height - height) / 2;

    return new Promise((resolve, reject) => {
        // ウィンドウを開く
        let dialogWindow = window.open(
            url, 
            'modalDialog', 
            `width=${width},height=${height},left=${left},top=${top}`
        );

        // ウィンドウがブロックされている場合
        if (!dialogWindow) {
            reject(new Error('ポップアップがブロックされました'));
            return;
        }

        // メッセージイベントのリスナーを追加（子ウィンドウからのメッセージを受信）
        window.addEventListener('message', function(event) {
            // オリジンを確認して、信頼できるソースからのメッセージのみ処理する
            if (event.origin !== window.location.origin) {
                return;
            }

            // メッセージを解決する
            resolve(event.data);
        });

        // ウィンドウが閉じられたことを定期的にチェック
        let interval = setInterval(() => {
            if (dialogWindow && dialogWindow.closed) {
                clearInterval(interval);
            }
        }, 500);
    });
}

// ボタンのクリックイベントでモーダルを開く
document.getElementById('openDialogBtn').addEventListener('click', () => {
    openModalDialog('child.html')
        .then((result) => {
            document.getElementById('result').innerText = `選択された結果: ${result}`;
        })
        .catch((error) => {
            alert(error.message);
        });
});


</script>

</body>
</html>
```

#### 子ウィンドウ (`child.html` 側のスクリプト)

```html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>子ウィンドウ</title>
</head>
<body>

<h1>子ウィンドウ</h1>
<p>何か選択してください:</p>
<button id="option1">選択肢1</button>
<button id="option2">選択肢2</button>

<script>
    function sendResult(result) {
        // 親ウィンドウにメッセージを送信
        window.opener.postMessage(result, window.location.origin);
    }

    document.getElementById('option1').addEventListener('click', () => {
        sendResult('選択肢1');
        window.close();  // ウィンドウを閉じる
    });

    document.getElementById('option2').addEventListener('click', () => {
        sendResult('選択肢2');
        window.close();  // ウィンドウを閉じる
    });
</script>

</body>
</html>
```

以下、親ウィンドウと子ウィンドウの詳細な解説です。

## 親ウィンドウ (`index.html` 側)

### 概要
`index.html` では、ボタンをクリックすると `window.open` を使って新しいウィンドウ（子ウィンドウ）を開き、`Promise` を使用して子ウィンドウのデータ（選択された結果）を受け取る処理を実装しています。

### スクリプトの流れ

1. **`openModalDialog` 関数**:
   - `window.open` を使って新しいウィンドウ（子ウィンドウ）を開きます。ここで、ウィンドウのサイズや位置を指定できます。第2引数 `'modalDialog'` はウィンドウの名前です。この名前で後からウィンドウを特定できます。
   - `Promise` を使って、非同期的に子ウィンドウからのデータを受け取ります。

2. **`window.open`**:
   - 新しいウィンドウを開くための関数です。第1引数にウィンドウで表示する URL（`child.html`）を指定し、第3引数でウィンドウのサイズや位置を指定しています。これで新しい子ウィンドウが開かれます。

3. **ポップアップブロックの確認**:
   - `dialogWindow` が `null` の場合は、ブラウザのポップアップブロッカーによってウィンドウの生成がブロックされたと考えられます。その場合は `Promise` を `reject` してエラーメッセージを表示します。

4. **メッセージイベントリスナー**:
   - 子ウィンドウから送信された `postMessage` を受信するため、`message` イベントをリッスンしています。
   - **`event.origin` の確認**: セキュリティ上の理由で、メッセージが同じオリジン（URLのスキーム、ホスト、ポートが同一）から送信されているか確認しています。
   - 正しいオリジンからメッセージが送信されていれば、`resolve` を使って `Promise` を完了させ、子ウィンドウからのデータを親ウィンドウで受け取ります。

5. **ウィンドウの閉じられた状態の確認**:
   - 定期的に `dialogWindow.closed` をチェックして、子ウィンドウが閉じられたかどうか確認しています。この部分では、特にデータの受け渡しに影響はありませんが、ウィンドウが閉じられているかを監視しています。

6. **ボタンのクリックイベント**:
   - ボタンをクリックすると `openModalDialog` を呼び出し、子ウィンドウを開きます。子ウィンドウで選択肢がクリックされ、データが渡されると、親ウィンドウに結果が表示されます。

---

## 子ウィンドウ (`child.html` 側)

### 概要
子ウィンドウでは、選択肢を選んだ結果を親ウィンドウに送信し、その後自分自身（子ウィンドウ）を閉じるという処理を行います。

### スクリプトの流れ

1. **`sendResult` 関数**:
   - 親ウィンドウにデータを送信するための関数です。選択された結果（`"選択肢1"` または `"選択肢2"`）を `window.opener.postMessage` を使って親ウィンドウに送信します。

2. **`window.opener.postMessage(result, window.location.origin)`**:
   - `window.opener` は、親ウィンドウを参照するオブジェクトです。`postMessage` メソッドを使って、親ウィンドウにデータを送信します。
   - `result` は選択された結果の値です（`"選択肢1"` や `"選択肢2"`）。
   - `window.location.origin` はメッセージが送信されるオリジン（プロトコル、ドメイン、ポート）を指定します。これにより、同じオリジン内でメッセージを送信できるようになります。

3. **`window.close()`**:
   - データを親ウィンドウに送信した後、`window.close()` で子ウィンドウを閉じます。

4. **選択肢のクリックイベント**:
   - ボタンがクリックされると、それぞれのボタンに対応する結果（`"選択肢1"` または `"選択肢2"`）を `sendResult` 関数を通じて親ウィンドウに送信し、その後子ウィンドウを閉じます。

---


## Promiseについて
子ウィンドウの値を取得するのに、子ウィンドウが閉じるのを確認してから親ウィンドウでその値を取得しますが、その時に`Promise`を使用しています、その使用に関しもう少し深堀します。

`Promise` は、JavaScript で非同期処理を扱うためのオブジェクトです。`Promise` は、非同期処理が「成功」または「失敗」したときに、その結果を処理するための方法を提供します。

### `Promise` の基本構造
`Promise` は次のような構造を持っています。

```javascript
new Promise((resolve, reject) => {
    // 非同期処理を実行
    if (成功) {
        resolve(結果); // 成功時
    } else {
        reject(エラー); // 失敗時
    }
});
```

- `resolve`: 非同期処理が成功した場合に呼び出され、成功結果を呼び出し元に渡します。
- `reject`: 非同期処理が失敗した場合に呼び出され、エラーを呼び出し元に渡します。

`Promise` を使うことで、非同期処理の結果がいつ完了するか分からない場合でも、完了した際に処理を続けられるようにすることができます。

### 親ウィンドウの `Promise` 使用について

親ウィンドウのコードでは、`window.open` で子ウィンドウを開き、そのウィンドウでユーザーが何らかの操作をしたときに結果を親ウィンドウに返すという非同期処理を行っています。この場合、`Promise` を使うことで、子ウィンドウの処理が完了した時点で親ウィンドウに結果を返すことができます。

#### `Promise` の流れ

親ウィンドウの `openModalDialog` 関数では、次のように `Promise` を使用しています。

### `openModalDialog` 関数での `Promise` 使用

```javascript
function openModalDialog(url) {
    return new Promise((resolve, reject) => {
        // 非同期処理: 子ウィンドウを開く
        let dialogWindow = window.open(url, 'modalDialog', `width=500,height=400`);

        // ポップアップがブロックされた場合、Promiseをrejectで終了
        if (!dialogWindow) {
            reject(new Error('ポップアップがブロックされました'));
            return;
        }

        // 子ウィンドウからのメッセージを待ち受ける
        window.addEventListener('message', function(event) {
            if (event.origin === window.location.origin) {
                // メッセージを受け取ったらPromiseをresolveして終了
                resolve(event.data);
            }
        });
    });
}
```

この関数の詳細な流れを見ていきます。

### 1. `new Promise` を返す
`openModalDialog` 関数は `Promise` を返します。これにより、非同期処理（子ウィンドウを開き、その結果を受け取るまでの処理）を待って処理を行えるようになります。

### 2. 子ウィンドウを開く (`window.open`)
まず `window.open` を使って子ウィンドウを開きます。ここで新しいウィンドウが開かれますが、この時点ではまだ子ウィンドウでユーザーが何を選択するかが分かりません。これが非同期処理の一つの例です。

- **同期処理と非同期処理の違い**:
  - 同期処理の場合、処理が終わるまで次の行のコードは実行されません。
  - 非同期処理の場合、次の行のコードはすぐに実行されますが、結果が得られるのは後になります。この後処理を待つために `Promise` を使います。

### 3. ポップアップがブロックされているかの確認
ポップアップがブロックされている場合、`window.open` は `null` を返します。この場合、`reject` メソッドを呼び出し、`Promise` を失敗（エラー）として処理します。これにより、親ウィンドウでエラーをキャッチでき、適切なエラーメッセージ（ポップアップがブロックされた）を表示できます。

### 4. メッセージの受信 (`postMessage`)
`Promise` 内で `window.addEventListener('message')` を使って、子ウィンドウからの `postMessage` を待ち受けます。

- `postMessage` でメッセージが受信されると、`resolve` が呼び出され、`Promise` が成功として完了します。
- このとき、子ウィンドウから送信されたデータ（`event.data`）が親ウィンドウに渡されます。

### 5. `Promise` の結果処理 (`then` と `catch`)
この `Promise` を返すことによって、親ウィンドウで子ウィンドウからの結果を待って次の処理を行うことができるようになります。

```javascript
openModalDialog('child.html')
    .then((result) => {
        // 子ウィンドウからのメッセージを表示
        document.getElementById('result').innerText = `選択された結果: ${result}`;
    })
    .catch((error) => {
        // ポップアップがブロックされたなどのエラー処理
        alert(error.message);
    });
```

- **`then`**: `Promise` が成功した場合（`resolve` された場合）、この部分の処理が実行されます。ここでは、子ウィンドウから受け取ったデータ（`result`）を親ウィンドウに表示しています。
- **`catch`**: `Promise` が失敗した場合（`reject` された場合）、エラーメッセージを表示します。たとえば、ポップアップがブロックされたときにこの部分が実行されます。

### `Promise` の利点
`Promise` を使うことで、非同期処理の結果（この場合は子ウィンドウでの選択結果）を簡単に扱うことができます。`Promise` がなければ、複雑なコールバック関数を使う必要があり、コードが読みづらくなることがありますが、`Promise` を使うと非同期処理を順序立てて記述することができます。

- **非同期処理の管理**: 非同期処理が成功するか失敗するかを、`resolve` と `reject` で明確に管理できる。
- **可読性**: コールバック関数を使わず、順序立てて処理を記述できるため、コードの可読性が向上する。

### まとめ
この `Promise` の使い方によって、親ウィンドウと子ウィンドウの間の非同期な通信をスムーズに管理できます。親ウィンドウでは子ウィンドウが完了するのを待ってから次の処理を行い、エラーが発生した場合には適切に対処できるようになっています。


## window.addEventListener('message', ...)について
window.addEventListener('message', ...)について、わかりにくいかもしれないのでもう少し説明を加えます。

`window.addEventListener('message', ...)` を使って、**子ウィンドウから送信されたデータを親ウィンドウで受信する**仕組みを実装しています。この `message` イベントリスナーは、子ウィンドウから `postMessage` によって送られてくるメッセージを待ち受け、受信したメッセージを処理するためのものです。

### 仕組みの流れ

1. **子ウィンドウ (`child.html`) が親ウィンドウにデータを送信**:
   子ウィンドウ側で、`window.opener.postMessage(result, window.location.origin)` を使用して親ウィンドウにメッセージを送信します。

   ```javascript
   window.opener.postMessage(result, window.location.origin);
   ```

   - `result` は子ウィンドウで選択されたデータです（例: `'選択肢1'` や `'選択肢2'`）。
   - `window.location.origin` は、親ウィンドウに送信する際のオリジン（プロトコル、ホスト、ポートの組み合わせ）を指定します。これにより、セキュリティを強化し、異なるオリジンからのデータ送信を防ぎます。

2. **親ウィンドウ (`index.html`) がデータを受信**:
   親ウィンドウ側では、`window.addEventListener('message', ...)` を使用して、子ウィンドウから送られてくるメッセージを受信します。

   ```javascript
   window.addEventListener('message', function(event) {
       // オリジンの確認（セキュリティ対策）
       if (event.origin === window.location.origin) {
           // 子ウィンドウからのメッセージを受け取ったら処理を実行
           resolve(event.data);  // Promiseを成功として終了し、データを親ウィンドウで処理
       }
   });
   ```

   この `message` イベントリスナーが、子ウィンドウから送信されたメッセージを受け取り、その内容を `event.data` を通じて処理します。

   - **`event.origin` の確認**: 受信したメッセージの送信元（オリジン）が、親ウィンドウのオリジンと一致しているか確認します。これは、他のサイトから送信されたメッセージを防ぐためのセキュリティ対策です。
   - **`resolve(event.data)`**: メッセージが正常に受信されたら、`Promise` の `resolve` 関数を呼び出して、親ウィンドウで処理が完了します。この `resolve` によって、メッセージ内容（`event.data`）が親ウィンドウに渡されます。

### なぜ `message` を使用するのか？
`postMessage` と `message` イベントリスナーの組み合わせは、異なるウィンドウ（親ウィンドウと子ウィンドウ、または異なるタブ）間で安全にデータを送受信するための標準的な方法です。

1. **異なるウィンドウ間のデータ通信**:
   通常、ウィンドウやフレーム間で直接データをやり取りすることはできませんが、`postMessage` を使用すると、クロスオリジン（異なるプロトコルやドメイン）であっても、セキュリティを保ちながらデータを送信できます。

2. **セキュリティ**:
   `postMessage` はセキュリティ機構を備えており、送信先のオリジンを指定することで、信頼できる送信元からのメッセージだけを受信することができます。この仕組みを使うことで、他のサイトからの不正なメッセージを無視できるため、安全にデータをやり取りすることができます。

### まとめ

- 親ウィンドウは `window.addEventListener('message', ...)` を使って、子ウィンドウからの `postMessage` を待ち受けます。
- 子ウィンドウは `window.opener.postMessage(result, window.location.origin)` で親ウィンドウにデータを送信します。
- 親ウィンドウは、受信したメッセージの `event.data` を使って処理を行い、子ウィンドウからのデータを受け取ります。

この `message` イベントリスナーを使うことで、親ウィンドウと子ウィンドウ間の通信を安全かつ非同期で行うことができます。

## 上記プログラムの動作について
デスクトップで今回のプログラムを動作させると、`file://` プロトコルでファイルを直接開くことになり、ブラウザのセキュリティポリシーにより `postMessage` の送信が制限されます。`postMessage` を使用するには、ファイルをローカルサーバー経由で開く必要があります。たとえば、ローカル環境で簡単にウェブサーバーを起動できる方法があります。

### ローカルサーバーのセットアップ方法

#### 1. Python を使用した簡単な HTTP サーバーのセットアップ
Python がインストールされている場合、次のコマンドで簡単な HTTP サーバーを立ち上げられます。

- **Python 3 系の場合**:
  ```bash
  python -m http.server 8000
  ```

- **Python 2 系の場合**:
  ```bash
  python -m SimpleHTTPServer 8000
  ```

これにより、現在のディレクトリをウェブサーバーとして動作させ、ブラウザから `http://localhost:8000` でアクセスできます。

#### 2. Node.js の `http-server` モジュールを使用した方法

Node.js がインストールされている場合、`http-server` をインストールして使用することもできます。

```bash
npm install -g http-server
```

その後、プロジェクトフォルダで次のコマンドを実行します。

```bash
http-server
```

これにより、`http://localhost:8080` などでサーバーが起動し、ブラウザからファイルにアクセスできるようになります。

### アクセス方法

ローカルサーバーを起動した後、`index.html` と `child.html` に `http://localhost:8000/index.html` のようにブラウザでアクセスして動作を確認してください。

#### 例: Python サーバー使用時
1. ターミナルで `python -m http.server 8000` を実行。
2. ブラウザで `http://localhost:8000/index.html` にアクセス。
3. `postMessage` によるデータ通信が `file://` ではなく、HTTP(S) プロトコルを使用するので、エラーが解消されます。

これにより、セキュリティエラーを回避し、`postMessage` を使用した親子ウィンドウ間の通信が正常に行えるようになります。


## 参考
https://developer.mozilla.org/ja/docs/Web/API/Window/open

https://www.kogures.com/hitoshi/javascript/windowOpen/index.html

https://qiita.com/www-tacos/items/863eedf12eec046e88ad