Edited at

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

More than 1 year has passed since last update.

これはChromium Browser アドベントカレンダーの十三日目の記事です。本記事ではHTML規格で定義されるWindowProxyを紹介します。ウェブ開発者にとって、ナビゲーションへの理解を深める一助になればと思います。


WindowProxyとはどのようなものか?

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

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


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

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

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

WindowProxyにはクロスオリジンの場合を扱うための処理がいくつも定義されています。少し具体例を見てみましょう。

HTML 7.4.1 [[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の意味ではなく、例外を投げないことが保証されている、という意味であることに注意してください3

HTML 7.4.5 [[GetOwnProperty]] ( P )

step 3. If ! IsPlatformObjectSameOrigin(W) is true, then return ! OrdinaryGetOwnProperty(W, P).

step 4. Let property be ! CrossOriginGetOwnPropertyHelper(W, P).

(抜粋、強調は引用者によります。)

プロパティアクセス一般は[[GetOwnProperty]]で定義されますが、クロスオリジンの場合にはCrossOriginGetOwnPropertyHelperに処理が委ねられ、特別なプロパティのみアクセスが許可されます。

このように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>を指し示すようになります。

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


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に保存しています。......さて、本当にそうでしょうか?

console.log(w_child_1 === w_child_2);

// => true

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

console.log(w_child_1.location.pathname);

// => "/child_2.html"

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


  1. 非表示となったナビゲーション前のウィンドウへアクセスし続けることがなくなる。w_child_1にアクセスし続けるJavaScriptコードを書いても、表示中のナビゲーション後のウィンドウへアクセスできる。[ウェブ開発者の利点]

  2. ナビゲーション前のウィンドウへアクセスする手段がなくなる4ことにより、ナビゲーション前のページやリソースを破棄できるようになる。より少ないメモリで動作可能になる。[エンドユーザの利点]

  3. ナビゲーション後のウィンドウが、ナビゲーション前のウィンドウにアクセスした場合(特にクロスオリジンの場合)の仕様や問題を考えなくて済むようになる。[ブラウザ開発者の利点 ;)]

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


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という仕組みがある。


  2. Windowオブジェクトだと思われがちなwindowparentなどは実際にはWindowProxyであり、いつの間にかプロクシされていた。

  3. クロスオリジンアクセスのサポートも(ついでに?)WindowProxyが行っている。

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


免責

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

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




おまけ

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


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

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


名称の変遷

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

WindowProxy
Window
おそらくの名前の由来

outer window
inner window
1つだったWindowが2つに分離され、outer/innerという言葉で区別される6

outer global
inner global
Windowに依存せずにglobal object一般に適用される/できる/すべきと考える。7

global proxy
global object
outer/innerという区別から"proxy"という本質を表現するようになる。

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


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

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

V8のオブジェクト群を表現するクラス階層につていはv8/src/objects.hのコメントが参考になります。本節に関係する部分を抜粋します。


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である(FunctionObjectのサブクラスである)点に対応しています。ECMAScriptのProxyはJSObjectクラスとは独立したJSProxyクラスで実装しています。v8::i::HeapObjectは実行時型情報を管理しているのでv8::i::HeapObject::IsJSObjectなどで実行時型検査が可能です。

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

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

// 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()が使われます。


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を提供しています。

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::CreateContextv8::Context::Newを使っています。


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

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

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


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)を使ってローカルプロセス実装に処理を委譲するので、実装の主要部分はローカルプロセス実装にあります8。ここではblink::WindowProxyblink::LocalWindowProxyを見ていきます。


blink::WindowProxy

blink::WindowProxyの主な役割はV8のglobal proxyを保持し続けることです。blink::WindowProxy::global_proxy_というメンバ変数にglobal proxyを保持しています。これには二つの目的があります。一つは、各フレーム(ブラウザのタブやiframe)に対応するglobal proxyを覚えておくこと。もう一つは、global proxyの寿命管理をすることです。

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

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

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つが必要になります。

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実行環境と一対一対応しています。





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



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



  3. HTML規格では2.1.9 DependenciesにてECMAScriptに依存する、と明言しており、ECMAScriptの記法で書かれている部分も少なくありません。エクスクラメーションマーク!も、内部スロット[[slot_name]]もECMAScriptの記法です。 



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



  5. Chromiumのソースコード(Window.idlなど)ではWindowProxyではなくWindowと書かれていますが、実際にはWindowProxyが返されます。ChromiumのIDLコンパイラがWindowProxyを型として認識しないため便宜上Windowと書いているだけです。(ちょっとかっこ悪いけど)動作には影響ないのでご安心ください。:) 



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



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



  8. マルチプロセスモデル(ローカル/リモートプロセス実装)をサポートするため、V8はv8::Context::NewRemoteContextというAPIを提供しています。このAPIはリモートプロセス用のglobal proxyをstubとして作成します。 



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