WindowProxy「俺の名前はいっぱいあってな…」

これは[Chromium Browser アドベントカレンダー](https://qiita.com/advent-calendar/2017/chromium)の十三日目の記事です。本記事ではHTML規格で定義される[`WindowProxy`](https://html.spec.whatwg.org/multipage/window-object.html#the-windowproxy-exotic-object)を紹介します。ウェブ開発者にとって、ナビゲーションへの理解を深める一助になればと思います。



# WindowProxyとはどのようなものか？

HTML規格で定義されている[`WindowProxy`](https://html.spec.whatwg.org/multipage/window-object.html#the-windowproxy-exotic-object)は、端的に言えば「`Window`オブジェクトを指し示すもの」であり、その名前の通り`Window`オブジェクトへのプロクシです。これは`Window`に対してのみ定義されている特別なプロクシであり、他のDOMオブジェクトはこのようなプロクシを持ちません。これは`Window`がナビゲーション(ページ遷移)をサポートする唯一のグローバルオブジェクトであるため[^workerproxy]です。なぜナビゲーションやグローバルオブジェクトが重要になるのかは、後ほど説明します。

[^workerproxy]: 各種Workerはグローバルオブジェクトではあっても、ナビゲーションをサポートしないため、`WindowProxy`に相当するWorkerProxyのようなものを必要としません。

特別な場合を除けばこのプロクシは何もしません。`WindowProxy`オブジェクトを`<wp>`、`Window`オブジェクトを`<w>`と書くことにすると、`<wp>.document`は`<w>.document`と同じことをします。当然これだけではわざわざ`WindowProxy`を定義する意味がありませんから、`WindowProxy`ならではの特別な性質が大きく二つあります。


## 特徴1 クロスオリジンアクセスのサポート

`WindowProxy`が担っている役割の一つはクロスオリジンアクセスを提供することです。本記事ではオリジン(生成元)の厳密な説明はしませんので、[MDNの説明](https://developer.mozilla.org/ja/docs/Web/Security/Same-origin_policy "同一オリジンポリシー")などを参考にしたり、ウェブで調べてください。とても大雑把な説明をすると、2つのwindowがあった時、ドメインが両方とも "https://chromium.org/" であれば自由に通信したりリソース共有してもよいが、一方が "https://chromium.org/" で他方が "https://example.org/" であった場合は許可しない、という仕組みです。前者を同一オリジン(同一生成元)、後者をクロスオリジンと呼んだりします[^cross-origin]。

[^cross-origin]: 厳密には、HTML規格では[same origin](https://html.spec.whatwg.org/multipage/origin.html#same-origin)と[same origin-domain](https://html.spec.whatwg.org/multipage/origin.html#same-origin-domain)という2つのコンセプトを定義して使い分けている上に、cross originというコンセプトは(わたしの知る限り)定義していません。本記事ではそこまで厳密な内容に踏み入らないので、クロスオリジンという言葉も使っています。ほとんどの場合same origin-domainではないケースをクロスオリジンと呼んでいます。

ほぼすべてのDOMオブジェクトはクロスオリジンアクセスを一切サポートしません。クロスオリジンにも関わらずアクセスしようとすると`SecurityError`が発生します。`WindowProxy`と`Location`の2つだけが例外的にクロスオリジンアクセスをサポートしています。

[`WindowProxy`](https://html.spec.whatwg.org/multipage/window-object.html#the-windowproxy-exotic-object)にはクロスオリジンの場合を扱うための処理がいくつも定義されています。少し具体例を見てみましょう。

[HTML 7.4.1 \[\[GetPrototypeOf\]\] \( \)](https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-getprototypeof)
step 1. Let W be the value of the [[Window]] internal slot of this.
step 2. If **! IsPlatformObjectSameOrigin(W) is true**, then return ! OrdinaryGetPrototypeOf(W).
step 3. Return null.
(強調は引用者によります。)

この定義を意訳するとクロスオリジンならば`<wp>.__proto__`は`null`を返し、同一オリジンならば`<w>.__proto__`を返す、という意味です。`! IsPlatformObjectSameOrigin(W)`の部分が同一オリジンかどうかの判定をしてます。ウェブ標準規格に慣れていない方は、エクスクラメーションマーク`!`はnegateの意味ではなく、[例外を投げないことが保証されている](https://tc39.github.io/ecma262/#sec-returnifabrupt-shorthands)、という意味であることに注意してください[^ecmascript-in-webspec]。

[^ecmascript-in-webspec]: HTML規格では[2.1.9 Dependencies](https://html.spec.whatwg.org/multipage/infrastructure.html#dependencies)にてECMAScriptに依存する、と明言しており、ECMAScriptの記法で書かれている部分も少なくありません。エクスクラメーションマーク`!`も、内部スロット`[[slot_name]]`もECMAScriptの記法です。

[HTML 7.4.5 \[\[GetOwnProperty\]\] \( P \)](https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-getownproperty)
step 3. If **! IsPlatformObjectSameOrigin(W) is true**, then return ! OrdinaryGetOwnProperty(W, P).
step 4. Let property be **! CrossOriginGetOwnPropertyHelper(W, P)**.
(抜粋、強調は引用者によります。)

プロパティアクセス一般は`[[GetOwnProperty]]`で定義されますが、クロスオリジンの場合には[`CrossOriginGetOwnPropertyHelper`](https://html.spec.whatwg.org/multipage/browsers.html#crossorigingetownpropertyhelper-\(-o,-p-\))に処理が委ねられ、特別なプロパティのみアクセスが許可されます。

このように`WindowProxy`にはクロスオリジンアクセスを定義するという重要な役割があるのですが、実は`Location`もクロスオリジンアクセスを(LocationProxyなしで)定義しています。`Window`にのみプロクシが必要とされる本質的な理由は、*クロスオリジンアクセスとは別*にあります。`Window`のみにプロクシが定義されている理由、それはナビゲーションにあります。


## 特徴2 "現在の=最新の"ウィンドウへのプロクシ

`WindowProxy`はそもそも何をするプロクシなのかというと「現在のウィンドウ」へのプロクシです。ナビゲーション(ページ遷移)が発生すると、ナビゲーション前の古いウィンドウと、ナビゲーション後の新しいウィンドウができます。`WindowProxy`は常に最新のウィンドウへとプロクシします。`WindowProxy`は`[[Window]]`という内部スロット(internal slot)を持ち、ここに`Window`オブジェクトへのリファレンスを持ちます。ウィンドウ`<w1>`からウィンドウ`<w2>`へナビゲーションする場合、

    <wp>.[[Window]] === <w1>

という状態から

    <wp>.[[Window]] === <w2>

という状態になります。`WindowProxy` `<wp>`が新しい`Window`オブジェクト`<w2>`を指し示すようになります。

具体例を通して見てみましょう。

```html:parent.html
<iframe id="child" src="child_1.html"></iframe>
<script>
var w_child_1, w_child_2;

onload = () => {

let child_frame = document.getElementById("child");

// |w_child_1| is a child window before a navigation.
w_child_1 = child_frame.contentWindow;

// Let the child window navigate to another page.
w_child_1.location = "child_2.html";

// |w_child_2| is another child window after the navigation.
child_frame.onload = () => {
  w_child_2 = child_frame.contentWindow;
};

};  // onload
</script>
```

parent.htmlが何をやっているか簡単に説明します。iframe内のウィンドウをchild_1.htmlからchild_2.htmlへナビゲーションさせています。ナビゲーション前のウィンドウを`w_child_1`に、ナビゲーション後のウィンドウを`w_child_2`に保存しています。......さて、本当にそうでしょうか？

```javascript
console.log(w_child_1 === w_child_2);
// => true
```

`w_child_1`と`w_child_2`は同じオブジェクトであるようです。ピンときた方もいるでしょう。実は`w_child_1`も`w_child_2`もどちらも`Window`オブジェクトではなく`WindowProxy`なのです。したがって`w_child_1`もナビゲーション後のウィンドウを指し示します。

```javascript
console.log(w_child_1.location.pathname);
// => "/child_2.html"
```

これは無用な混乱を防ぐ、とても便利な機能なのです。ぱっと思いつくだけでも以下のような利点があります。

1. 非表示となったナビゲーション前のウィンドウへアクセスし続けることがなくなる。`w_child_1`にアクセスし続けるJavaScriptコードを書いても、表示中のナビゲーション後のウィンドウへアクセスできる。\[ウェブ開発者の利点\]
1. ナビゲーション前のウィンドウへアクセスする手段がなくなる[^access-to-old-document]ことにより、ナビゲーション前のページやリソースを破棄できるようになる。より少ないメモリで動作可能になる。\[エンドユーザの利点\]
1. ナビゲーション後のウィンドウが、ナビゲーション前のウィンドウにアクセスした場合(特にクロスオリジンの場合)の仕様や問題を考えなくて済むようになる。\[ブラウザ開発者の利点 ;\)\]

[^access-to-old-document]: 古いページのドキュメントを保存しておく手段が存在するため、`WindowProxy`があるからといって、必ずしも古いページを破棄できるとは限りません。しかしウェブ開発者が故意に古いページのドキュメントを保存しない限り、ほとんどの場合には古いページを破棄できるようになります。

これらの利点はナビゲーション前の古いウィンドウへのアクセス手段がなくなるからこそ活きてきます。このためHTML規格は古いウィンドウへのアクセスをなくすように定義されています。一般に`Window`オブジェクトを返すと考えられているWeb APIは、すべて`WindowProxy`を返すように定義されています。上記の例で使われた`contentWindow`だけでなく、`window`, `self`, `parent`, `opener`, `open()`の返り値などすべてです。[`Window`インタフェースの定義](https://html.spec.whatwg.org/multipage/window-object.html#the-window-object)を見てみると、`Window`型ではなく`WindowProxy`型と書かれているのが分かります[^chromium-idls-dont-use-windowproxy]。

[^chromium-idls-dont-use-windowproxy]: Chromiumのソースコード([Window.idl](https://cs.chromium.org/chromium/src/third_party/WebKit/Source/core/frame/Window.idl)など)では`WindowProxy`ではなく`Window`と書かれていますが、実際には`WindowProxy`が返されます。ChromiumのIDLコンパイラが`WindowProxy`を型として認識しないため便宜上`Window`と書いているだけです。(ちょっとかっこ悪いけど)動作には影響ないのでご安心ください。:)

```webidl:Window
[PrimaryGlobal, ...] 
interface Window : EventTarget {
  [Unforgeable] readonly attribute WindowProxy window;
  [Replaceable] readonly attribute WindowProxy? parent;
  WindowProxy? open(optional USVString url = "about:blank", optional DOMString target = "_blank", optional [TreatNullAs=EmptyString] DOMString features = "");
  ...
};
```

`WindowProxy`の最後の特徴は、ナビゲーション前後でオブジェクトが入れ替わらないことです。`Window`オブジェクトは新しいページ用に新しいオブジェクトが作成されますが、`WindowProxy`それ自体は同じオブジェクトが使い続けられます。当然であり自明だと思われるかもしれませんが、必ずしもそうではありません。新しい`WindowProxy`を作成することも(合理的ではないにせよ)可能だからです。上記の例を使うと`w_child_1 !== w_child_2`でありつつも`w_child_1.document === w_child_2.document`である(2つの`WindowProxy`が同じ`Window`オブジェクトを指し示す)ことができるからです。これは同値性に関して混乱を招くだけなので、実際にはナビゲーション時に`WindowProxy`を新しく作ることはありません。ナビゲーション時にはウィンドウやドキュメントなどすべてが入れ替わるのに対して、`WindowProxy`だけは同一であることは特記に値します。



# まとめ

`Window`に較べると知名度が低いと思われる`WindowProxy`を紹介しました。次の3点を理解してもらえたなら幸いです。

1. ナビゲーション後も最新の`Window`を参照するために`WindowProxy`という仕組みがある。
1. `Window`オブジェクトだと思われがちな`window`や`parent`などは実際には`WindowProxy`であり、いつの間にかプロクシされていた。
1. クロスオリジンアクセスのサポートも(ついでに？)`WindowProxy`が行っている。

ここまで読んでくださってありがとうございます。



# 免責

この記事は私の個人的な意見に基づき書かれております。私の所属する組織、団体には一切の関係はありません。

また記事の内容の正しさは保証できません。特にウェブ規格はliving standardを採用しているため、随時更新されています。最新の情報はご自身でご確認ください。



--------

--------

# おまけ

本文として書いてみたら思いのほか長くなり、しかもつまらなかったので、削除しようと思ったけど勿体ないので「おまけ」にした文章がこちらです。Chromiumにおける`WindowProxy`の実装について書いています。



## V8 JavaScriptエンジンにおける実装

HTML規格における`WindowProxy`の仕様について説明してきましたが、ここからはChromiumにおける実装の話をします。本節ではChromiumで採用しているJavaScriptエンジンであるV8が`WindowProxy`をどのように実装しているのかを見てみます。


### 名称の変遷

実装を見ていくにあたり、名称の変遷を見ておきましょう。HTML規格では`WindowProxy`と`Window`という名前で呼んでいますが、V8やBlinkなどのChromium内のコンポーネントは*主に*"global proxy"と"global object"という名前で呼びます。名前が統一されていない理由は、各ブラウザの実装が先に作られHTMLの仕様が後から策定されたことや、元々一つだったWindowを二つに分離するという発想から生まれたのが現在の`WindowProxy`であることによります。`WindowProxy`, `Window`は以下の別名で呼ばれることがあります。これはChromiumに限りません。古そうなものから順に並べています。

| WindowProxy | Window | おそらくの名前の由来 |
|:-:|:-:|:--|
| outer window | inner window | 1つだったWindowが2つに分離され、outer/innerという言葉で区別される[^outer-vs-inner]。 |
| outer global | inner global | Windowに依存せずにglobal object一般に適用される/できる/すべきと考える。[^v8-doesnt-depend-on-html] |
| global proxy | global object | outer/innerという区別から"proxy"という本質を表現するようになる。 |

[^outer-vs-inner]: 偶然か必然か分かりませんが、この記事の草稿はSplatoon2の"インナー対アウター"フェスの合間を縫って書かれています。

[^v8-doesnt-depend-on-html]: ECMAScriptを実装することを主目的とするJavaScriptエンジンなどは、"window"というHTML依存の用語/コンセプトを使わずに、一般化された仕組みとしてサポートしたいという意図があります。

このように名称の変遷を見ると、V8などが主に"global proxy", "global object"という名前を使うのも納得頂けると思います。ソースコードを読む際には、過渡期の用語や、誤解/混乱による用語の乱れや誤用にもご注意ください。


### v8::i::JSGlobalProxyとv8::i::JSGlobalObject

V8のコア部分はC++で実装されており`v8`という名前空間を使っています。Public API以外の内部実装は`v8::internal`という名前空間で定義されています。例えばV8仮想マシンを表現する`Isolate`というクラスは、`v8::Isolate`と`v8::internal::Isolate`の2つのクラスとして実装されています。V8の実装内部では頻繁に`internal`名前空間にアクセスするため、略記として`i`(`v8::i`)というエイリアスが定義されています。本記事でもこの略記を使います。

V8のオブジェクト群を表現するクラス階層につていは[v8/src/objects.hのコメント](https://cs.chromium.org/chromium/src/v8/src/objects.h?l=46)が参考になります。本節に関係する部分を抜粋します。

```c++:v8/src/objects.h
// Inheritance hierarchy:
// - Object
//   - HeapObject   (superclass for everything allocated in the heap)
//     - JSReceiver  (suitable for property access)
//       - JSObject
//         - JSFunction
//         - JSGlobalObject
//         - JSGlobalProxy
//       - JSProxy
```

JavaScriptオブジェクト一般は`v8::i::JSObject`クラスとして実装しています。`JSFunction`クラスが`JSObject`クラスのサブクラスである点は、ECMAScriptにおいてfunctionがcallable objectとして定義されている点や`Function.prototype.__proto__ === Object.prototype`である(`Function`が`Object`のサブクラスである)点に対応しています。ECMAScriptのProxyは`JSObject`クラスとは独立した`JSProxy`クラスで実装しています。`v8::i::HeapObject`は実行時型情報を管理しているので`v8::i::HeapObject::IsJSObject`などで実行時型検査が可能です。

global objectに対するスペシャリゼーション(特殊化)は[`v8::i::JSGlobalObject`](https://cs.chromium.org/chromium/src/v8/src/objects.h?q=JSGlobalObject&l=3603)というサブクラスを定義することで行っています。`JSGlobalObject`固有の機能として、`global_proxy()`メンバ関数と`IsDetached()`メンバ関数があります。`global_proxy()`は自身を参照している(あるいはかつて参照していた)`JSGlobalProxy`を返します。この`JSGlobalProxy`は現在は他の`JSGlobalObject`を指し示している可能性があるため一対一対応ではありません。`IsDetached()`は自身が`JSGlobalProxy`から切り離されていれば(`JSGlobalProxy`が自身を指し示さなくなっていれば)`true`を返します。

global proxyに対するスペシャリゼーションは[`v8::i::JSGlobalProxy`](https://cs.chromium.org/chromium/src/v8/src/objects.h?q=JSGlobalProxy&l=3577)というサブクラスを定義することで行っています。しかし目立った固有の機能というものはありません。global proxyはglobal objectを指し示す必要があるのでは？ それこそがglobal proxyの肝要なのでは？ と思うかもしれません(思ってください)。この肝腎要の部分はプロトタイプチェーンを使って実装しています。`JSGlobalObject`も`JSGlobalProxy`も`JSObject`のサブクラスでした。つまりどちらも`__proto__`によるプロトタイプチェーンをサポートしています。`JSGlobalProxy`から`JSGlobalObject`への参照はこのプロトタイプチェーンで実装されています。グローバルオブジェクトを参照するための特別なスロットを持つわけではなく、`__proto__`用のスロットを再利用する形で実装しています。これはプロパティ・ルックアップのプロクシを考えると自然な選択であることが分かります。プロトタイプチェーンやプロパティ・ルックアップについてご存じない方は[MDNの継承とプロトタイプチェーン](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Inheritance_and_the_prototype_chain "MDN 継承とプロトタイプチェーン")を参考にしてください。

```javascript
// Initial setup
gp = <global_proxy>;
g = <global_object>;
gp.__proto__ = g;

// Suppose that there is a property on |g|.
g.annin = "tofu";

console.log(gp.annin);
// => "tofu"
// |gp.annin| finds out |gp.__proto__.annin|
// that is |g.annin|.
```

このようにプロトタイプチェーンを使って実装しておけば、プロパティ・ルックアップに対するプロクシ処理を改めて実装しなくてもよくなります。ただし上記の例に加えて、初期設定を除く`gp.__proto__`へのアクセスを`g.__proto__`へのアクセスにプロクシするなど、多少の特別処理は必要になります。V8は`hidden_prototype`という内部フラグでプロトタイプチェーンの処理を一部変更しています。

`v8::i::JSGlobalProxy`に対する特別処理は他にも多くあり、実行時型検査`v8::i::HeapObject::IsJSGlobalProxy()`を使って書かれています。例として新しいプロパティをセットする場合を考えましょう。`v8::Object::Set(context, key, value)`がオブジェクトにプロパティをセットするAPIです。新しいプロパティをセットする場合には「どのオブジェクト上にプロパティを作成するか」を決定するために`v8::i::LookupIterator::GetStoreTarget()`が使われます。

```c++:v8/src/lookup.h
// v8::i::LookupIterator::GetStoreTarget()
class LookupIterator {
 public:
  Handle<JSObject> GetStoreTarget() const {
    DCHECK(receiver_->IsJSObject());
    if (receiver_->IsJSGlobalProxy()) {
      Map* map = JSGlobalProxy::cast(*receiver_)->map();
      if (map->has_hidden_prototype()) {
        return handle(JSGlobalObject::cast(map->prototype()), isolate_);
      }
    }
    return Handle<JSObject>::cast(receiver_);
  }
};
```

`GetStoreTarget()`は、レシーバオブジェクトが`JSGlobalProxy`でかつ`hidden_prototype`を使っている場合にはプロトタイプチェーンを一つ辿り`JSGlobalObject`を返す、そうでなければレシーバオブジェクト自体を返す、という処理を行っています。この処理があるため、グローバル変数を作成した場合にはglobal proxyではなくglobal object上に変数(プロパティ)が作成されます。そしてナビゲーションによりglobal objectの入れ替えが行われると、グローバル変数はすべて消えたように見えます。


### global proxyの作成とglobal objectの入れ替え

HTML規格におけるナビゲーションは、V8ではglobal proxyの指し示すglobal objectの入れ替えという形で表現されます。V8ではプロトタイプチェーンを使ってglobal proxyからglobal objectへのリファレンスを表現していますから、プロトタイプチェーンを書き換えることでナビゲーションを実装できます。またナビゲーション時には新しい`Window`オブジェクト(新しいglobal object)の作成も必要になります。V8はこれらすべてを一括で行う`v8::Context::New`というAPIを提供しています。

```c++
v8::Local<v8::Object> global_proxy;  // initially empty

v8::Local<v8::Context> context =
    v8::Context::New(isolate,
                     extension_configuration,
                     global_object_template,
                     global_proxy);

global_proxy = context->Global();

v8::Local<v8::Object> global_object = global_proxy->GetPrototype();
```

最初はglobal proxyがないところから始まるので`global_proxy`は空ハンドルです。`v8::Context::New`は新しいglobal objectとそれに一対一対応する`v8::Context`を作成します。`global_proxy`が空ハンドルの場合、新しいglobal proxyを作成します。空ハンドルでない場合には与えられた`global_proxy`を再利用します。global objectと`v8::Context`にglobal proxyを結びつけ、global proxyのプロトタイプチェーンにglobal objectをセットします。新規作成された`v8::Context`からはglobal proxyとglobal objectを取り出せます。

つまり`v8::Context::New`は一度目の呼び出しでは新規ウィンドウ作成相当、二度目以降の呼び出しではナビゲーション相当の処理を行います。Chromiumの中では[`blink::LocalWindowProxy::CreateContext`](https://cs.chromium.org/chromium/src/third_party/WebKit/Source/bindings/core/v8/LocalWindowProxy.cpp?q=CreateContext&l=227)で`v8::Context::New`を使っています。



## Blinkレンダリングエンジンの実装

JavaScriptエンジンV8の実装の後は、ChromiumのレンダリングエンジンBlinkにおける`WindowProxy`の実装を見てみましょう。プロクシ部分はV8が既に実装していますし、新規ウィンドウ作成やナビゲーションは`v8::Context::New`を使って実装できることを前節で見ましたので、それ以外の部分に注目してみましょう。

V8のglobal proxyを管理しているBlinkのクラスは`blink::WindowProxy`です。サブクラスに`blink::LocalWindowProxy`と`blink::RemoteWindowProxy`があります。この他にも`LocalFrame`/`RemoteFrame`や`LocalDOMWindow`/`RemoteDOMWindow`など`Local`と`Remote`というプリフィクスを持つペアのクラスがありますが、これらはそれぞれローカルプロセス実装とリモートプロセス実装に対応しています。Chromiumはマルチプロセスモデルを採用しており、すべてのページやiframeが同一のプロセスで処理されるとは限りません。アクセス先が同一プロセスである場合にはローカルプロセス実装で処理され、プロセスが異なる場合にはリモートプロセス実装を通して処理を委譲します。次の模式図を使って説明します。

```text:Communication between Local and Remote
   process_X                                       process_Y
     |                                               |
     + window_A as LocalDOMWindow     <==(IPC)==>    + window_A as RemoteDOMWindow
     |                                               |
     + window_B as RemoteDOMWindow    <==(IPC)==>    + window_B as LocalDOMWindow
```

ローカル/リモートは相対的なので、図中のprocess_Xにとってはprocess_X自身がローカルプロセスでありprocess_Yがリモートプロセスです。process_Xはwindow_Aの実体(ローカルプロセス実装)を持ちますが、window_Bの実体は持っていません。process_Xの中でwindow_Aがwindow_Bにアクセスした場合、`RemoteDOMWindow`を通してprocess_Yに処理をリクエストします。process_Yにとってはwindow_Bはローカルプロセス実装なのでリクエストされた処理を実行します。リモートプロセス実装はプロセス間通信(IPC)を使ってローカルプロセス実装に処理を委譲するので、実装の主要部分はローカルプロセス実装にあります[^v8-remote-context]。ここでは`blink::WindowProxy`と`blink::LocalWindowProxy`を見ていきます。

[^v8-remote-context]: マルチプロセスモデル(ローカル/リモートプロセス実装)をサポートするため、V8は[`v8::Context::NewRemoteContext`](https://cs.chromium.org/chromium/src/v8/include/v8.h?q=NewRemoteContext&l=8674)というAPIを提供しています。このAPIはリモートプロセス用のglobal proxyをstubとして作成します。


### `blink::WindowProxy`

`blink::WindowProxy`の主な役割はV8のglobal proxyを保持し続けることです。[`blink::WindowProxy::global_proxy_`](https://cs.chromium.org/chromium/src/third_party/WebKit/Source/bindings/core/v8/WindowProxy.h?q=global_proxy_&l=262)というメンバ変数にglobal proxyを保持しています。これには二つの目的があります。一つは、各フレーム(ブラウザのタブやiframe)に対応するglobal proxyを覚えておくこと。もう一つは、global proxyの寿命管理をすることです。

global proxyを含むV8オブジェクトは不要になると(参照元がなくなると)V8 GCによって回収されます。ブラウザのタブで表示中のglobal proxyが回収されては困りますから、Blinkが必要であると判断できるglobal proxyへは強参照を持ちます。document treeから切り離されたiframeなど、画面表示されなくなったフレームのglobal proxyは、Blinkから見ると必要ではないように見えるので弱参照へ切り替えます。この仕組は次のシナリオで必要になります。

```javascript
var child_iframe = document.getElementById('child-iframe');
var child_global_proxy = child_iframe.contentWindow;

// Remove |child_iframe| from the document tree.
child_iframe.remove();
// |child_iframe| is no longer displayed.
//
// blink::WindowProxy::global_proxy_ becomes a WEAK reference.

child_global_proxy;
// |child_global_proxy| is a STRONG reference to the global proxy
// of the (past) child window.  Still alive!
child_global_proxy.self;
// Blink needs to return the global proxy as |self| attribute.

child_global_proxy = null;
// Finally there is no strong reference to the global proxy.
```

`child_iframe.remove()`でiframeがdocument treeから切り離され(unloadイベントなどの処理もすべて終わると)、Blinkからは直接にはglobal proxyは必要なくなります。強参照を持ち続けるとメモリリークを引き起こすので、弱参照に切り替えます。この時、ウェブページのJavaScriptコード中にはglobal proxyへの参照が残っている可能性があります。上記の例では`child_global_proxy`がglobal proxyへの強参照を持っています。この場合、`child_global_proxy.self`というWeb APIをサポートするためには、Blinkもglobal proxyへの参照を必要とします。よって弱参照を保持します。JavaScriptコードからもglobal proxyへの参照がなくなると、V8 GCが回収します。


### `blink::LocalWindowProxy`

`blink::LocalWindowProxy`はローカルプロセス実装なので細々と色々なことを実装していますが、プロクシとしての重要な役割はV8オブジェクト(global proxy, global object)とBlinkオブジェクト(LocalDOMWindow)を対応付けることです。

V8オブジェクトはPublic APIの`v8::Object`クラスとして提供されます。`v8::Object`はinternal fieldという内部スロットを提供します。internal fieldに何を保存するかはembedder(この場合Blink)の自由です。Blinkは、Blinkオブジェクトへの生ポインタを`v8::Object`のinternal fieldに埋め込むことで、V8オブジェクトからBlinkオブジェクトへのマッピングを実装しています。Blinkオブジェクトは`v8::Persistent<v8::Object>`ハンドルを持つことで、BlinkオブジェクトからV8オブジェクトへのマッピングを実装しています。一言で言ってしまえば、V8オブジェクトとBlinkオブジェクトはそれぞれ互いへのリファレンスを持ち合っています。

`blink::LocalWindowProxy`は`v8::Context::New`を使って新しいglobal objectを作りますが、その後速やかに、`Window`オブジェクトのローカルプロセス実装である`blink::LocalDOMWindow`をglobal objectと対応付けます。同時に新規作成された、あるいは再利用されたglobal proxyも同じ`blink::LocalDOMWindow`に対応付けます[^globalproxy-points-to-localdomwindow]。ナビゲーションが起こるたびにこの対応付けは更新されます。

[^globalproxy-points-to-localdomwindow]: global proxyはプロトタイプチェーンによりglobal objectを指し示していますから、"global proxy → global object → `blink::LocalDOMWindow`"と辿ることができます。しかしリモートプロセス用global proxyはglobal objectを持たないので、この方法は使えません。そこでglobal proxyに直接`blink::DOMWindow`を対応付けています。global proxyを特別扱いしなくてもV8オブジェクトとBlinkオブジェクトの相互変換ができるという利点もあります。

`blink::RemoteWindowProxy`もglobal proxyと`blink::RemoteDOMWindow`の対応付けが主な役割です。


### `blink::WindowProxyManager`

最後に`blink::WindowProxyManager`を紹介します。HTMLの`WindowProxy`とはほぼ関係ないですが、`blink::WindowProxy`とは関係するので紹介します。

ChromiumはChrome Extensionsという拡張機能をサポートしています。Chrome Extensionsはウェブページとは独立したJavaScript実行環境で実行します(ただしcontent scriptは例外)。Chrome Extensionsが複数実行される場合、それぞれのChrome Extensions毎にJavaScript実行環境を用意することでsandbox化しています。Chromiumでは"world"という言葉でこれらのJavaScript実行環境を区別しています。ウェブページのJavaScript実行環境は"main world"、Chrome ExtensionsのJavaScript実行環境は"isolated world"と呼んでいます。isolated worldは一般に複数あります。

ブラウザのタブが2つ(frame_A, frame_B)とChrome Extensionsが2つ(isolated_world_X, isolated_world_Y)がある場合、JavaScript実行環境は以下の6つが必要になります。

```text
1. (frame_A, main_world)          4. (frame_B, main_world)
2. (frame_A, isolated_world_X)    5. (frame_B, isolated_world_X)
3. (frame_A, isolated_world_Y)    6. (frame_B, isolated_world_Y)
```

これらの対応関係を管理するのが`blink::WindowProxyManager`の役割です。
`blink::WindowProxyManager`は各フレームにつき一つ作られ、`blink::WindowProxyManager::GetWindowProxy(world)`がworldに対応する`blink::WindowProxy`を返します。`blink::WindowProxy`はglobal proxyと一対一対応であり、すなわちJavaScript実行環境と一対一対応しています。