1. 序論
この節は規範的ではない。This section is non-normative.
web アプリの処理能特性の測定法は、 web アプリをより高速にするために重要な側面になる。 [JSMEASURE] などの JavaScript に基づく仕組みは、 アプリにおける利用者の待ち時間( latency )測定用の包括的な手段を供せる一方で、 多くの事例で,それらは端点間における待ち時間の完全なあるいは詳細な様相を供せない。 例えば,次の JavaScript は、 ページが全部的に load されるまでの時間を,ごく素朴に測定しようと試みる: Accurately measuring performance characteristics of web applications is an important aspect of making web applications faster. While JavaScript-based mechanisms, such as the one described in [JSMEASURE], can provide comprehensive instrumentation for user latency measurements within an application, in many cases, they are unable to provide a complete or detailed end-to-end latency picture. For example, the following JavaScript shows a naive attempt to measure the time it takes to fully load a page:
<html>
<head>
<script type="text/javascript">
var start = new Date().getTime();
function onLoad() {
var now = new Date().getTime();
var latency = now - start;
alert("page loading time: " + latency);
}
</script>
</head>
<body onload="onLoad()">
<!--
ページ本体の内容…
Main page body goes from here. -->
</body>
</html>
上のスクリプトは head
タグ内の最初の JavaScript コードが実行されて以降から,ページが load されるまでの時間は計算するが、
サーバからページを得るために要した時間や, ページの初期化 lifecycle についての情報は与えない。
The above script calculates the time it takes to load the page after the first bit of JavaScript in the head is executed, but it does not give any information about the time it takes to get the page from the server, or the initialization lifecycle of the page.
この仕様は、
文書のナビに関係する高分解能な処理能計量データを格納する/検索取得するための,
[PERFORMANCE-TIMELINE-2]
に関与する PerformanceNavigationTiming
インタフェースを定義する。
PerformanceNavigationTiming
インタフェースは [HR-TIME] を利用するので、
すべての時刻値は,
当のエントリに関連する設定群オブジェクトの時刻起点を基準に測定される。
【したがって、 PerformanceNavigationTiming
インタフェースが返す時刻値 0 は,時刻起点を表すことになる。】
This specification defines the PerformanceNavigationTiming interface which participates in the [PERFORMANCE-TIMELINE-2] to store and retrieve high resolution performance metric data related to the navigation of a document. As the PerformanceNavigationTiming interface uses [HR-TIME], all time values are measured with respect to the time origin of the entry's relevant settings object.
例えば,HTTP 応答の終了がナビの開始から 100ms 後であったとするなら、
PerformanceNavigationTiming
のデータは次の様になるであろう:
For example, if we know that the response end occurs 100ms after the start of navigation, the PerformanceNavigationTiming data could look like so:
startTime: 0.000 /* ナビ要請の開始時刻 start time of the navigation request */ responseEnd: 100.000 /* 最後のバイトが受信された高分解能な時刻 high resolution time of last received byte */
文書のナビに関係する正確な計時データを得るために,
PerformanceNavigationTiming
インタフェースを利用する例を次のスクリプトに示す:
The following script shows how a developer can use the PerformanceNavigationTiming interface to obtain accurate timing data related to the navigation of the document:
<script> function showNavigationDetails() { /* 最初のエントリを取得する Get the first entry */ const [entry] = performance.getEntriesByType("navigation"); /* 結果を開発者コンソール内に表の形で示す Show it in a nice table in the developer console */ console.table(entry.toJSON()); } </script> <body onload="showNavigationDetails()">
(結果はコンソールではなく,ここに示される)【 他ページでも利用できるブックマークレット(結果はコンソールに示される) 】【 レベル 1 実装用のものもある 】
2. 各種用語
【 この節の内容うち一部は省略する。 】 The construction "a Foo object", where Foo is actually an interface, is sometimes used instead of the more accurate "an object implementing the interface Foo.
【この仕様の API の記述に現れる】 現在の文書 ( current document )は、 【これ°に関連する大域オブジェクトである】window に結び付けられた文書を指す。 The term current document refers to the document associated with the Window object's newest Document object.
【 加えて, “前文書” ( previous document )は、 現在の文書へナビゲートされる前の時点で,同じナビ可能にて作動中な文書であったものを指す(もしあれば)。 】
この仕様を通して、 時刻は,文書のナビの開始からミリ秒数で測定される。 例えば,文書のナビの開始は、 時刻 0 で生じる。 語 “〜の時刻” は、[ 文書のナビの開始から その時点までに経過したミリ秒数 ]による時刻を指す。 この[ 時刻の定義 ]は、 [HR-TIME] に基づく。 Throughout this work, all time values are measured in milliseconds since the start of navigation of the document. For example, the start of navigation of the document occurs at time 0. The term current time refers to the number of milliseconds since the start of navigation of the document until the current moment in time. This definition of time is based on [HR-TIME] specification.
4. ナビ計時属性
【 この節は規範的ではない。 (この節の原文のタイトルは “処理モデル” であるが、 処理モデルは,各種 “計時情報” として他の仕様( [FETCH], [HTML] など)に統合された。) 】 4. Process4.1. Processing Model
PerformanceNavigationTiming
インタフェースにて定義される計時属性を次の図式に示す。
括弧内の属性は、
同一生成元でない文書を孕むナビでは可用でない。
Figure 1 This figure illustrates the timing attributes defined by the PerformanceNavigationTiming interface. Attributes in parenthesis indicate that they may not be available for navigations involving documents from different origins.startTime
【 = 0 になる 】unloadEventStart
)unloadEventEnd
)domInteractive
domContentLoadedEventStart
domContentLoadedEventEnd
domComplete
loadEventStart
loadEventEnd
【
原文では,[
リダイレクト 〜 応答
]の箇所にも
PerformanceResourceTiming
[RESOURCE-TIMING] に定義される各属性が記されているが、
その仕様の図式と重複するので,和訳では省略する。
】【
この訳では、
原文の図式( SVG )を HTML と CSS による等価な図式に差し替えている。
】
プライバシーの考慮点
この節は規範的ではない。This section is non-normative.
- 情報の流出 5.1.1. Information disclosure
- 注意深く細工された計時攻撃の利用により、 末端利用者による[ 閲覧/行動 ]の履歴が流出する可能性がある。 一例として、 unload 時刻から前のページの unload ハンドラに要した時間が明らかにされ,利用者のログイン状態°の推定に利用され得る。 これらの攻撃は、 文書を unload するときに — 新たなページでセッション履歴を更新する アルゴリズム [HTML] の中で — 同一生成元の検査を施行することにより軽減される。 There is the potential for disclosing an end-user's browsing and activity history by using carefully crafted timing attacks. For instance, the unloading time reveals how long the previous page takes to execute its unload handler, which could be used to infer the user's login status. These attacks have been mitigated by enforcing the same origin check algorithm when unloading a document, as detailed in the HTML spec.
- 緩い同一生成元施策は、 文書間に渡る,未承認の訪問に対する十分な保護を供さない。 共有ホストにおいては、 信頼できない第三者主体が,同じ IP アドレスの異なるポート上で HTTP サーバをホストし得る。 The relaxed same origin policy doesn't provide sufficient protection against unauthorized visits across documents. In shared hosting, an untrusted third party is able to host an HTTP server at the same IP address but on a different port.
- ディレクトリ間をまたがるアクセス 5.1.2. Cross-directory access
-
同じホスト名を共有する異なるページ
— 例えば、
同じサイトにホストされている,利用者が生成した内容を伴う, 異なる作者による内容 —
は、
パス名に基いてアクセスを制約するような特能がないため,同一生成元から来たものと見なされる。
これらのページ間のナビに際しては、
後のページから,リダイレクションや
unload
イベントに対する計時など,前のページの計時情報へのアクセスが可能になる。 Different pages sharing one host name, for example contents from different authors hosted on sites with user generated content are considered from the same origin because there is no feature to restrict the access by pathname. Navigating between these pages allows a latter page to access timing information of the previous one, such as timing regarding redirection and unload event.
セキュリティの考慮点
この節は規範的ではない。This section is non-normative.
PerformanceNavigationTiming
インタフェースは、
前文書についての計時情報を現在の文書に公開する。
PerformanceNavigationTiming
の属性のうち,前文書の情報を含むものへのアクセスを制限するため、
前文書を unload するときに
— 新たなページでセッション履歴を更新する アルゴリズム [HTML] の中で —
同一生成元施策が施行され,【失敗した場合は】前文書に関係する属性は 0 に設定される。
The PerformanceNavigationTiming interface exposes timing information about the previous document to the current document. To limit the access to PerformanceNavigationTiming attributes which include information on the previous document, the previous document unloading algorithm enforces the same origin policy and attributes related to the previous document are set to zero.
- プロキシサーバの検出法 5.2.1. Detecting proxy servers
-
UA と web サーバとの間にプロキシが配備されている場合、
connectStart
属性とconnectEnd
属性との間の時区間は、 web サーバではなく,プロキシと UA との間の待ち時間を示すものになる。 それにより、 web サーバは,プロキシの存在を推定できる可能性がある。 SOCKS プロキシについては、 この時区間にはプロキシ認証に要した時間とプロキシが web サーバへの接続に要した時間も含まれ,プロキシの検出を難しくする。 HTTP プロキシの場合、 UA はプロキシサーバについての知識を一切持たないこともあるので、 この攻撃を常に軽減できるとは限らない。 In case a proxy is deployed between the user agent and the web server, the time interval between the connectStart and the connectEnd attributes indicates the delay between the user agent and the proxy instead of the web server. With that, web server can potentially infer the existence of the proxy. For SOCKS proxy, this time interval includes the proxy authentication time and time the proxy takes to connect to the web server, which obfuscate the proxy detection. In case of an HTTP proxy, the user agent might not have any knowledge about the proxy server at all so it's not always feasible to mitigate this attack.
廃用
この節では、
以前に [NAVIGATION-TIMING] レベル 1 にて導入された[
属性, インタフェース
]を定義し,後方互換性のために ここに保つ。
作者には、
以下のインタフェースを利用するべきでない
— 新たな PerformanceNavigationTiming
インタフェースを利用することを強く勧める。
変更点と改善点の要約を見よ。
This section defines attributes and interfaces previously introduced in [NAVIGATION-TIMING] Level 1 and are kept here for backwards compatibility. Authors should not use the following interfaces and are strongly advised to use the new PerformanceNavigationTiming interface—see summary of changes and improvements.
【この訳( レベル 1 )に特有な表記規約】
この節にて原文が参照する HTTP 用語は,過去の HTTP 仕様 [RFC2616] を参照しているが、 この訳では,最新な HTTP 仕様( RFC 9110 〜 9112 )の等価な記述を参照することにする。
用語 “前文書( previous document )” は、 現在の文書へナビゲートされる前の時点で,当のナビ可能にて作動中な文書であったものを指す(明確には定義されていない)。
以下における 局所キャッシュ等 は、[ HTTP キャッシュ/局所リソース ]を指す総称である。
PerformanceTiming
インタフェース
[Exposed=Window] interfacePerformanceTiming
{ readonly attribute unsigned long longnavigationStart
; readonly attribute unsigned long longunloadEventStart
; readonly attribute unsigned long longunloadEventEnd
; readonly attribute unsigned long longredirectStart
; readonly attribute unsigned long longredirectEnd
; readonly attribute unsigned long longfetchStart
; readonly attribute unsigned long longdomainLookupStart
; readonly attribute unsigned long longdomainLookupEnd
; readonly attribute unsigned long longconnectStart
; readonly attribute unsigned long longconnectEnd
; readonly attribute unsigned long longsecureConnectionStart
; readonly attribute unsigned long longrequestStart
; readonly attribute unsigned long longresponseStart
; readonly attribute unsigned long longresponseEnd
; readonly attribute unsigned long longdomLoading
; readonly attribute unsigned long longdomInteractive
; readonly attribute unsigned long longdomContentLoadedEventStart
; readonly attribute unsigned long longdomContentLoadedEventEnd
; readonly attribute unsigned long longdomComplete
; readonly attribute unsigned long longloadEventStart
; readonly attribute unsigned long longloadEventEnd
; [Default] objecttoJSON
(); };
navigationStart
からの時間差)
】
(結果はコンソールではなく,ここに示される)
【 他ページでも利用できるブックマークレット(結果はコンソールに示される) 】【 レベル 2 実装用のものもある 】
注記: この節に定義されるすべての時刻値は、 1970 年 1 月 1 日, 午前 0 時( UTC )からミリ秒数で測定される。 Note All time values defined in this section are measured in milliseconds since midnight of January 1, 1970 (UTC).
navigationStart
- 前文書があるならば UA が前文書に対し unload を prompt し終えた直後の時刻 / 他の場合は 現在の文書が作成された時刻を返すものとする。 This attribute must return the time immediately after the user agent finishes prompting to unload the previous document. If there is no previous document, this attribute must return the time the current document is created.
- 注記:
この属性は、
PerformanceNavigationTiming
用には定義されていない。 作者は、 代わりにtimeOrigin
を利用して,等価な時刻印を得れる。 Note This attribute is not defined for PerformanceNavigationTiming. Instead, authors can use timeOrigin to obtain an equivalent timestamp. unloadEventStart
-
前文書がある, かつ
前文書と現在の文書が同一生成元に属する場合は,UA が前文書の
unload
イベントを開始する直前の時刻 / 他の場合は 0 を返すものとする。 If the previous document and the current document have the same origin, this attribute must return the time immediately before the user agent starts the unload event of the previous document. If there is no previous document or the previous document has a different origin than the current document, this attribute must return zero. unloadEventEnd
-
前文書はある, かつ
前文書と現在の文書が同一生成元に属する, かつ
前文書の
unload
イベントは完了している 場合は,UA が そのイベントを終えた直後の時刻 / 他の場合は 0 を返すものとする。 If the previous document and the current document have the same same origin, this attribute must return the time immediately after the user agent finishes the unload event of the previous document. If there is no previous document or the previous document has a different origin than the current document or the unload is not yet completed, this attribute must return zero. -
ナビゲート時に HTTP リダイレクトがあって, かつ その中に同一生成元に属さない HTTP リダイレクトがある場合、[
unloadEventStart
,unloadEventEnd
]とも 0 を返すものとする。 If there are HTTP redirects when navigating and not all the redirects are from the same origin, both PerformanceTiming.unloadEventStart and PerformanceTiming.unloadEventEnd must return zero. redirectStart
-
ナビゲート時に HTTP リダイレクトがあって, かつ
すべての HTTP リダイレクトが同一生成元に属する場合は,
fetchStart
と同じ値 / 他の場合は 0 を返すものとする。 If there are HTTP redirects when navigating and if all the redirects are from the same origin, this attribute must return the starting time of the fetch that initiates the redirect. Otherwise, this attribute must return zero. redirectEnd
- ナビゲート時に HTTP リダイレクトがあって, かつ すべての HTTP リダイレクトが同一生成元に属する場合は,その最後のリダイレクトを成す応答の最後のバイトを受信した直後の時刻 / 他の場合は 0 を返すものとする。 If there are HTTP redirects when navigating and all redirects are from the same origin, this attribute must return the time immediately after receiving the last byte of the response of the last redirect. Otherwise, this attribute must return zero.
fetchStart
-
GET
要請メソッドを利用して新たなリソースが fetch された場合は,UA が HTTP キャッシュの検査を開始する直前の時刻 / 他の場合は UA がリソースの fetch を開始した時刻 を返すものとする。 If the new resource is to be fetched using a "GET" request method, fetchStart must return the time immediately before the user agent starts checking the HTTP cache. Otherwise, it must return the time when the user agent starts fetching the resource. domainLookupStart
-
現在の文書のドメイン名検索を開始する直前の時刻を返すものとする。
持続的な接続が利用されている,または
現在の文書が局所キャッシュ等から検索取得されている場合、
この属性は
fetchStart
と同じ値を返すものとする。 This attribute must return the time immediately before the user agent starts the domain name lookup for the current document. If a persistent connection [RFC2616] is used or the current document is retrieved from the HTTP cache or local resources, this attribute must return the same value as PerformanceTiming.fetchStart. domainLookupEnd
-
UA が現在の文書に対するドメイン名検索を終えた直後の時刻を返すものとする。
持続的な接続が利用されている,または
現在の文書が局所キャッシュ等から検索取得されている場合、
この属性は
fetchStart
と同じ値を返すものとする。 This attribute must return the time immediately after the user agent finishes the domain name lookup for the current document. If a persistent connection [RFC2616] is used or the current document is retrieved from the HTTP cache or local resources, this attribute must return the same value as PerformanceTiming.fetchStart. - 注記:
HTTP キャッシュからの内容の 検査と検索取得は fetch 処理の一部である。
それは、[
requestStart
,responseStart
,responseEnd
]属性が受け持つ。 Note Checking and retrieving contents from the HTTP cache [RFC2616] is part of the fetching process. It's covered by the PerformanceTiming.requestStart, PerformanceTiming.responseStart and PerformanceTiming.responseEnd attributes. - 注記:
UA がキャッシュ内にドメイン情報をすでに持っている場合、[
domainLookupStart
,domainLookupEnd
]は,順に,UA によるキャッシュからのドメインデータの検索取得を[ 開始, 終了 ]した時刻を表現する。 Note In case where the user agent already has the domain information in cache, domainLookupStart and domainLookupEnd represent the times when the user agent starts and ends the domain data retrieval from the cache. connectStart
-
UA が文書を検索取得するためにサーバへの接続を確立し始める直前の時刻を返すものとする。
持続的な接続が利用されている,または
現在の文書が局所キャッシュ等から検索取得されている場合、
この属性は
domainLookupEnd
と同じ値を返すものとする。 This attribute must return the time immediately before the user agent start establishing the connection to the server to retrieve the document. If a persistent connection [RFC2616] is used or the current document is retrieved from the HTTP cache or local resources, this attribute must return value of PerformanceTiming.domainLookupEnd. connectEnd
-
UA が文書を検索取得するためのサーバへの接続を確立し終えた直後の時刻を返すものとする。
持続的な接続が利用されている,または
現在の文書が局所キャッシュ等から検索取得されている場合、
この属性は
domainLookupEnd
と同じ値を返すものとする。 This attribute must return the time immediately after the user agent finishes establishing the connection to the server to retrieve the current document. If a persistent connection [RFC2616] is used or the current document is retrieved from the HTTP cache or local resources, this attribute must return the value of PerformanceTiming.domainLookupEnd. -
トランスポート接続が失敗し, かつ UA が接続を再び開いた場合、[
connectStart
,connectEnd
]は,新たな接続に対応する値を返すべきである。 If the transport connection fails and the user agent reopens a connection, PerformanceTiming.connectStart and PerformanceTiming.connectEnd should return the corresponding values of the new connection. -
connectEnd
には、 SSL ハンドシェイクや SOCKS 認証も含めて,トランスポート接続を確立するのに要した時間も含めるものとする。 PerformanceTiming.connectEnd must include the time interval to establish the transport connection as well as other time interval such as SSL handshake and SOCKS authentication. secureConnectionStart
-
この属性のサポートは任意選択である。
UA は、
この属性を可用にしない場合は,
undefined
を返すものとする。 可用にする場合、[[ 現在のページの URL スキーム [URL] = "https
" ]ならば 現在の接続をセキュアにするためのハンドシェイク処理を開始する直前の時刻 / 他の場合は 0 ]を返すものとする。 This attribute is optional. User agents that don't have this attribute available must set it as undefined. When this attribute is available, if the scheme [URL] of the current page is "https", this attribute must return the time immediately before the user agent starts the handshake process to secure the current connection. If this attribute is available but HTTPS is not used, this attribute must return zero. requestStart
- UA が,現在の文書を[ サーバから, または局所キャッシュ等から ]得るために 要請を開始した直前の時刻を返すものとする。 This attribute must return the time immediately before the user agent starts requesting the current document from the server, or from the HTTP cache or from local resources.
-
要請を送信した後にトランスポート接続が失敗して, UA が接続を再び開いて要請を送信し直していた場合、
requestStart
は,新たな要請に対応している値を返すべきである。 If the transport connection fails after a request is sent and the user agent reopens a connection and resend the request, PerformanceTiming.requestStart should return the corresponding values of the new request. -
注記: このインタフェースには
requestEnd
のような類いの,要請の送信の完了を表現する属性は含まれていない: Note This interface does not include an attribute to represent the completion of sending the request, e.g., requestEnd.- UA からの要請の送信の完了は、 (その種の属性の便益のほとんどを占める)ネットワークトランスポートにおける対応する完了時刻を常に指示するとは限らない。 Completion of sending the request from the user agent does not always indicate the corresponding completion time in the network transport, which brings most of the benefit of having such an attribute.
- 一部の UA においては、 HTTP 層のカプセル化に因り, 要請の送信を実際に完了した時刻を決定するのにコストがかかる。 Some user agents have high cost to determine the actual completion time of sending the request due to the HTTP layer encapsulation.
responseStart
- UA がサーバからの, または局所キャッシュ等からの,応答の最初のバイトを受信した直後の時刻を返すものとする。 This attribute must return the time immediately after the user agent receives the first byte of the response from the server, or from the HTTP cache or from local resources.
responseEnd
- UA が 現在の文書の最後のバイトを受信した直後の時刻と, トランスポート接続が閉じらる直前の時刻のうち,早い方を返すものとする。 ここでの文書は、[ サーバ, 局所キャッシュ等 ]いずれかから受信されたものも含まれる。 This attribute must return the time immediately after the user agent receives the last byte of the current document or immediately before the transport connection is closed, whichever comes first. The document here can be received either from the server, the HTTP cache or from local resources.
domLoading
-
UA が文書の現在の準備度を "
loading
" に設定する直前の時刻を返すものとする。 This attribute must return the time immediately before the user agent sets the current document readiness to "loading". - 既存の UA 内で文書が いつ作成されたかの違いに因り,この属性が返す値は実装に特有であり、 有意義な計量に利用されるべきではない。 Due to differences in when a Document object is created in existing user agents, the value returned by the domLoading is implementation specific and should not be used in meaningful metrics.
domInteractive
-
UA が文書の現在の準備度を "
interactive
" に設定する直前の時刻を返すものとする。 This attribute must return the time immediately before the user agent sets the current document readiness to "interactive". domContentLoadedEventStart
-
UA が文書に向けて
DOMContentLoaded
イベントを発火する直前の時刻を返すものとする。 This attribute must return the time immediately before the user agent fires the DOMContentLoaded event at the Document. domContentLoadedEventEnd
-
文書の
DOMContentLoaded
イベント が完了した直後の時刻を返すものとする。 This attribute must return the time immediately after the document's DOMContentLoaded event completes. domComplete
-
UA が文書の現在の準備度を "
complete
" に設定する直前の時刻を返すものとする。 This attribute must return the time immediately before the user agent sets the current document readiness to "complete". -
文書の現在の準備度が複数回にわたり同じ状態に変化した場合、
domLoading
,domInteractive
,domContentLoadedEventStart
,domContentLoadedEventEnd
,domComplete
は、 最初に生じた 文書の現在の準備度の変化に対応している時刻を返すものとする。 If the current document readiness changes to the same state multiple times, PerformanceTiming.domLoading, PerformanceTiming.domInteractive, PerformanceTiming.domContentLoadedEventStart, PerformanceTiming.domContentLoadedEventEnd and PerformanceTiming.domComplete must return the time of the first occurrence of the corresponding document readiness change. loadEventStart
-
現在の文書に
load
イベントが発火される直前の時刻を返すものとする。load
イベントがまだ発火されていないときには、 0 を返すものとする。 This attribute must return the time immediately before the load event of the current document is fired. It must return zero when the load event is not fired yet. loadEventEnd
-
現在の文書の
load
イベントの発火が完了した時刻を返すものとする。load
イベントがまだ発火されていない, またはまだ完了していないときには、 0 を返すものとする。 This attribute must return the time when the load event of the current document is completed. It must return zero when the load event is not fired or is not completed. toJSON
- これ°用の既定の toJSON 手続きを走らす。 Runs the default toJSON steps for this.
Performance
インタフェースに対する拡張
[Exposed=Window] partial interfacePerformance
{ [SameObject] readonly attributePerformanceTiming
timing
; [SameObject] readonly attributePerformanceNavigation
navigation
; };
Performance
インタフェースは [PERFORMANCE-TIMELINE-2] にて定義される。
The Performance interface is defined in [PERFORMANCE-TIMELINE-2].
timing
- 最後の非リダイレクト ナビ以降の閲覧文脈に関係する計時情報を表現する。 The timing attribute represents the timing information related to the browsing contexts since the last non-redirect navigation.\
-
この属性は
PerformanceTiming
インタフェースにより定義される。 This attribute is defined by the PerformanceTiming interface. navigation
-
この属性は
PerformanceNavigation
インタフェースにより定義される。 The navigation attribute is defined by the PerformanceNavigation interface.
謝辞
貢献された次の方々に感謝する:
Thanks to Anne Van Kesteren, Arvind Jain, Boris Zbarsky, Jason Weber, Jonas Sicking, James Simonsen, Karen Anderson, Nic Jansma, Philippe Le Hegaret, Steve Souders, Todd Reifsteck, Tony Gentilcore, William Chan and Zhiheng Wang for their contributions to this work.