これは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
が発生します。WindowProxy
とLocation
の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>
を指し示すようになります。
具体例を通して見てみましょう。
<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_1
とw_child_2
は同じオブジェクトであるようです。ピンときた方もいるでしょう。実はw_child_1
もw_child_2
もどちらもWindow
オブジェクトではなくWindowProxy
なのです。したがってw_child_1
もナビゲーション後のウィンドウを指し示します。
console.log(w_child_1.location.pathname);
// => "/child_2.html"
これは無用な混乱を防ぐ、とても便利な機能なのです。ぱっと思いつくだけでも以下のような利点があります。
- 非表示となったナビゲーション前のウィンドウへアクセスし続けることがなくなる。
w_child_1
にアクセスし続けるJavaScriptコードを書いても、表示中のナビゲーション後のウィンドウへアクセスできる。[ウェブ開発者の利点] - ナビゲーション前のウィンドウへアクセスする手段がなくなる4ことにより、ナビゲーション前のページやリソースを破棄できるようになる。より少ないメモリで動作可能になる。[エンドユーザの利点]
- ナビゲーション後のウィンドウが、ナビゲーション前のウィンドウにアクセスした場合(特にクロスオリジンの場合)の仕様や問題を考えなくて済むようになる。[ブラウザ開発者の利点 ;)]
これらの利点はナビゲーション前の古いウィンドウへのアクセス手段がなくなるからこそ活きてきます。このためHTML規格は古いウィンドウへのアクセスをなくすように定義されています。一般にWindow
オブジェクトを返すと考えられているWeb APIは、すべてWindowProxy
を返すように定義されています。上記の例で使われたcontentWindow
だけでなく、window
, self
, parent
, opener
, open()
の返り値などすべてです。Window
インタフェースの定義を見てみると、Window
型ではなくWindowProxy
型と書かれているのが分かります5。
[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点を理解してもらえたなら幸いです。
- ナビゲーション後も最新の
Window
を参照するためにWindowProxy
という仕組みがある。 -
Window
オブジェクトだと思われがちなwindow
やparent
などは実際にはWindowProxy
であり、いつの間にかプロクシされていた。 - クロスオリジンアクセスのサポートも(ついでに?)
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という言葉で区別される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::Isolate
とv8::internal::Isolate
の2つのクラスとして実装されています。V8の実装内部では頻繁にinternal
名前空間にアクセスするため、略記としてi
(v8::i
)というエイリアスが定義されています。本記事でもこの略記を使います。
V8のオブジェクト群を表現するクラス階層につていは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
というサブクラスを定義することで行っています。JSGlobalObject
固有の機能として、global_proxy()
メンバ関数とIsDetached()
メンバ関数があります。global_proxy()
は自身を参照している(あるいはかつて参照していた)JSGlobalProxy
を返します。このJSGlobalProxy
は現在は他のJSGlobalObject
を指し示している可能性があるため一対一対応ではありません。IsDetached()
は自身がJSGlobalProxy
から切り離されていれば(JSGlobalProxy
が自身を指し示さなくなっていれば)true
を返します。
global proxyに対するスペシャリゼーションはv8::i::JSGlobalProxy
というサブクラスを定義することで行っています。しかし目立った固有の機能というものはありません。global proxyはglobal objectを指し示す必要があるのでは? それこそがglobal proxyの肝要なのでは? と思うかもしれません(思ってください)。この肝腎要の部分はプロトタイプチェーンを使って実装しています。JSGlobalObject
もJSGlobalProxy
もJSObject
のサブクラスでした。つまりどちらも__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::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::CreateContext
で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が同一のプロセスで処理されるとは限りません。アクセス先が同一プロセスである場合にはローカルプロセス実装で処理され、プロセスが異なる場合にはリモートプロセス実装を通して処理を委譲します。次の模式図を使って説明します。
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::WindowProxy
とblink::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::LocalWindowProxy
はv8::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実行環境と一対一対応しています。
-
各種Workerはグローバルオブジェクトではあっても、ナビゲーションをサポートしないため、
WindowProxy
に相当するWorkerProxyのようなものを必要としません。 ↩ -
厳密には、HTML規格ではsame originとsame origin-domainという2つのコンセプトを定義して使い分けている上に、cross originというコンセプトは(わたしの知る限り)定義していません。本記事ではそこまで厳密な内容に踏み入らないので、クロスオリジンという言葉も使っています。ほとんどの場合same origin-domainではないケースをクロスオリジンと呼んでいます。 ↩
-
HTML規格では2.1.9 DependenciesにてECMAScriptに依存する、と明言しており、ECMAScriptの記法で書かれている部分も少なくありません。エクスクラメーションマーク
!
も、内部スロット[[slot_name]]
もECMAScriptの記法です。 ↩ -
古いページのドキュメントを保存しておく手段が存在するため、
WindowProxy
があるからといって、必ずしも古いページを破棄できるとは限りません。しかしウェブ開発者が故意に古いページのドキュメントを保存しない限り、ほとんどの場合には古いページを破棄できるようになります。 ↩ -
Chromiumのソースコード(Window.idlなど)では
WindowProxy
ではなくWindow
と書かれていますが、実際にはWindowProxy
が返されます。ChromiumのIDLコンパイラがWindowProxy
を型として認識しないため便宜上Window
と書いているだけです。(ちょっとかっこ悪いけど)動作には影響ないのでご安心ください。:) ↩ -
偶然か必然か分かりませんが、この記事の草稿はSplatoon2の"インナー対アウター"フェスの合間を縫って書かれています。 ↩
-
ECMAScriptを実装することを主目的とするJavaScriptエンジンなどは、"window"というHTML依存の用語/コンセプトを使わずに、一般化された仕組みとしてサポートしたいという意図があります。 ↩
-
マルチプロセスモデル(ローカル/リモートプロセス実装)をサポートするため、V8は
v8::Context::NewRemoteContext
というAPIを提供しています。このAPIはリモートプロセス用のglobal proxyをstubとして作成します。 ↩ -
global proxyはプロトタイプチェーンによりglobal objectを指し示していますから、"global proxy → global object →
blink::LocalDOMWindow
"と辿ることができます。しかしリモートプロセス用global proxyはglobal objectを持たないので、この方法は使えません。そこでglobal proxyに直接blink::DOMWindow
を対応付けています。global proxyを特別扱いしなくてもV8オブジェクトとBlinkオブジェクトの相互変換ができるという利点もあります。 ↩